-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
137 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
from primitive_metadata import primitive_rdf as rdf | ||
|
||
|
||
GRAVY = rdf.IriNamespace("https://addons.osf.example/vocab/2023/") | ||
GRAVY = rdf.IriNamespace("https://addons.osf.example/vocab/2024/") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import dataclasses | ||
import inspect | ||
import logging | ||
from typing import Callable | ||
|
||
from addon_toolkit.namespaces import GRAVY | ||
|
||
|
||
__all__ = ( # public module attrs: | ||
"immediate_operation", | ||
"proxy_read_operation", | ||
"proxy_act_operation", | ||
) | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
### | ||
# decorators to declare operations on interface classes | ||
|
||
|
||
def immediate_operation(fn): | ||
# decorator for operations that can be computed immediately, | ||
# without sending any requests or waiting on external resources | ||
# (e.g. build a url in a known pattern or return declared static metadata) | ||
assert inspect.isfunction(fn) | ||
assert not inspect.isawaitable(fn) | ||
# TODO: assert based on `inspect.signature(fn).parameters` | ||
# TODO: helpful error messaging for implementers | ||
return _DecoratedOperation(fn) | ||
|
||
|
||
def proxy_read_operation(fn): | ||
# decorator for operations that require fetching data from elsewhere, | ||
# but make no changes (e.g. get a metadata description of an item, | ||
# list items in a given folder) | ||
assert inspect.isasyncgenfunction(fn) | ||
# TODO: assert based on `inspect.signature(fn).parameters` | ||
# TODO: assert based on return value? | ||
return _DecoratedOperation(fn) | ||
|
||
|
||
def proxy_act_operation(fn): | ||
# decorator for operations that initiate change, may take some time, | ||
# and may fail in strange ways (e.g. delete an item, copy a file tree) | ||
assert inspect.iscoroutine(fn) | ||
# TODO: assert based on `inspect.signature(fn).parameters` | ||
# TODO: assert based on return value? | ||
return _DecoratedOperation(fn) | ||
|
||
|
||
### | ||
# module-private helpers | ||
|
||
|
||
@dataclasses.dataclass | ||
class _DecoratedOperation: | ||
"""a temporary object for decorated operation methods""" | ||
|
||
operation_fn: Callable | ||
|
||
def __set_name__(self, cls, name): | ||
# called for each decorated class method | ||
_operation_method_map = _get_operation_method_map(cls) | ||
assert name not in _operation_method_map | ||
_operation_method_map[name] | ||
# overwrite this _DecoratedOperation with the operation_fn | ||
# now that operation record-keeping has completed | ||
setattr(cls, name, self.operation_fn) | ||
|
||
|
||
def _get_operation_iri(fn): | ||
# may raise AttributeError | ||
return getattr(fn, GRAVY.operation) | ||
|
||
|
||
def _set_operation_iri(operation_fn, operation_iri): | ||
try: | ||
_prior_value = _get_operation_iri(operation_fn) | ||
except AttributeError: | ||
_prior_value = None | ||
if _prior_value is not None: | ||
raise ValueError("cannot call _set_operation_iri twice (on %r)", operation_fn) | ||
setattr(operation_fn, GRAVY.operation, operation_iri) | ||
|
||
|
||
def _get_operation_method_map(obj): | ||
try: | ||
return getattr(obj, GRAVY.operation_map) | ||
except AttributeError: | ||
_operation_method_map = {} | ||
setattr(obj, GRAVY.operation_map, _operation_method_map) | ||
return _operation_method_map |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,88 +1,82 @@ | ||
from addon_service.namespaces import GRAVY | ||
|
||
from .capability import ( | ||
immediate_capability, | ||
proxy_act_capability, | ||
proxy_read_capability, | ||
) | ||
from .interfaces import ( | ||
BaseAddonInterface, | ||
PagedResult, | ||
) | ||
from .operation import ( | ||
immediate_operation, | ||
proxy_act_operation, | ||
proxy_read_operation, | ||
) | ||
|
||
|
||
# what a base StorageInterface could be like (incomplete) | ||
class StorageInterface(BaseAddonInterface): | ||
## | ||
# "item-read" capabilities: | ||
# "item-read" operations: | ||
|
||
@immediate_capability(GRAVY.item_download_url, requires={GRAVY.read}) | ||
@immediate_operation | ||
def item_download_url(self, item_id: str) -> str: | ||
raise NotImplementedError # e.g. waterbutler url, when appropriate | ||
|
||
@proxy_read_capability(GRAVY.get_item_description, requires={GRAVY.read}) | ||
@proxy_read_operation | ||
async def get_item_description(self, item_id: str) -> dict: | ||
raise NotImplementedError | ||
|
||
## | ||
# "item-write" capabilities: | ||
# "item-write" operations: | ||
|
||
@immediate_capability(GRAVY.item_upload_url, requires={GRAVY.write}) | ||
@immediate_operation | ||
def item_upload_url(self, item_id: str) -> str: | ||
raise NotImplementedError | ||
|
||
@proxy_act_capability(GRAVY.pls_delete_item, requires={GRAVY.write}) | ||
@proxy_act_operation | ||
async def pls_delete_item(self, item_id: str): | ||
raise NotImplementedError | ||
|
||
## | ||
# "tree-read" capabilities: | ||
# "tree-read" operations: | ||
|
||
@proxy_read_capability(GRAVY.get_root_item_ids, requires={GRAVY.read, GRAVY.tree}) | ||
@proxy_read_operation | ||
async def get_root_item_ids(self) -> PagedResult[str]: | ||
raise NotImplementedError | ||
|
||
@proxy_read_capability(GRAVY.get_parent_item_id, requires={GRAVY.read, GRAVY.tree}) | ||
@proxy_read_operation | ||
async def get_parent_item_id(self, item_id: str) -> str | None: | ||
raise NotImplementedError | ||
|
||
@proxy_read_capability(GRAVY.get_item_path, requires={GRAVY.read, GRAVY.tree}) | ||
@proxy_read_operation | ||
async def get_item_path(self, item_id: str) -> str: | ||
raise NotImplementedError | ||
|
||
@proxy_read_capability(GRAVY.get_child_item_ids, requires={GRAVY.read, GRAVY.tree}) | ||
@proxy_read_operation | ||
async def get_child_item_ids(self, item_id: str) -> PagedResult[str]: | ||
raise NotImplementedError | ||
|
||
## | ||
# "tree-write" capabilities | ||
# "tree-write" operations | ||
|
||
@proxy_act_capability(GRAVY.pls_move_item, requires={GRAVY.write, GRAVY.tree}) | ||
@proxy_act_operation | ||
async def pls_move_item(self, item_id: str, new_treepath: str): | ||
raise NotImplementedError | ||
|
||
@proxy_act_capability(GRAVY.pls_copy_item, requires={GRAVY.write, GRAVY.tree}) | ||
@proxy_act_operation | ||
async def pls_copy_item(self, item_id: str, new_treepath: str): | ||
raise NotImplementedError | ||
|
||
## | ||
# "version-read" capabilities | ||
# "version-read" operations | ||
|
||
@proxy_read_capability( | ||
GRAVY.get_current_version_id, requires={GRAVY.read, GRAVY.version} | ||
) | ||
@proxy_read_operation | ||
async def get_current_version_id(self, item_id: str) -> str: | ||
raise NotImplementedError | ||
|
||
@proxy_read_capability(GRAVY.get_version_ids, requires={GRAVY.read, GRAVY.version}) | ||
@proxy_read_operation | ||
async def get_version_ids(self, item_id: str) -> PagedResult[str]: | ||
raise NotImplementedError | ||
|
||
## | ||
# "version-write" capabilities | ||
# "version-write" operations | ||
|
||
@proxy_act_capability( | ||
GRAVY.pls_restore_version, requires={GRAVY.write, GRAVY.version} | ||
) | ||
@proxy_act_operation | ||
async def pls_restore_version(self, item_id: str, version_id: str): | ||
raise NotImplementedError |