Source code for globus_sdk.utils

import collections
import hashlib
import os
import sys
import typing as t
from base64 import b64encode
from enum import Enum

C = t.TypeVar("C", bound=t.Callable[..., t.Any])
T = t.TypeVar("T")
R = t.TypeVar("R")

    # pylint: disable=unsubscriptable-object
    PayloadWrapperBase = collections.UserDict[str, t.Any]
    PayloadWrapperBase = collections.UserDict

def sha256_string(s: str) -> str:
    return hashlib.sha256(s.encode("utf-8")).hexdigest()

def b64str(s: str) -> str:
    return b64encode(s.encode("utf-8")).decode("utf-8")

def slash_join(a: str, b: t.Optional[str]) -> str:
    Join a and b with a single slash, regardless of whether they already
    contain a trailing/leading slash or neither.
    if not b:  # "" or None, don't append a slash
        return a
    if a.endswith("/"):
        if b.startswith("/"):
            return a[:-1] + b
        return a + b
    if b.startswith("/"):
        return a + b
    return a + "/" + b

def doc_api_method(
    external_message: str,
    external_link: str,
    external_base_url: str = "",
    # we could override the format string if wanted (after the normal header)
    external_format_str: str = (
        "See `{message} <{base_url}/{link}>`_ in the API documentation for details."
) -> t.Callable[[C], C]:
    def decorate(func: C) -> C:
        func.__doc__ = f"""{func.__doc__}

        **External Documentation**

        """ + external_format_str.format(
            message=external_message, base_url=external_base_url, link=external_link
        return func

    return decorate

def safe_strseq_iter(value: t.Iterable[t.Any]) -> t.Generator[str, None, None]:
    Given an Iterable (typically of strings), produce an iterator over it of strings.
    This is a passthrough with two caveats:
    - if the value is a solitary string, yield only that value
    - str value in the iterable which is not a string

    This helps handle cases where a string is passed to a function expecting an iterable
    of strings, as well as cases where an iterable of UUID objects is accepted for a
    list of IDs, or something similar.
    if isinstance(value, str):
        yield value
        for x in value:
            yield str(x)

def render_enums_for_api(value: t.Any) -> t.Any:
    Convert enum values to their underlying value.

    If a value is an iterable type, it will be converted to a list and the values will
    also be converted if they are enum values.
    # special-case: handle str and bytes because these types are technically iterable
    # types (of bytes or str values) which could trip someone up
    if isinstance(value, (str, bytes)):
        return value
    if isinstance(value,
        return [render_enums_for_api(x) for x in value]
    return value.value if isinstance(value, Enum) else value

[docs]class PayloadWrapper(PayloadWrapperBase): """ A class for defining helper objects which wrap some kind of "payload" dict. Typical for helper objects which formulate a request payload, e.g. as JSON. Payload types inheriting from this class can be passed directly to the client ``post()``, ``put()``, and ``patch()`` methods instead of a dict. These methods will recognize a ``PayloadWrapper`` and convert it to a dict for serialization with the requested encoder (e.g. as a JSON request body). """ # use UserDict rather than subclassing dict so that our API is always consistent # e.g. `dict.pop` does not invoke `dict.__delitem__`. Overriding `__delitem__` on a # dict subclass can lead to inconsistent behavior between usages like these: # x = d["k"]; del d["k"] # x = d.pop("k") # # UserDict inherits from MutableMapping and only defines the dunder methods, so # changing its behavior safely/consistently is simpler # # internal helpers for setting non-null values # def _set_value( self, key: str, val: t.Any, callback: t.Optional[t.Callable[[t.Any], t.Any]] = None, ) -> None: if val is not None: self[key] = callback(val) if callback else val def _set_optstrs(self, **kwargs: t.Any) -> None: for k, v in kwargs.items(): self._set_value(k, v, callback=str) def _set_optstrlists(self, **kwargs: t.Optional[t.Iterable[t.Any]]) -> None: for k, v in kwargs.items(): self._set_value(k, v, callback=lambda x: list(safe_strseq_iter(x))) def _set_optbools(self, **kwargs: t.Optional[bool]) -> None: for k, v in kwargs.items(): self._set_value(k, v, callback=bool) def _set_optints(self, **kwargs: t.Any) -> None: for k, v in kwargs.items(): self._set_value(k, v, callback=int)
def in_sphinx_build() -> bool: # pragma: no cover # check if `sphinx-build` was used to invoke return os.path.basename(sys.argv[0]) in ["sphinx-build", "sphinx-build.exe"] class _classproperty(t.Generic[T, R]): """ WARNING: for internal use only. Everything in `globus_sdk.utils` is meant to be internal only, but that holds for this class **in particular**. This is a well-typed Generic Descriptor which can be used to wrap `classmethod` decorated functions. Usage should be: @utils.classproperty def foo(...): ... After python3.8 EOL, this should be replaced with @classmethod @property def foo(...): ... However, this will also require proper mypy support. See also: For more guidance on how this works, see the python3 descriptor guide: """ def __init__(self, func: t.Callable[[t.Type[T]], R]) -> None: self.func = func def __get__(self, obj: t.Any, cls: t.Type[T]) -> R: return self.func(cls) # if running under sphinx, define this as the stacked classmethod(property(...)) # decoration, so that proper autodoc generation happens if in_sphinx_build(): # pragma: no cover def classproperty(func: t.Callable[[T], R]) -> _classproperty[T, R]: # type ignore this because # - it doesn't match the return type # - mypy doesn't understand classmethod(property(...)) on older pythons return classmethod(property(func)) # type: ignore else: def classproperty(func: t.Callable[[T], R]) -> _classproperty[T, R]: # type cast to convert instance method to class method return _classproperty(t.cast(t.Callable[[t.Type[T]], R], func))