about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/google_auth_oauthlib
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/google_auth_oauthlib')
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py23
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py507
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py151
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py172
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py124
6 files changed, 977 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py
new file mode 100644
index 00000000..1905f9a4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py
@@ -0,0 +1,23 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://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.
+
+"""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"]
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py
new file mode 100644
index 00000000..e564ca43
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py
@@ -0,0 +1,507 @@
+# Copyright 2016 Google Inc.
+#
+# 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.
+
+"""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", None)
+
+        (
+            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.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 an error 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.
+        """
+        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:
+                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.
+            authorization_response = wsgi_app.last_request_uri.replace("http", "https")
+            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")]
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py
new file mode 100644
index 00000000..25462f4c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py
@@ -0,0 +1,151 @@
+# Copyright 2017 Google Inc.
+#
+# 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.
+
+"""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.utcfromtimestamp(session.token["expires_at"])
+    return credentials
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py
new file mode 100644
index 00000000..b1ed990e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py
@@ -0,0 +1,172 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://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.
+
+"""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)
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py
new file mode 100644
index 00000000..db679a18
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2017 Google Inc.
+#
+# 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.
+
+"""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