Skip to content

Commit

Permalink
capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Feb 7, 2024
1 parent 61b56a5 commit fc2a943
Show file tree
Hide file tree
Showing 14 changed files with 118 additions and 44 deletions.
2 changes: 1 addition & 1 deletion addon_service/authorized_storage_account/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models

from addon_service.capability.models import IntStorageCapability
from addon_service.common.base_model import AddonsServiceBaseModel
from addon_service.common.int_capability import IntStorageCapability


class AuthorizedStorageAccount(AddonsServiceBaseModel):
Expand Down
14 changes: 8 additions & 6 deletions addon_service/authorized_storage_account/serializers.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
from rest_framework_json_api import serializers
from rest_framework_json_api.relations import (
ResourceRelatedField,
HyperlinkedRelatedField,
ResourceRelatedField,
)
from rest_framework_json_api.utils import get_resource_type_from_model

from addon_service.capability.serializers import StorageCapabilityField
from addon_service.models import (
AuthorizedStorageAccount,
ConfiguredStorageAddon,
ExternalStorageService,
ExternalCredentials,
ExternalAccount,
ExternalCredentials,
ExternalStorageService,
InternalUser,
)


RESOURCE_NAME = get_resource_type_from_model(AuthorizedStorageAccount)


Expand All @@ -32,7 +34,6 @@ def to_internal_value(self, data):


class AuthorizedStorageAccountSerializer(serializers.HyperlinkedModelSerializer):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

Expand All @@ -41,9 +42,9 @@ def __init__(self, *args, **kwargs):
self.fields.pop("configured_storage_addons", None)

url = serializers.HyperlinkedIdentityField(
view_name=f"{RESOURCE_NAME}-detail",
required=False
view_name=f"{RESOURCE_NAME}-detail", required=False
)
authorized_capabilities = StorageCapabilityField()
account_owner = AccountOwnerField(
many=False,
queryset=InternalUser.objects.all(),
Expand Down Expand Up @@ -103,4 +104,5 @@ class Meta:
"external_storage_service",
"username",
"password",
"authorized_capabilities",
]
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class _IntEnumForEnum(enum.IntEnum):
def __init_subclass__(cls, /, base_enum: type[enum.Enum], **kwargs):
super().__init_subclass__(**kwargs)
cls.__base_enum = base_enum
# ensure enums have same names
_base_names = {_item.name for _item in base_enum}
_int_names = {_item.name for _item in cls}
assert _base_names == _int_names
Expand Down
44 changes: 44 additions & 0 deletions addon_service/capability/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import enum

from rest_framework_json_api import serializers

from addon_service.capability.models import (
IntStorageCapability,
StorageCapability,
)


class EnumsMultipleChoiceField(serializers.MultipleChoiceField):
__internal_enum: type[enum.Enum]
__external_enum: type[enum.Enum]

def __init__(self, /, internal_enum, external_enum, **kwargs):
_choices = {_external_member.value for _external_member in external_enum}
super().__init__(**kwargs, choices=_choices)
self.__internal_enum = internal_enum
self.__external_enum = external_enum

def to_internal_value(self, data):
_names = super().to_internal_value(data)
return {self._to_internal_enum_member(_name) for _name in _names}

def to_representation(self, value):
_member_list = super().to_representation(value)
return {self._to_external_enum_value(_member) for _member in _member_list}

def _to_internal_enum_member(self, external_value):
_external_member = self.__external_enum(external_value)
return self.__internal_enum[_external_member.name]

def _to_external_enum_value(self, internal_value):
_internal_member = self.__internal_enum(internal_value)
_external_member = self.__external_enum[_internal_member.name]
return _external_member.value


def StorageCapabilityField(**kwargs):
return EnumsMultipleChoiceField(
external_enum=StorageCapability,
internal_enum=IntStorageCapability,
**kwargs,
)
2 changes: 1 addition & 1 deletion addon_service/configured_storage_addon/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models

from addon_service.capability.models import IntStorageCapability
from addon_service.common.base_model import AddonsServiceBaseModel
from addon_service.common.int_capability import IntStorageCapability


class ConfiguredStorageAddon(AddonsServiceBaseModel):
Expand Down
3 changes: 3 additions & 0 deletions addon_service/configured_storage_addon/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from rest_framework_json_api.relations import ResourceRelatedField
from rest_framework_json_api.utils import get_resource_type_from_model

