Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for platform API #116

Merged
merged 8 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
sg-s marked this conversation as resolved.
Show resolved Hide resolved
@-mkdir -p ~/.deeporigin
@test -f ~/.deeporigin/deeporigin || ln -s $(CURDIR)/venv/bin/deeporigin ~/.deeporigin/deeporigin
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies = [
"pyjwt",
"cryptography",
"python-box",
"do-sdk-platform==1.0.0",
sg-s marked this conversation as resolved.
Show resolved Hide resolved
]
dynamic = ["version"]

Expand Down
4 changes: 3 additions & 1 deletion src/data_hub/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Empty file added src/platform/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions src/platform/organizations.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions src/platform/tools.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions src/platform/users.py
Original file line number Diff line number Diff line change
@@ -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)
146 changes: 146 additions & 0 deletions src/platform/utils.py
Original file line number Diff line number Diff line change
@@ -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"],
)
sg-s marked this conversation as resolved.
Show resolved Hide resolved

client = do_sdk_platform.ApiClient(configuration=configuration)
else:
client = do_sdk_platform.ApiClient()
sg-s marked this conversation as resolved.
Show resolved Hide resolved

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
]
)
sg-s marked this conversation as resolved.
Show resolved Hide resolved

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
12 changes: 12 additions & 0 deletions src/platform/volumes.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions src/platform/workstations.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion src/utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".")

Expand Down
2 changes: 2 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""helper module to set up tests"""

import io
from contextlib import redirect_stderr, redirect_stdout

Expand Down