Skip to content

Commit

Permalink
Promote gare from experimental (#1048)
Browse files Browse the repository at this point in the history
* Promote `gare` from `experimental`

The `auth_requirements_error` module is moved to `globus_sdk.gare` and
docs and importts are updated.

Included in this change, `GlobusAuthRequirementsError` has been
renamed to `GARE`, as the `Error` suffix to a name typically indicates
that a class is an exception class.

Aliasing has been put in place from `experimental` to the new code
location.

As a minor, related fix `_serializable.Serializable` now uses a
`Self` type annotation, rather than a type var. This improves the doc
rendering of the `from_dict` and `to_dict` methods, as they show the
self-type rather than an unbound type variable T.

* Require typing_extensions on Python<3.11

In order to use `typing.Self`, we must install `typing_extensions` on
Python 3.10.

* Rename the various GARE functions

And include testing of the rename aliases.

* Apply suggestions from code review

Co-authored-by: Ada <107940310+ada-globus@users.noreply.github.com>

* y mypy y

---------

Co-authored-by: Ada <107940310+ada-globus@users.noreply.github.com>
  • Loading branch information
sirosen and ada-globus authored Sep 17, 2024
1 parent 1d5cb80 commit 99d3bcd
Show file tree
Hide file tree
Showing 22 changed files with 233 additions and 166 deletions.
11 changes: 11 additions & 0 deletions changelog.d/20240916_102235_sirosen_move_gare.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Changed
~~~~~~~

- Globus Auth Requirements errors are no longer ``experimental``. They have
been moved to the ``globus_sdk.gare`` module and the primary document type
has been renamed from ``GlobusAuthRequirementsError`` to ``GARE``. (:pr:`NUMBER`)

- The functions provided by this interface have also been renamed to use
``gare`` in their naming: ``to_gare``, ``is_gare``, ``has_gares``, and
``to_gares`` are now all available. The older names are available as
aliases from the ``experimental`` namespace.
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
Auth Requirements Errors
========================
Globus Auth Requirements Errors (GAREs)
=======================================

Globus Auth Requirements Error is a response format that conveys to a client any
'Globus Auth Requirements Error' is a response format that conveys to a client any
modifications to a session (i.e., "boosting") that will be required
to complete a particular request.

The ``globus_sdk.experimental.auth_requirements_error`` module provides a
number of tools to make it easier to identify and handle these errors when they occur.
The ``globus_sdk.gare`` module provides a number of tools to make it easier to
identify and handle these errors when they occur.

GlobusAuthRequirementsError
---------------------------
GARE
----

The ``GlobusAuthRequirementsError`` class provides a model for working with Globus
Auth Requirements Error responses.
The ``GARE`` class provides a model for working with Globus Auth Requirements Error
responses.

Services in the Globus ecosystem may need to communicate authorization requirements
to their consumers. For example, a service may need to instruct clients to have the user
consent to an additional scope, ``"foo"``. In such a case, ``GlobusAuthRequirementsError``
can provide serialization into the well-known Globus Auth Requirements Error format:
consent to an additional scope, ``"foo"``. In such a case, ``GARE`` can provide
serialization into the well-known Globus Auth Requirements Error format:

.. code-block:: python
from globus_sdk.experimental.auth_requirements_error import GlobusAuthRequirementsError
from globus_sdk.gare import GARE
error = GlobusAuthRequirementsError(
error_doc = GARE(
code="ConsentRequired",
authorization_parameters=GlobusAuthorizationParameters(
required_scopes=["foo"],
Expand All @@ -42,9 +42,9 @@ by specifying ``include_extra=True`` when calling ``to_dict()``.

.. code-block:: python
from globus_sdk.experimental.auth_requirements_error import GlobusAuthRequirementsError
from globus_sdk.gare import GARE
error = GlobusAuthRequirementsError(
error = GARE(
code="ConsentRequired",
authorization_parameters=GlobusAuthorizationParameters(
required_scopes=["foo"],
Expand All @@ -61,8 +61,8 @@ by specifying ``include_extra=True`` when calling ``to_dict()``.
# Render a dictionary with extra fields
error.to_dict(include_extra=True)
These fields are stored by both the ``GlobusAuthRequirementsError`` and
``GlobusAuthenticationParameters`` classes in an ``extra`` attribute.
These fields are stored by both the ``GARE`` and ``GlobusAuthenticationParameters``
classes in an ``extra`` attribute.

.. note::

Expand All @@ -75,37 +75,34 @@ These fields are stored by both the ``GlobusAuthRequirementsError`` and
Parsing Responses
-----------------

If you are writing a client to a Globus API, the ``auth_requirements_error`` subpackage
provides utilities to detect legacy Globus Auth requirements error response
formats and normalize them.
If you are writing a client to a Globus API, the ``gare`` subpackage provides utilities
to detect legacy Globus Auth requirements error response formats and normalize them.

To detect if a ``GlobusAPIError``, ``ErrorSubdocument``, or JSON response
dictionary represents an error that can be converted to a Globus Auth
Requirements Error, you can use, e.g.,:

.. code-block:: python
from globus_sdk.experimental import auth_requirements_error
from globus_sdk import gare
error_dict = {
"code": "ConsentRequired",
"message": "Missing required foo consent",
}
# The dict is not a Globus Auth Requirements Error, so `False` is returned.
auth_requirements_error.utils.is_auth_requirements_error(error_dict)
gare.is_auth_requirements_error(error_dict)
# The dict is not a Globus Auth Requirements Error and cannot be converted.
auth_requirements_error.utils.to_auth_requirements_error(error_dict) # None
gare.to_auth_requirements_error(error_dict) # None
error_dict = {
"code": "ConsentRequired",
"message": "Missing required foo consent",
"required_scopes": ["urn:globus:auth:scope:transfer.api.globus.org:all[*foo]"],
}
auth_requirements_error.utils.is_auth_requirements_error(error_dict) # True
auth_requirements_error.utils.to_auth_requirements_error(
error_dict
) # GlobusAuthRequirementsError
gare.is_auth_requirements_error(error_dict) # True
gare.to_auth_requirements_error(error_dict) # GARE
.. note::

Expand All @@ -118,22 +115,38 @@ Requirements Error, you can use, e.g.,:

.. code-block:: python
auth_requirements_error.utils.to_auth_requirements_error(
other_error
) # GlobusAuthRequirementsError
auth_requirements_error.utils.to_auth_requirements_errors(
[other_error]
) # [GlobusAuthRequirementsError, ...]
gare.to_auth_requirements_error(other_error) # GARE
gare.to_auth_requirements_errors([other_error]) # [GARE, ...]
Notes
-----

``GlobusAuthRequirementsError`` enforces types strictly when parsing a Globus
Auth Requirements Error response dictionary, and will raise a ``ValueError`` if a
``GARE`` enforces types strictly when parsing a Globus Auth Requirements Error
response dictionary, and will raise a :class:`globus_sdk.ValidationError` if a
supported field is supplied with a value of the wrong type.

``GlobusAuthRequirementsError`` does not attempt to mimic or itself enforce
any logic specific to the Globus Auth service with regard to what represents a valid
combination of fields (e.g., ``session_required_mfa`` requires either
``session_required_identities`` or ``session_required_single_domain``
in order to be properly handled).
``GARE`` does not attempt to mimic or itself enforce any logic specific to the
Globus Auth service with regard to what represents a valid combination of fields
(e.g., ``session_required_mfa`` requires either ``session_required_identities`` or
``session_required_single_domain`` in order to be properly handled).

Reference
---------

.. currentmodule:: globus_sdk.gare

.. autoclass:: GARE
:members:
:inherited-members:

.. autoclass:: GlobusAuthorizationParameters
:members:
:inherited-members:

.. autofunction:: to_gare

.. autofunction:: to_gares

.. autofunction:: is_gare

.. autofunction:: has_gares
1 change: 1 addition & 0 deletions docs/authorization/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Components of the Globus SDK which handle application authorization.

globus_authorizers
scopes_and_consents/index
gare
1 change: 0 additions & 1 deletion docs/experimental/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ Globus SDK Experimental Components
:caption: Contents
:maxdepth: 1

auth_requirements_errors
scope_parser
globus_app

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ dependencies = [
"cryptography>=3.3.1,!=3.4.0",
# depend on the latest version of typing-extensions on python versions which do
# not have all of the typing features we use
'typing_extensions>=4.0; python_version<"3.10"',
'typing_extensions>=4.0; python_version<"3.11"',
# python versions older than 3.9 don't have importlib.resources
'importlib_resources>=5.12.0; python_version<"3.9"',
]
Expand Down
8 changes: 6 additions & 2 deletions src/globus_sdk/_serializable.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from __future__ import annotations

import inspect
import sys
import typing as t

T = t.TypeVar("T", bound="Serializable")
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


class Serializable:
Expand All @@ -30,7 +34,7 @@ def _supported_fields(cls) -> list[str]:
]

@classmethod
def from_dict(cls: type[T], data: dict[str, t.Any]) -> T:
def from_dict(cls, data: dict[str, t.Any]) -> Self:
"""
Instantiate from a dictionary.
Expand Down
51 changes: 51 additions & 0 deletions src/globus_sdk/experimental/auth_requirements_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

import sys
import typing as t

__all__ = (
"GlobusAuthRequirementsError",
"GlobusAuthorizationParameters",
"to_auth_requirements_error",
"to_auth_requirements_errors",
"is_auth_requirements_error",
"has_auth_requirements_errors",
)

# legacy aliases
# (when accessed, these will emit deprecation warnings)
if t.TYPE_CHECKING:
from globus_sdk.gare import GARE as GlobusAuthRequirementsError
from globus_sdk.gare import GlobusAuthorizationParameters
from globus_sdk.gare import has_gares as has_auth_requirements_errors
from globus_sdk.gare import is_gare as is_auth_requirements_error
from globus_sdk.gare import to_gare as to_auth_requirements_error
from globus_sdk.gare import to_gares as to_auth_requirements_errors
else:

_RENAMES = {
"GlobusAuthRequirementsError": "GARE",
"to_auth_requirements_error": "to_gare",
"to_auth_requirements_errors": "to_gares",
"is_auth_requirements_error": "is_gare",
"has_auth_requirements_errors": "has_gares",
}

def __getattr__(name: str) -> t.Any:
import globus_sdk.gare as gare_module
from globus_sdk.exc import warn_deprecated

new_name = _RENAMES.get(name, name)

warn_deprecated(
"'globus_sdk.experimental.auth_requirements_error' has been renamed to "
"'globus_sdk.gare'. "
f"Importing '{name}' from `globus_sdk.experimental` is deprecated. "
f"Use `globus_sdk.gare.{new_name}` instead."
)

value = getattr(gare_module, new_name, None)
if value is None:
raise AttributeError(f"module {__name__} has no attribute {name}")
setattr(sys.modules[__name__], name, value)
return value
19 changes: 0 additions & 19 deletions src/globus_sdk/experimental/auth_requirements_error/__init__.py

This file was deleted.

4 changes: 1 addition & 3 deletions src/globus_sdk/experimental/globus_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
from globus_sdk import AuthClient, AuthLoginClient, GlobusSDKUsageError, Scope
from globus_sdk._types import ScopeCollectionType, UUIDLike
from globus_sdk.authorizers import GlobusAuthorizer
from globus_sdk.experimental.auth_requirements_error import (
GlobusAuthorizationParameters,
)
from globus_sdk.experimental.tokenstorage import TokenStorage
from globus_sdk.gare import GlobusAuthorizationParameters
from globus_sdk.scopes import AuthScopes, scopes_to_scope_list

from ._types import TokenStorageProvider
Expand Down
4 changes: 1 addition & 3 deletions src/globus_sdk/experimental/globus_app/client_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

from globus_sdk import AuthLoginClient, ConfidentialAppAuthClient, GlobusSDKUsageError
from globus_sdk._types import ScopeCollectionType, UUIDLike
from globus_sdk.experimental.auth_requirements_error import (
GlobusAuthorizationParameters,
)
from globus_sdk.gare import GlobusAuthorizationParameters

from .app import GlobusApp
from .authorizer_factory import ClientCredentialsAuthorizerFactory
Expand Down
4 changes: 1 addition & 3 deletions src/globus_sdk/experimental/globus_app/user_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
NativeAppAuthClient,
)
from globus_sdk._types import ScopeCollectionType, UUIDLike
from globus_sdk.experimental.auth_requirements_error import (
GlobusAuthorizationParameters,
)
from globus_sdk.experimental.login_flow_manager import (
CommandLineLoginFlowManager,
LoginFlowManager,
)
from globus_sdk.gare import GlobusAuthorizationParameters

from ._types import LoginFlowManagerProvider
from .app import GlobusApp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
GlobusSDKUsageError,
OAuthTokenResponse,
)
from globus_sdk.experimental.auth_requirements_error import (
GlobusAuthorizationParameters,
)
from globus_sdk.gare import GlobusAuthorizationParameters

from .login_flow_manager import LoginFlowManager

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
from string import Template

from globus_sdk import AuthLoginClient, GlobusSDKUsageError, OAuthTokenResponse
from globus_sdk.experimental.auth_requirements_error import (
GlobusAuthorizationParameters,
)
from globus_sdk.experimental.login_flow_manager.login_flow_manager import (
LoginFlowManager,
)
from globus_sdk.gare import GlobusAuthorizationParameters

from ._local_server import (
DEFAULT_HTML_TEMPLATE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
NativeAppAuthClient,
OAuthTokenResponse,
)
from globus_sdk.experimental.auth_requirements_error import (
GlobusAuthorizationParameters,
)
from globus_sdk.gare import GlobusAuthorizationParameters


class LoginFlowManager(metaclass=abc.ABCMeta):
Expand Down
11 changes: 11 additions & 0 deletions src/globus_sdk/gare/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from ._auth_requirements_error import GARE, GlobusAuthorizationParameters
from ._functional_api import has_gares, is_gare, to_gare, to_gares

__all__ = (
"GARE",
"GlobusAuthorizationParameters",
"to_gare",
"to_gares",
"is_gare",
"has_gares",
)
Loading

0 comments on commit 99d3bcd

Please sign in to comment.