from addon_service.capability.serializers import StorageCapabilityField
from addon_service.models import (
AuthorizedStorageAccount,
ConfiguredStorageAddon,
Expand All @@ -22,6 +23,7 @@ def to_internal_value(self, data):
class ConfiguredStorageAddonSerializer(serializers.HyperlinkedModelSerializer):
root_folder = serializers.CharField(required=False)
url = serializers.HyperlinkedIdentityField(view_name=f"{RESOURCE_NAME}-detail")
connected_capabilities = StorageCapabilityField()
base_account = ResourceRelatedField(
queryset=AuthorizedStorageAccount.objects.all(),
many=False,
Expand All @@ -47,4 +49,5 @@ class Meta:
"root_folder",
"base_account",
"authorized_resource",
"connected_capabilities",
]
39 changes: 34 additions & 5 deletions addon_service/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by Django 4.2.7 on 2023-12-11 20:02
# Generated by Django 4.2.7 on 2024-02-07 20:42

import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import (
migrations,
Expand Down Expand Up @@ -27,6 +28,20 @@ class Migration(migrations.Migration):
),
("created", models.DateTimeField(editable=False)),
("modified", models.DateTimeField()),
(
"authorized_capabilities",
django.contrib.postgres.fields.ArrayField(
base_field=models.IntegerField(
choices=[
(1, "ACCESS"),
(2, "BROWSE"),
(3, "UPDATE"),
(4, "COMMIT"),
]
),
size=None,
),
),
("default_root_folder", models.CharField(blank=True)),
],
options={
Expand Down Expand Up @@ -215,19 +230,33 @@ class Migration(migrations.Migration):
("modified", models.DateTimeField()),
("root_folder", models.CharField()),
(
"base_account",
"connected_capabilities",
django.contrib.postgres.fields.ArrayField(
base_field=models.IntegerField(
choices=[
(1, "ACCESS"),
(2, "BROWSE"),
(3, "UPDATE"),
(4, "COMMIT"),
]
),
size=None,
),
),
(
"authorized_resource",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="configured_storage_addons",
to="addon_service.authorizedstorageaccount",
to="addon_service.internalresource",
),
),
(
"authorized_resource",
"base_account",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="configured_storage_addons",
to="addon_service.internalresource",
to="addon_service.authorizedstorageaccount",
),
),
],
Expand Down
3 changes: 3 additions & 0 deletions addon_service/tests/_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from factory.django import DjangoModelFactory

from addon_service import models as db
from addon_service.capability.models import IntStorageCapability


class InternalUserFactory(DjangoModelFactory):
Expand Down Expand Up @@ -59,6 +60,7 @@ class Meta:
model = db.AuthorizedStorageAccount

default_root_folder = "/"
authorized_capabilities = factory.List([IntStorageCapability.ACCESS])
external_storage_service = factory.SubFactory(ExternalStorageServiceFactory)
external_account = factory.SubFactory(ExternalAccountFactory)
# TODO: external_account.credentials_issuer same as
Expand All @@ -70,5 +72,6 @@ class Meta:
model = db.ConfiguredStorageAddon

