Skip to content

Commit

Permalink
Merge pull request #41 from jupyter-naas/100-feat-assets-api
Browse files Browse the repository at this point in the history
100 feat assets api
  • Loading branch information
Dr0p42 committed May 3, 2024
2 parents dd14499 + 51f5436 commit 2d1936d
Show file tree
Hide file tree
Showing 13 changed files with 672 additions and 313 deletions.
1 change: 1 addition & 0 deletions naas_python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .domains.registry.handlers.PythonHandler import primaryAdaptor as registry
from .domains.space.handlers.PythonHandler import primaryAdaptor as space
from .domains.secret.handlers.PythonHandler import primaryAdaptor as secret
from .domains.asset.handlers.PythonHandler import primaryAdaptor as asset
from .domains.storage.handlers.PythonHandler import primaryAdaptor as storage
from .utils.log import initialize_logging

Expand Down
7 changes: 7 additions & 0 deletions naas_python/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
primaryAdaptor as typerSecretAdaptor,
)

from naas_python.domains.asset.handlers.CLIAssetHandler import (
primaryAdaptor as typerAssetAdaptor,
)

from naas_python.domains.storage.handlers.CLIStorageHandler import (
primaryAdaptor as typerStorageAdaptor,
)


def _create_cli_app():
app = typer.Typer(
epilog="Found a bug? Report it at https://github.com/jupyter-naas/naas-python/issues",
Expand All @@ -32,8 +37,10 @@ def _create_cli_app():
app.add_typer(typerSpaceAdaptor.app, name="space")
app.add_typer(typerRegistryAdaptor.app, name="registry")
app.add_typer(typerSecretAdaptor.app, name="secret")
app.add_typer(typerAssetAdaptor.app, name="asset")
app.add_typer(typerStorageAdaptor.app, name="storage")


return app


Expand Down
32 changes: 32 additions & 0 deletions naas_python/domains/asset/AssetDomain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from .models.Asset import Asset

from naas_python.domains.asset.AssetSchema import (
IAssetDomain,
IAssetAdaptor,
Asset,
AssetCreation,
AssetUpdate
)

class AssetDomain(IAssetDomain):
adaptor: IAssetAdaptor

def __init__(self, adaptor: IAssetAdaptor):
self.adaptor = adaptor

def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
asset = self.adaptor.create_asset(workspace_id, asset_creation)
return asset

def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
asset = self.adaptor.get_asset(workspace_id, asset_id)
return asset


def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
response = self.adaptor.update_asset(workspace_id, asset_id, asset_update)
return response

def delete_asset(self, workspace_id:str, asset_id:str) -> None:
self.adaptor.delete_asset(workspace_id, asset_id)
return None
54 changes: 54 additions & 0 deletions naas_python/domains/asset/AssetSchema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from abc import ABCMeta, abstractmethod

from naas_models.pydantic.asset_p2p import *
from .models.Asset import Asset, AssetCreation, AssetUpdate

from naas_python.utils.exceptions import NaasException

# Exception
class AssetNotFound(NaasException): pass
class AssetConflictError(NaasException): pass
class AssetRequestError(NaasException): pass

# Secondary adaptor
class IAssetAdaptor(metaclass=ABCMeta):

@abstractmethod
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
raise NotImplementedError()

@abstractmethod
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
raise NotImplementedError()

@abstractmethod
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
raise NotImplementedError()

@abstractmethod
def delete_asset(self, workspace_id:str, asset_id:str) -> None:
raise NotImplementedError()

# Domain
class IAssetDomain(metaclass=ABCMeta):
adaptor: IAssetAdaptor

@abstractmethod
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
raise NotImplementedError()

@abstractmethod
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
raise NotImplementedError()

@abstractmethod
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
raise NotImplementedError()

@abstractmethod
def delete_asset(self, workspace_id:str, asset_id:str) -> None:
raise NotImplementedError()

# Primary Adaptor
class IAssetPrimaryAdaptor:
pass
33 changes: 33 additions & 0 deletions naas_python/domains/asset/adaptors/primary/SDKAssetAdaptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

from naas_python.domains.asset.AssetSchema import (
IAssetDomain,
IAssetPrimaryAdaptor,
Asset,
AssetCreation,
AssetUpdate
)

class SDKAssetAdaptor(IAssetPrimaryAdaptor):
domain: IAssetDomain

def __init__(self, domain: IAssetDomain):
self.domain = domain

def create_asset(self, workspace_id:str, asset_creation: AssetCreation) -> Asset:
"""Create an asset from the given asset_creation object"""
asset = self.domain.create_asset(workspace_id, asset_creation)
return asset

def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
"""Get an asset from the given workspace_id and asset_id"""
asset = self.domain.get_asset(workspace_id, asset_id)
return asset

def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
asset = self.domain.update_asset(workspace_id, asset_id, asset_update)
return asset

def delete_asset(self, workspace_id:str, asset_id:str) -> dict:
"""Delete an asset from the given asset_id"""
response = self.domain.delete_asset(workspace_id, asset_id)
return response
130 changes: 130 additions & 0 deletions naas_python/domains/asset/adaptors/primary/TyperAssetAdaptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import typer
from typer.core import TyperGroup
from click import Context
from rich.console import Console
from rich import print
from logging import getLogger

from naas_python.domains.asset.AssetSchema import (
IAssetDomain,
IAssetPrimaryAdaptor,
# IAssetInvoker,
Asset,
AssetCreation,
AssetUpdate
)

logger = getLogger(__name__)

class OrderCommands(TyperGroup):
def list_commands(self, ctx: Context):
"""Return list of commands in the order appear."""
return list(self.commands)

class TyperAssetAdaptor(IAssetPrimaryAdaptor):
def __init__(self, domain: IAssetDomain):
super().__init__()

self.domain = domain
self.console = Console()

self.app = typer.Typer(
cls=OrderCommands,
help="Naas Asset CLI",
add_completion=False,
no_args_is_help=True,
pretty_exceptions_enable=False,
rich_markup_mode="rich",
context_settings={"help_option_names": ["-h", "--help"]},
)

self.app.command("create")(self.create_asset)
self.app.command("delete")(self.delete_asset)
self.app.command("get")(self.get_asset)
self.app.command("update")(self.update_asset)

def create_asset(self,
workspace_id:str = typer.Option(None, "--workspace-id", "-w", help="ID of the workspace"),
# asset_creation: AssetCreation = typer.Option(..., "--object", "-o", help="Storage object to create an asset from."),
storage: str = typer.Option(None, "--storage", "-s", help="Storage name to create an asset from. ie:\"data1\""),
object: str = typer.Option(None, "--object", "-o", help="Object to create an asset from. ie:\"/dir1/tmp.txt\""),
version: str = typer.Option(None, "--object-version", "-ov", help="Optional version of the storage object"),
visibility: str = typer.Option(None, "--visibility", "-vis", help="Optional visibility of the asset"),
content_disposition: str = typer.Option(None, "--content-disposition", "-cd", help="Optinal content disposition of the asset"),
password: str = typer.Option(None, "--password", "-p", help="Optional password to decrypt the storage object"),
rich_preview: bool = typer.Option(
False,
"--rich-preview",
"-rp",
help="Rich preview of the information as a table",
))-> Asset:
asset_creation_args:dict = {"asset_creation": {}}
if workspace_id is not None:
asset_creation_args["asset_creation"]["workspace_id"] = workspace_id
if storage is not None and object is not None:
asset_creation_args["asset_creation"]["storage_name"] = storage
asset_creation_args["asset_creation"]["object_name"] = object
if version is not None:
asset_creation_args["asset_creation"]["object_version"] = version
if visibility is not None:
asset_creation_args["asset_creation"]["visibility"] = visibility
if content_disposition is not None:
asset_creation_args["asset_creation"]["content_disposition"] = content_disposition
if password is not None:
asset_creation_args["asset_creation"]["password"] = password

asset_creation : AssetCreation = asset_creation_args
print("creating asset...")
asset = self.domain.create_asset(workspace_id, asset_creation)

def get_asset(self,
workspace_id:str = typer.Option(None, "--workspace-id", "-w", help="ID of the workspace"),
asset_id: str = typer.Option(None, "--asset-id", "-id", help="ID of the asset to get."),
rich_preview: bool = typer.Option(
False,
"--rich-preview",
"-rp",
help="Rich preview of the information as a table",
))-> Asset:
print("getting asset...")
asset = self.domain.get_asset(workspace_id, asset_id)
print(asset)

def update_asset(self,
workspace_id:str = typer.Option(None, "--workspace-id", "-w", help="ID of the workspace"),
asset_id: str = typer.Option(None, "--asset-id", "-id", help="ID of the asset to update."),
visibility: str = typer.Option(None, "--visibility", "-vis", help="Optional visibility of the asset"),
content_disposition: str = typer.Option(None, "--content-disposition", "-cd", help="Optinal content disposition of the asset"),
# password: str = typer.Option(None, "--password", "-p", help="Optional password to decrypt the storage object"),
rich_preview: bool = typer.Option(
False,
"--rich-preview",
"-rp",
help="Rich preview of the information as a table",
))-> Asset:

asset_update_args:dict = {"asset_update": {}}
if visibility is not None:
asset_update_args["asset_update"]["visibility"] = visibility
if content_disposition is not None:
asset_update_args["asset_update"]["content_disposition"] = content_disposition
#TODO feature
# if password is not None:
# asset_update_args["asset_update"]["password"] = password
asset_update : AssetUpdate = asset_update_args
print("updating asset...")
asset = self.domain.update_asset(workspace_id, asset_id, asset_update)
print(asset)

def delete_asset(self,
workspace_id:str = typer.Option(..., "--workspace-id", "-w", help="ID of the workspace"),
asset_id: str = typer.Option(..., "--asset-id", "-id", help="ID of the asset to delete."),
rich_preview: bool = typer.Option(
False,
"--rich-preview",
"-rp",
help="Rich preview of the information as a table",
))-> None:
print("deleting asset...")
self.domain.delete_asset(workspace_id, asset_id)
print("Done.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import requests, json

import pydash as _

from naas_python.utils.domains_base.secondary.BaseAPIAdaptor import BaseAPIAdaptor

from naas_python.domains.asset.AssetSchema import (
IAssetAdaptor,
AssetConflictError,
AssetNotFound,
AssetRequestError,
Asset,
AssetCreation,
AssetUpdate,
)

class NaasAssetAPIAdaptor(BaseAPIAdaptor, IAssetAdaptor):
def __init__(self):
super().__init__()

def _handle_response(self, api_response: requests.Response) -> dict:
if api_response.status_code == 201:
return None

elif api_response.status_code == 200:
return api_response.json()

elif api_response.status_code == 409:
raise AssetConflictError(api_response.json()['message'])

elif api_response.status_code == 404:
raise AssetNotFound(api_response.json()['message'])

elif api_response.status_code == 400:
raise AssetRequestError(api_response.json()['message'])

elif api_response.status_code == 500:
if 'code' in api_response.json() and api_response.json()['code'] == AssetError.UNEXPECTED_ERROR:
raise AssetUnexpectedError(api_response.json()['message'])
else:
raise AssetInternalError(api_response.json()['message'])
else:
raise Exception(f"An unknown error occurred: {api_response.json()}")

@BaseAPIAdaptor.service_status_decorator
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
_url = f"{self.host}/workspace/{workspace_id}/asset/"

api_response = self.make_api_request(
requests.post,
_url,
payload=json.dumps(asset_creation)
)
return self._handle_response(api_response)

@BaseAPIAdaptor.service_status_decorator
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
_url = f"{self.host}/workspace/{workspace_id}/asset/{asset_id}"
api_response = self.make_api_request(
requests.get,
_url
)
return self._handle_response(api_response)

@BaseAPIAdaptor.service_status_decorator
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
_url = f"{self.host}/workspace/{workspace_id}/asset/{asset_id}"
api_response = self.make_api_request(
requests.put,
_url,
payload=json.dumps(asset_update)
)
return self._handle_response(api_response)

@BaseAPIAdaptor.service_status_decorator
def delete_asset(self, workspace_id: str, asset_id: str) -> None:
_url = f"{self.host}/workspace/{workspace_id}/asset/{asset_id}"
api_response = self.make_api_request(
requests.delete,
_url
)
return None
12 changes: 12 additions & 0 deletions naas_python/domains/asset/handlers/CLIAssetHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

from naas_python.domains.asset.adaptors.secondary.NaasAssetAPIAdaptor import NaasAssetAPIAdaptor
from naas_python.domains.asset.AssetDomain import AssetDomain
from naas_python.domains.asset.adaptors.primary.TyperAssetAdaptor import TyperAssetAdaptor

import logging

secondaryAdaptor = NaasAssetAPIAdaptor()

domain = AssetDomain(secondaryAdaptor)

primaryAdaptor = TyperAssetAdaptor(domain)
7 changes: 7 additions & 0 deletions naas_python/domains/asset/handlers/PythonHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from naas_python.domains.asset.adaptors.secondary.NaasAssetAPIAdaptor import NaasAssetAPIAdaptor
from naas_python.domains.asset.AssetDomain import AssetDomain
from naas_python.domains.asset.adaptors.primary.SDKAssetAdaptor import SDKAssetAdaptor

secondaryAdaptor = NaasAssetAPIAdaptor()
domain = AssetDomain(secondaryAdaptor)
primaryAdaptor = SDKAssetAdaptor(domain)

0 comments on commit 2d1936d

Please sign in to comment.