Skip to content

Commit 2d1936d

Browse files
authored
Merge pull request #41 from jupyter-naas/100-feat-assets-api
100 feat assets api
2 parents dd14499 + 51f5436 commit 2d1936d

File tree

13 files changed

+672
-313
lines changed

13 files changed

+672
-313
lines changed

naas_python/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .domains.registry.handlers.PythonHandler import primaryAdaptor as registry
22
from .domains.space.handlers.PythonHandler import primaryAdaptor as space
33
from .domains.secret.handlers.PythonHandler import primaryAdaptor as secret
4+
from .domains.asset.handlers.PythonHandler import primaryAdaptor as asset
45
from .domains.storage.handlers.PythonHandler import primaryAdaptor as storage
56
from .utils.log import initialize_logging
67

naas_python/cli.py

+7
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616
primaryAdaptor as typerSecretAdaptor,
1717
)
1818

19+
from naas_python.domains.asset.handlers.CLIAssetHandler import (
20+
primaryAdaptor as typerAssetAdaptor,
21+
)
22+
1923
from naas_python.domains.storage.handlers.CLIStorageHandler import (
2024
primaryAdaptor as typerStorageAdaptor,
2125
)
2226

27+
2328
def _create_cli_app():
2429
app = typer.Typer(
2530
epilog="Found a bug? Report it at https://github.com/jupyter-naas/naas-python/issues",
@@ -32,8 +37,10 @@ def _create_cli_app():
3237
app.add_typer(typerSpaceAdaptor.app, name="space")
3338
app.add_typer(typerRegistryAdaptor.app, name="registry")
3439
app.add_typer(typerSecretAdaptor.app, name="secret")
40+
app.add_typer(typerAssetAdaptor.app, name="asset")
3541
app.add_typer(typerStorageAdaptor.app, name="storage")
3642

43+
3744
return app
3845

3946

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from .models.Asset import Asset
2+
3+
from naas_python.domains.asset.AssetSchema import (
4+
IAssetDomain,
5+
IAssetAdaptor,
6+
Asset,
7+
AssetCreation,
8+
AssetUpdate
9+
)
10+
11+
class AssetDomain(IAssetDomain):
12+
adaptor: IAssetAdaptor
13+
14+
def __init__(self, adaptor: IAssetAdaptor):
15+
self.adaptor = adaptor
16+
17+
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
18+
asset = self.adaptor.create_asset(workspace_id, asset_creation)
19+
return asset
20+
21+
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
22+
asset = self.adaptor.get_asset(workspace_id, asset_id)
23+
return asset
24+
25+
26+
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
27+
response = self.adaptor.update_asset(workspace_id, asset_id, asset_update)
28+
return response
29+
30+
def delete_asset(self, workspace_id:str, asset_id:str) -> None:
31+
self.adaptor.delete_asset(workspace_id, asset_id)
32+
return None
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from abc import ABCMeta, abstractmethod
2+
3+
from naas_models.pydantic.asset_p2p import *
4+
from .models.Asset import Asset, AssetCreation, AssetUpdate
5+
6+
from naas_python.utils.exceptions import NaasException
7+
8+
# Exception
9+
class AssetNotFound(NaasException): pass
10+
class AssetConflictError(NaasException): pass
11+
class AssetRequestError(NaasException): pass
12+
13+
# Secondary adaptor
14+
class IAssetAdaptor(metaclass=ABCMeta):
15+
16+
@abstractmethod
17+
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
18+
raise NotImplementedError()
19+
20+
@abstractmethod
21+
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
22+
raise NotImplementedError()
23+
24+
@abstractmethod
25+
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
26+
raise NotImplementedError()
27+
28+
@abstractmethod
29+
def delete_asset(self, workspace_id:str, asset_id:str) -> None:
30+
raise NotImplementedError()
31+
32+
# Domain
33+
class IAssetDomain(metaclass=ABCMeta):
34+
adaptor: IAssetAdaptor
35+
36+
@abstractmethod
37+
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
38+
raise NotImplementedError()
39+
40+
@abstractmethod
41+
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
42+
raise NotImplementedError()
43+
44+
@abstractmethod
45+
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
46+
raise NotImplementedError()
47+
48+
@abstractmethod
49+
def delete_asset(self, workspace_id:str, asset_id:str) -> None:
50+
raise NotImplementedError()
51+
52+
# Primary Adaptor
53+
class IAssetPrimaryAdaptor:
54+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
from naas_python.domains.asset.AssetSchema import (
3+
IAssetDomain,
4+
IAssetPrimaryAdaptor,
5+
Asset,
6+
AssetCreation,
7+
AssetUpdate
8+
)
9+
10+
class SDKAssetAdaptor(IAssetPrimaryAdaptor):
11+
domain: IAssetDomain
12+
13+
def __init__(self, domain: IAssetDomain):
14+
self.domain = domain
15+
16+
def create_asset(self, workspace_id:str, asset_creation: AssetCreation) -> Asset:
17+
"""Create an asset from the given asset_creation object"""
18+
asset = self.domain.create_asset(workspace_id, asset_creation)
19+
return asset
20+
21+
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
22+
"""Get an asset from the given workspace_id and asset_id"""
23+
asset = self.domain.get_asset(workspace_id, asset_id)
24+
return asset
25+
26+
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
27+
asset = self.domain.update_asset(workspace_id, asset_id, asset_update)
28+
return asset
29+
30+
def delete_asset(self, workspace_id:str, asset_id:str) -> dict:
31+
"""Delete an asset from the given asset_id"""
32+
response = self.domain.delete_asset(workspace_id, asset_id)
33+
return response
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import typer
2+
from typer.core import TyperGroup
3+
from click import Context
4+
from rich.console import Console
5+
from rich import print
6+
from logging import getLogger
7+
8+
from naas_python.domains.asset.AssetSchema import (
9+
IAssetDomain,
10+
IAssetPrimaryAdaptor,
11+
# IAssetInvoker,
12+
Asset,
13+
AssetCreation,
14+
AssetUpdate
15+
)
16+
17+
logger = getLogger(__name__)
18+
19+
class OrderCommands(TyperGroup):
20+
def list_commands(self, ctx: Context):
21+
"""Return list of commands in the order appear."""
22+
return list(self.commands)
23+
24+
class TyperAssetAdaptor(IAssetPrimaryAdaptor):
25+
def __init__(self, domain: IAssetDomain):
26+
super().__init__()
27+
28+
self.domain = domain
29+
self.console = Console()
30+
31+
self.app = typer.Typer(
32+
cls=OrderCommands,
33+
help="Naas Asset CLI",
34+
add_completion=False,
35+
no_args_is_help=True,
36+
pretty_exceptions_enable=False,
37+
rich_markup_mode="rich",
38+
context_settings={"help_option_names": ["-h", "--help"]},
39+
)
40+
41+
self.app.command("create")(self.create_asset)
42+
self.app.command("delete")(self.delete_asset)
43+
self.app.command("get")(self.get_asset)
44+
self.app.command("update")(self.update_asset)
45+
46+
def create_asset(self,
47+
workspace_id:str = typer.Option(None, "--workspace-id", "-w", help="ID of the workspace"),
48+
# asset_creation: AssetCreation = typer.Option(..., "--object", "-o", help="Storage object to create an asset from."),
49+
storage: str = typer.Option(None, "--storage", "-s", help="Storage name to create an asset from. ie:\"data1\""),
50+
object: str = typer.Option(None, "--object", "-o", help="Object to create an asset from. ie:\"/dir1/tmp.txt\""),
51+
version: str = typer.Option(None, "--object-version", "-ov", help="Optional version of the storage object"),
52+
visibility: str = typer.Option(None, "--visibility", "-vis", help="Optional visibility of the asset"),
53+
content_disposition: str = typer.Option(None, "--content-disposition", "-cd", help="Optinal content disposition of the asset"),
54+
password: str = typer.Option(None, "--password", "-p", help="Optional password to decrypt the storage object"),
55+
rich_preview: bool = typer.Option(
56+
False,
57+
"--rich-preview",
58+
"-rp",
59+
help="Rich preview of the information as a table",
60+
))-> Asset:
61+
asset_creation_args:dict = {"asset_creation": {}}
62+
if workspace_id is not None:
63+
asset_creation_args["asset_creation"]["workspace_id"] = workspace_id
64+
if storage is not None and object is not None:
65+
asset_creation_args["asset_creation"]["storage_name"] = storage
66+
asset_creation_args["asset_creation"]["object_name"] = object
67+
if version is not None:
68+
asset_creation_args["asset_creation"]["object_version"] = version
69+
if visibility is not None:
70+
asset_creation_args["asset_creation"]["visibility"] = visibility
71+
if content_disposition is not None:
72+
asset_creation_args["asset_creation"]["content_disposition"] = content_disposition
73+
if password is not None:
74+
asset_creation_args["asset_creation"]["password"] = password
75+
76+
asset_creation : AssetCreation = asset_creation_args
77+
print("creating asset...")
78+
asset = self.domain.create_asset(workspace_id, asset_creation)
79+
80+
def get_asset(self,
81+
workspace_id:str = typer.Option(None, "--workspace-id", "-w", help="ID of the workspace"),
82+
asset_id: str = typer.Option(None, "--asset-id", "-id", help="ID of the asset to get."),
83+
rich_preview: bool = typer.Option(
84+
False,
85+
"--rich-preview",
86+
"-rp",
87+
help="Rich preview of the information as a table",
88+
))-> Asset:
89+
print("getting asset...")
90+
asset = self.domain.get_asset(workspace_id, asset_id)
91+
print(asset)
92+
93+
def update_asset(self,
94+
workspace_id:str = typer.Option(None, "--workspace-id", "-w", help="ID of the workspace"),
95+
asset_id: str = typer.Option(None, "--asset-id", "-id", help="ID of the asset to update."),
96+
visibility: str = typer.Option(None, "--visibility", "-vis", help="Optional visibility of the asset"),
97+
content_disposition: str = typer.Option(None, "--content-disposition", "-cd", help="Optinal content disposition of the asset"),
98+
# password: str = typer.Option(None, "--password", "-p", help="Optional password to decrypt the storage object"),
99+
rich_preview: bool = typer.Option(
100+
False,
101+
"--rich-preview",
102+
"-rp",
103+
help="Rich preview of the information as a table",
104+
))-> Asset:
105+
106+
asset_update_args:dict = {"asset_update": {}}
107+
if visibility is not None:
108+
asset_update_args["asset_update"]["visibility"] = visibility
109+
if content_disposition is not None:
110+
asset_update_args["asset_update"]["content_disposition"] = content_disposition
111+
#TODO feature
112+
# if password is not None:
113+
# asset_update_args["asset_update"]["password"] = password
114+
asset_update : AssetUpdate = asset_update_args
115+
print("updating asset...")
116+
asset = self.domain.update_asset(workspace_id, asset_id, asset_update)
117+
print(asset)
118+
119+
def delete_asset(self,
120+
workspace_id:str = typer.Option(..., "--workspace-id", "-w", help="ID of the workspace"),
121+
asset_id: str = typer.Option(..., "--asset-id", "-id", help="ID of the asset to delete."),
122+
rich_preview: bool = typer.Option(
123+
False,
124+
"--rich-preview",
125+
"-rp",
126+
help="Rich preview of the information as a table",
127+
))-> None:
128+
print("deleting asset...")
129+
self.domain.delete_asset(workspace_id, asset_id)
130+
print("Done.")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import requests, json
2+
3+
import pydash as _
4+
5+
from naas_python.utils.domains_base.secondary.BaseAPIAdaptor import BaseAPIAdaptor
6+
7+
from naas_python.domains.asset.AssetSchema import (
8+
IAssetAdaptor,
9+
AssetConflictError,
10+
AssetNotFound,
11+
AssetRequestError,
12+
Asset,
13+
AssetCreation,
14+
AssetUpdate,
15+
)
16+
17+
class NaasAssetAPIAdaptor(BaseAPIAdaptor, IAssetAdaptor):
18+
def __init__(self):
19+
super().__init__()
20+
21+
def _handle_response(self, api_response: requests.Response) -> dict:
22+
if api_response.status_code == 201:
23+
return None
24+
25+
elif api_response.status_code == 200:
26+
return api_response.json()
27+
28+
elif api_response.status_code == 409:
29+
raise AssetConflictError(api_response.json()['message'])
30+
31+
elif api_response.status_code == 404:
32+
raise AssetNotFound(api_response.json()['message'])
33+
34+
elif api_response.status_code == 400:
35+
raise AssetRequestError(api_response.json()['message'])
36+
37+
elif api_response.status_code == 500:
38+
if 'code' in api_response.json() and api_response.json()['code'] == AssetError.UNEXPECTED_ERROR:
39+
raise AssetUnexpectedError(api_response.json()['message'])
40+
else:
41+
raise AssetInternalError(api_response.json()['message'])
42+
else:
43+
raise Exception(f"An unknown error occurred: {api_response.json()}")
44+
45+
@BaseAPIAdaptor.service_status_decorator
46+
def create_asset(self, workspace_id:str, asset_creation:AssetCreation) -> Asset:
47+
_url = f"{self.host}/workspace/{workspace_id}/asset/"
48+
49+
api_response = self.make_api_request(
50+
requests.post,
51+
_url,
52+
payload=json.dumps(asset_creation)
53+
)
54+
return self._handle_response(api_response)
55+
56+
@BaseAPIAdaptor.service_status_decorator
57+
def get_asset(self, workspace_id:str, asset_id:str) -> Asset:
58+
_url = f"{self.host}/workspace/{workspace_id}/asset/{asset_id}"
59+
api_response = self.make_api_request(
60+
requests.get,
61+
_url
62+
)
63+
return self._handle_response(api_response)
64+
65+
@BaseAPIAdaptor.service_status_decorator
66+
def update_asset(self, workspace_id:str, asset_id:str, asset_update: AssetUpdate) -> Asset:
67+
_url = f"{self.host}/workspace/{workspace_id}/asset/{asset_id}"
68+
api_response = self.make_api_request(
69+
requests.put,
70+
_url,
71+
payload=json.dumps(asset_update)
72+
)
73+
return self._handle_response(api_response)
74+
75+
@BaseAPIAdaptor.service_status_decorator
76+
def delete_asset(self, workspace_id: str, asset_id: str) -> None:
77+
_url = f"{self.host}/workspace/{workspace_id}/asset/{asset_id}"
78+
api_response = self.make_api_request(
79+
requests.delete,
80+
_url
81+
)
82+
return None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
from naas_python.domains.asset.adaptors.secondary.NaasAssetAPIAdaptor import NaasAssetAPIAdaptor
3+
from naas_python.domains.asset.AssetDomain import AssetDomain
4+
from naas_python.domains.asset.adaptors.primary.TyperAssetAdaptor import TyperAssetAdaptor
5+
6+
import logging
7+
8+
secondaryAdaptor = NaasAssetAPIAdaptor()
9+
10+
domain = AssetDomain(secondaryAdaptor)
11+
12+
primaryAdaptor = TyperAssetAdaptor(domain)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from naas_python.domains.asset.adaptors.secondary.NaasAssetAPIAdaptor import NaasAssetAPIAdaptor
2+
from naas_python.domains.asset.AssetDomain import AssetDomain
3+
from naas_python.domains.asset.adaptors.primary.SDKAssetAdaptor import SDKAssetAdaptor
4+
5+
secondaryAdaptor = NaasAssetAPIAdaptor()
6+
domain = AssetDomain(secondaryAdaptor)
7+
primaryAdaptor = SDKAssetAdaptor(domain)

0 commit comments

Comments
 (0)