root_folder = "/"
connected_capabilities = factory.List([IntStorageCapability.ACCESS])
base_account = factory.SubFactory(AuthorizedStorageAccountFactory)
authorized_resource = factory.SubFactory(InternalResourceFactory)
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,13 @@ def test_get(self):
set(_content["data"]["attributes"].keys()),
{
"default_root_folder",
"authorized_capabilities",
},
)
self.assertEqual(
_content["data"]["attributes"]["authorized_capabilities"],
["access"],
)
self.assertEqual(
set(_content["data"]["relationships"].keys()),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,13 @@ def test_get(self):
set(_content["data"]["attributes"].keys()),
{
"root_folder",
"connected_capabilities",
},
)
self.assertEqual(
_content["data"]["attributes"]["connected_capabilities"],
["access"],
)
self.assertEqual(
set(_content["data"]["relationships"].keys()),
{
Expand Down
2 changes: 1 addition & 1 deletion addon_toolkit/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def __set_name__(self, cls, name):
AddonOperation.register(
AddonOperation(
operation_type=self.operation_type,
capability_id=self.capability_id,
capability_id=str(self.capability_id),
declaration_cls=cls,
method_name=name,
),
Expand Down
Empty file added addon_toolkit/tests/__init__.py
Empty file.
42 changes: 12 additions & 30 deletions addon_toolkit/tests/test_addon_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
class TestAddonCategory(unittest.TestCase):
def setUp(self):
class _MyChecksumArchiveCapability(enum.StrEnum):
GET_IT = enum.auto()
PUT_IT = enum.auto()
GET_IT = "get-it"
PUT_IT = "put-it"

class _MyChecksumArchiveInterface(AddonInterface):
"""this is a docstring for _MyChecksumArchiveInterface
Expand All @@ -26,23 +26,17 @@ class _MyChecksumArchiveInterface(AddonInterface):
@redirect_operation(capability=_MyChecksumArchiveCapability.GET_IT)
def url_for_get(self, checksum_iri) -> str:
"""this url_for_get docstring should find its way to docs"""
return f"https://myarchive.example///{checksum_iri}"
raise NotImplementedError

@proxy_operation(capability=_MyChecksumArchiveCapability.GET_IT)
async def query_relations(self, checksum_iri, query=None):
"""this query_relations docstring should find its way to docs"""
# yields rdf triples (or twoples with implicit subject)
yield ("http://purl.org/dc/terms/references", "checksum:foo:bar")
raise NotImplementedError

@redirect_operation(capability=_MyChecksumArchiveCapability.PUT_IT)
def url_for_put(self, checksum_iri):
"""this url_for_put docstring should find its way to docs"""
# TODO: how to represent "send a PUT request here"?
# return RedirectLadle(
# HTTPMethod.PUT,
# f'https://myarchive.example///{checksum_iri}',
# )?
return f"https://myarchive.example///{checksum_iri}"
raise NotImplementedError

self.my_addon_category = AddonCategory(
capabilities=_MyChecksumArchiveCapability,
Expand All @@ -54,7 +48,7 @@ def url_for_get(self, checksum_iri) -> str:
return f"https://myarchive.example///{checksum_iri}"

async def query_relations(self, checksum_iri, query=None):
# yields rdf triples (or twoples with implicit subject)
# maybe yield rdf triples (or twoples with implicit subject)
yield ("http://purl.org/dc/terms/references", "checksum:foo:bar")

def url_for_put(self, checksum_iri):
Expand All @@ -70,19 +64,19 @@ def url_for_put(self, checksum_iri):
def test_operation_list(self):
_get_operation = AddonOperation(
operation_type=AddonOperationType.REDIRECT,
capability=self.my_addon_category.capabilities.GET,
capability_id=self.my_addon_category.capabilities.GET_IT,
declaration_cls=self.my_addon_category.base_interface,
method_name="url_for_get",
)
_put_operation = AddonOperation(
operation_type=AddonOperationType.REDIRECT,
capability=self.my_addon_category.capabilities.PUT,
capability_id=self.my_addon_category.capabilities.PUT_IT,
declaration_cls=self.my_addon_category.base_interface,
method_name="url_for_put",
)
_query_operation = AddonOperation(
operation_type=AddonOperationType.PROXY,
capability=self.my_addon_category.capabilities.GET,
capability_id=self.my_addon_category.capabilities.GET_IT,
declaration_cls=self.my_addon_category.base_interface,
method_name="query_relations",
)
Expand All @@ -91,26 +85,14 @@ def test_operation_list(self):
{_get_operation, _put_operation, _query_operation},
)
self.assertEqual(
set(
self.my_addon_category.operations_declared(
capability_iri=self.namespace.get_thing,
)
),
set(self.my_addon_category.operations_declared(capability_id="get-it")),
{_get_operation, _query_operation},
)
self.assertEqual(
set(
self.my_addon_category.operations_declared(
capability_iri=self.namespace.put_thing,
)
),
set(self.my_addon_category.operations_declared(capability_id="put-it")),
{_put_operation},
)
self.assertEqual(
set(
self.my_addon_category.operations_declared(
capability_iri="http://nothing.example/",
)
),
set(self.my_addon_category.operations_declared(capability_id="nothing")),
set(),
)

0 comments on commit fc2a943

Please sign in to comment.