diff --git a/Makefile b/Makefile index 98fc987..1d6e99c 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ install: @python3 -m venv venv @source $(CURDIR)/venv/bin/activate && \ pip install --upgrade pip && \ - pip install -e .[lint,test,dev,docs,plots] && \ + pip install --no-cache-dir -e .[lint,test,dev,docs,plots] && \ deactivate @-mkdir -p ~/.deeporigin @test -f ~/.deeporigin/deeporigin || ln -s $(CURDIR)/venv/bin/deeporigin ~/.deeporigin/deeporigin diff --git a/pyproject.toml b/pyproject.toml index 40b772e..56e54b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "pyjwt", "cryptography", "python-box", + "do-sdk-platform==1.0.0", ] dynamic = ["version"] diff --git a/src/data_hub/_api.py b/src/data_hub/_api.py index 3545e61..b34cdf1 100644 --- a/src/data_hub/_api.py +++ b/src/data_hub/_api.py @@ -9,6 +9,7 @@ import sys from pathlib import Path +from beartype import beartype from box import Box from deeporigin import auth from deeporigin.exceptions import DeepOriginException @@ -47,7 +48,8 @@ } -def _get_client_methods(): +@beartype +def _get_client_methods() -> set: # the only reason we're creating this client is to # extract methods from it. So no need to # authenticate diff --git a/src/platform/__init__.py b/src/platform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/organizations.py b/src/platform/organizations.py new file mode 100644 index 0000000..ba0ad2d --- /dev/null +++ b/src/platform/organizations.py @@ -0,0 +1,12 @@ +"""bridge module to interact with the platform organizations api""" + +import sys + +from deeporigin.platform.utils import add_functions_to_module + +methods = add_functions_to_module( + module=sys.modules[__name__], + api_name="OrganizationsApi", +) + +__all__ = list(methods) diff --git a/src/platform/tools.py b/src/platform/tools.py new file mode 100644 index 0000000..d0508e0 --- /dev/null +++ b/src/platform/tools.py @@ -0,0 +1,12 @@ +"""bridge module to interact with the platform tools api""" + +import sys + +from deeporigin.platform.utils import add_functions_to_module + +methods = add_functions_to_module( + module=sys.modules[__name__], + api_name="ToolsApi", +) + +__all__ = list(methods) diff --git a/src/platform/users.py b/src/platform/users.py new file mode 100644 index 0000000..9459693 --- /dev/null +++ b/src/platform/users.py @@ -0,0 +1,12 @@ +"""bridge module to interact with the platform users api""" + +import sys + +from deeporigin.platform.utils import add_functions_to_module + +methods = add_functions_to_module( + module=sys.modules[__name__], + api_name="UsersApi", +) + +__all__ = list(methods) diff --git a/src/platform/utils.py b/src/platform/utils.py new file mode 100644 index 0000000..243598f --- /dev/null +++ b/src/platform/utils.py @@ -0,0 +1,146 @@ +"""module to automatically wrap methods in autogenerated low-level code and re-expose them as high-level functions""" + +import inspect +from urllib.parse import urljoin + +import do_sdk_platform +from beartype import beartype +from box import Box +from deeporigin.auth import get_tokens +from deeporigin.config import get_value +from deeporigin.utils.core import _get_method + + +@beartype +def add_functions_to_module( + module: str, + api_name: str, +) -> set: + """utility function to dynamically add functions to a module + + This function works by calling setattr on the module. + + Args: + module (str): name of the module + api_name (str): name of the API + + Returns: + set of methods that were added + """ + methods = _get_client_methods( + _get_api_client( + api_name=api_name, + ) + ) + + for method in methods: + # clean up the name so that it's more readable + sanitized_method_name = method.split("controller")[0].rstrip("_") + + # add this function as an attribute to this module + # so that we can call it + setattr( + module, + sanitized_method_name, + _create_function( + method_path=method, + api_name=api_name, + ), + ) + + return methods + + +@beartype +def _get_api_client(*, api_name: str, configure: bool = True): + """return a configured client for the API we want to access + + Args: + api_name (str): name of the API + + Returns: + configured client + """ + + if configure: + configuration = do_sdk_platform.configuration.Configuration( + host=urljoin(get_value()["api_endpoint"], "/api"), + access_token=get_tokens()["access"], + ) + + client = do_sdk_platform.ApiClient(configuration=configuration) + else: + client = do_sdk_platform.ApiClient() + + api_class = getattr(do_sdk_platform, api_name) + client = api_class(api_client=client) + return client + + +@beartype +def _get_client_methods(client) -> set: + """utility function to get methods from the client that return raw responses from the server""" + methods = set( + [ + attr + for attr in dir(client) + if callable(getattr(client, attr)) + and not attr.startswith("_") + and "without_preload_content" in attr + ] + ) + + return methods + + +def _create_function(*, method_path: str, api_name: str): + """utility function the dynamically creates functions + that wrap low-level functions in the DeepOrigin data API""" + + # we're constructing a client solely for the purposes + # of inspecting its methods and extracting + # function signatures. So we don't need any + # authentication + + client = _get_api_client( + configure=False, + api_name=api_name, + ) + + method = _get_method(client, method_path) + + signature = inspect.signature(method) + + def dynamic_function( + *, + client=None, + **kwargs, + ): + """dynamic function that wraps low-level functions in the DeepOrigin platform API""" + + if client is None: + client = _get_api_client(api_name=api_name) + method = _get_method(client, method_path) + + # call the low level API method + response = method(**kwargs) + + if not isinstance(response, dict): + response = response.json() + + if "data" in response.keys(): + response = response["data"] + if isinstance(response, list): + response = [Box(item) for item in response] + else: + response = Box(response) + else: + response = Box(response) + + return response + + # attach the signature of the underlying method to the + # function so that IDEs can display it properly + dynamic_function.__signature__ = signature + + return dynamic_function diff --git a/src/platform/volumes.py b/src/platform/volumes.py new file mode 100644 index 0000000..a4a0bba --- /dev/null +++ b/src/platform/volumes.py @@ -0,0 +1,12 @@ +"""bridge module to interact with the platform tools api""" + +import sys + +from deeporigin.platform.utils import add_functions_to_module + +methods = add_functions_to_module( + module=sys.modules[__name__], + api_name="VolumesApi", +) + +__all__ = list(methods) diff --git a/src/platform/workstations.py b/src/platform/workstations.py new file mode 100644 index 0000000..b87de61 --- /dev/null +++ b/src/platform/workstations.py @@ -0,0 +1,12 @@ +"""bridge module to interact with the platform tools api""" + +import sys + +from deeporigin.platform.utils import add_functions_to_module + +methods = add_functions_to_module( + module=sys.modules[__name__], + api_name="ComputebenchesApi", +) + +__all__ = list(methods) diff --git a/src/utils/core.py b/src/utils/core.py index ca4ff40..da640c8 100644 --- a/src/utils/core.py +++ b/src/utils/core.py @@ -129,7 +129,7 @@ def clear(self): self._save(self._data) -def _get_method(obj, method_path): +def _get_method(obj, method_path: str): # Split the method path into components methods = method_path.split(".") diff --git a/tests/utils.py b/tests/utils.py index ca46e7d..17d22d1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,5 @@ +"""helper module to set up tests""" + import io from contextlib import redirect_stderr, redirect_stdout