Skip to content

Commit

Permalink
(wip) operation params
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Feb 26, 2024
1 parent 5c6817d commit 5acde4e
Show file tree
Hide file tree
Showing 20 changed files with 451 additions and 244 deletions.
9 changes: 6 additions & 3 deletions addon_imps/storage/my_blarg.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from addon_toolkit.storage import StorageInterface
from addon_toolkit.storage import (
RedirectResult,
StorageInterface,
)


class MyBlargStorage(StorageInterface):
"""blarg?"""

def item_download_url(self, item_id):
def download(self, item_id) -> RedirectResult:
"""blarg blarg blarg"""
return f"http://blarg.example/{item_id}"
return RedirectResult(f"http://blarg.example/{item_id}")
4 changes: 2 additions & 2 deletions addon_service/authorized_storage_account/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from addon_service.capability.enums import IntStorageCapability
from addon_service.common.base_model import AddonsServiceBaseModel
from addon_service.storage_operation.models import StorageOperationModel
from addon_toolkit import get_operation_implementations
from addon_toolkit import AddonOperationImplementation


class AuthorizedStorageAccount(AddonsServiceBaseModel):
Expand Down Expand Up @@ -50,7 +50,7 @@ def authorized_capabilities_enum(self) -> list[enum.Enum]:
def authorized_operations(self) -> list[StorageOperationModel]:
return [
StorageOperationModel(_imp)
for _imp in get_operation_implementations(
for _imp in AddonOperationImplementation.on_implementation_cls(
self.external_storage_service.storage_imp.imp_cls,
capabilities=self.authorized_capabilities_enum,
)
Expand Down
52 changes: 52 additions & 0 deletions addon_service/common/dataclass_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import abc
import json
from urllib.parse import (
quote,
unquote,
)

from addon_service.common.opaque import (
make_opaque,
unmake_opaque,
)


# not a database model; just a convenience wrapper for
# AddonOperationImplementation to give the serializer
class BaseDataclassModel(abc.ABC):
###
# abstract methods

@classmethod
@abc.abstractmethod
def get_by_natural_key(cls, key: list):
raise NotImplementedError

@property
@abc.abstractmethod
def natural_key(self) -> list:
raise NotImplementedError

###
# class methods

@classmethod
def get_by_pk(cls, pk: str):
_natural_key = json.loads(unmake_opaque(pk))
return cls.get_by_natural_key(_natural_key)

@classmethod
def get_by_natural_key_str(cls, key_str: str):
_key = [unquote(_key_segment) for _key_segment in key_str.split(":")]
return cls.get_by_natural_key(_key)

###
# instance methods

@property
def natural_key_str(self) -> str:
return ":".join(quote(_key_segment) for _key_segment in self.natural_key)

@property
def pk(self) -> str:
return make_opaque(json.dumps(self.natural_key))
4 changes: 2 additions & 2 deletions addon_service/configured_storage_addon/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from addon_service.capability.enums import IntStorageCapability
from addon_service.common.base_model import AddonsServiceBaseModel
from addon_service.storage_operation.models import StorageOperationModel
from addon_toolkit import get_operation_implementations
from addon_toolkit import AddonOperationImplementation


class ConfiguredStorageAddon(AddonsServiceBaseModel):
Expand Down Expand Up @@ -51,7 +51,7 @@ def connected_capabilities_enum(self) -> list[enum.Enum]:
def connected_operations(self) -> list[StorageOperationModel]:
return [
StorageOperationModel(_imp)
for _imp in get_operation_implementations(
for _imp in AddonOperationImplementation.on_implementation_cls(
self.base_account.external_storage_service.storage_imp.imp_cls,
capabilities=self.connected_capabilities_enum,
)
Expand Down
9 changes: 5 additions & 4 deletions addon_service/management/commands/fill_garbage.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ def handle_label(self, label, **options):
IntStorageCapability.UPDATE,
],
)
for _j in range(5):
for _op in _asa.authorized_operations:
_ir, _ = db.ResourceReference.objects.get_or_create(
resource_uri=f"http://osf.example/r{label}{_j}",
resource_uri=f"http://osf.example/r{label}{_op.name}",
)
_csa = db.ConfiguredStorageAddon.objects.create(
base_account=_asa,
Expand All @@ -53,8 +53,9 @@ def handle_label(self, label, **options):
)
_soi = db.StorageOperationInvocation.objects.create(
int_invocation_status=IntInvocationStatus.DONE,
operation_identifier="foo",
operation_args={},
operation_identifier=_op.natural_key_str,
operation_args={"item_id": "foo"},
thru_addon=_csa,
by_user=_iu,
)
return str(_soi)
9 changes: 8 additions & 1 deletion addon_service/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.7 on 2024-02-22 21:59
# Generated by Django 4.2.7 on 2024-02-26 21:42

import django.contrib.postgres.fields
import django.db.models.deletion
Expand Down Expand Up @@ -304,6 +304,13 @@ class Migration(migrations.Migration):
),
("operation_identifier", models.TextField()),
("operation_args", models.JSONField(blank=True, default=dict)),
(
"by_user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="addon_service.userreference",
),
),
(
"thru_addon",
models.ForeignKey(
Expand Down
7 changes: 4 additions & 3 deletions addon_service/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@
from addon_service.external_credentials.models import ExternalCredentials
from addon_service.external_storage_service.models import ExternalStorageService
from addon_service.resource_reference.models import ResourceReference
from addon_service.storage_imp.models import StorageImpModel
from addon_service.storage_operation.models import StorageOperationModel
from addon_service.storage_operation_invocation.models import StorageOperationInvocation
from addon_service.user_reference.models import UserReference


__all__ = (
"AuthorizedStorageAccount",
# 'AuthorizedComputeAccount',
"ConfiguredStorageAddon",
# 'ConfiguredComputeAddon',
"CredentialsIssuer",
"ExternalAccount",
"ExternalCredentials",
"ExternalStorageService",
# 'ExternalComputeService',
"ResourceReference",
"StorageImpModel",
"StorageOperationInvocation",
"StorageOperationModel",
"UserReference",
)
21 changes: 10 additions & 11 deletions addon_service/storage_imp/models.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import dataclasses

from addon_service.common.opaque import (
make_opaque,
unmake_opaque,
)
from addon_service.common.dataclass_model import BaseDataclassModel
from addon_service.storage_imp.known import (
ApiStorageImp,
StorageImp,
)
from addon_toolkit import get_operation_implementations
from addon_toolkit import AddonOperationImplementation


# not a database model; represents an implementation
# of StorageInterface in a form easy to give the serializer
@dataclasses.dataclass
class StorageImpModel:
class StorageImpModel(BaseDataclassModel):
imp_cls: type

@classmethod
def get_by_pk(cls, pk: str) -> "StorageImpModel":
_imp_name = unmake_opaque(pk)
def get_by_natural_key(cls, key: tuple) -> "StorageImpModel":
(_imp_name,) = key
_imp: StorageImp = ApiStorageImp[_imp_name].to_original_enum()
return cls(_imp.value)

@property
def pk(self) -> str:
return make_opaque(self.name)
def natural_key(self) -> tuple:
return (self.name,)

@property
def name(self) -> str:
Expand All @@ -42,7 +39,9 @@ def implemented_operations(self) -> list:

return [
StorageOperationModel(_imp_op)
for _imp_op in get_operation_implementations(self.imp_cls)
for _imp_op in AddonOperationImplementation.on_implementation_cls(
self.imp_cls
)
]

class JSONAPIMeta:
Expand Down
21 changes: 12 additions & 9 deletions addon_service/storage_operation/models.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import dataclasses

from addon_service.common.opaque import (
make_opaque,
unmake_opaque,
)
from addon_service.common.dataclass_model import BaseDataclassModel
from addon_service.storage_imp.known import (
ApiStorageImp,
StorageImp,
)
from addon_service.storage_imp.models import StorageImpModel
from addon_toolkit import AddonOperationImplementation
from addon_toolkit.dataclass_json import jsonschema_for_dataclass


# not a database model; just a convenience wrapper for
# AddonOperationImplementation to give the serializer
@dataclasses.dataclass
class StorageOperationModel:
class StorageOperationModel(BaseDataclassModel):
imp: AddonOperationImplementation

@classmethod
def get_by_pk(cls, pk: str) -> "StorageOperationModel":
_imp_name, _operation_name = unmake_opaque(pk).split("-") # TODO: reconsider
def get_by_natural_key(cls, key: tuple) -> "StorageOperationModel":
_imp_name, _operation_name = key
_interface_imp = StorageImp[_imp_name].value # TODO: try, 404
return cls(
AddonOperationImplementation.by_operation_name(
Expand All @@ -29,14 +27,15 @@ def get_by_pk(cls, pk: str) -> "StorageOperationModel":
)
)

###
# properties for serializer fields

@property
def pk(self) -> str:
def natural_key(self) -> tuple:
_imp_api_name = ApiStorageImp.from_original_enum_value(
self.imp.implementation_cls
).value
return make_opaque("-".join((_imp_api_name, self.name)))
return (_imp_api_name, self.name)

@property
def name(self):
Expand All @@ -58,5 +57,9 @@ def capability(self):
def implemented_by(self) -> StorageImpModel:
return StorageImpModel(self.imp.implementation_cls)

@property
def params_jsonschema(self) -> dict:
return jsonschema_for_dataclass(self.imp.operation.params_dataclass)

class JSONAPIMeta:
resource_name = "storage-operations"
13 changes: 12 additions & 1 deletion addon_service/storage_operation/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,27 @@

class StorageOperationSerializer(serializers.Serializer):
url = serializers.HyperlinkedIdentityField(view_name=SELF_LINK_VIEW_NAME)

###
# attributes

name = serializers.CharField(read_only=True)
docstring = serializers.CharField(read_only=True)
implementation_docstring = serializers.CharField(read_only=True)
capability = StorageCapabilityChoiceField(read_only=True)
params_jsonschema = serializers.JSONField()

###
# relationships

implemented_by = DataclassRelatedDataField(
dataclass_model=StorageImpModel,
related_link_view_name=RELATED_LINK_VIEW_NAME,
many=False,
)
# TODO operation_parameters = ListField(

###
# wiring

included_serializers = {
"implemented_by": "addon_service.serializers.StorageImpSerializer",
Expand Down
19 changes: 18 additions & 1 deletion addon_service/storage_operation_invocation/models.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import jsonschema
from django.core.exceptions import ValidationError
from django.db import models

from addon_service.common.base_model import AddonsServiceBaseModel
from addon_service.common.invocation import (
IntInvocationStatus,
InvocationStatus,
)
from addon_service.storage_operation.models import StorageOperationModel


class StorageOperationInvocation(AddonsServiceBaseModel):
int_invocation_status = models.IntegerField(
choices=IntInvocationStatus.as_django_choices(),
default=IntInvocationStatus.STARTING,
)
operation_identifier = models.TextField() # TODO
operation_identifier = models.TextField()
operation_args = models.JSONField(default=dict, blank=True)
thru_addon = models.ForeignKey("ConfiguredStorageAddon", on_delete=models.CASCADE)
by_user = models.ForeignKey("UserReference", on_delete=models.CASCADE)

class Meta:
indexes = [
Expand All @@ -29,3 +33,16 @@ def invocation_status(self) -> InvocationStatus:
return InvocationStatus(
IntInvocationStatus(self.int_invocation_status).to_original_enum()
)

@property
def operation(self) -> StorageOperationModel:
return StorageOperationModel.get_by_natural_key_str(self.operation_identifier)

def clean(self):
try:
jsonschema.validate(
instance=self.operation_args,
schema=self.operation.params_jsonschema,
)
except jsonschema.exceptions.ValidationError as _error:
raise ValidationError(_error)
Loading

0 comments on commit 5acde4e

Please sign in to comment.