resource_service.py
ofrak.service.resource_service
AttributeIndexDict (defaultdict)
defaultdict
that passes the missing key to the default factory.
ResourceService (ResourceServiceInterface)
create(self, resource)
async
Add a ResourceModel to the resource service
database according to the given model. If the resource
model says it has a parent,
resource
will be added as a child of that parent.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource |
ResourceModel |
The resource model to add to the database |
required |
Returns:
Type | Description |
---|---|
ResourceModel |
The same model which was passed in, with no changes |
Exceptions:
Type | Description |
---|---|
AlreadyExistError |
If |
NotFoundError |
If |
Source code in ofrak/service/resource_service.py
async def create(self, resource: ResourceModel) -> ResourceModel:
if resource.id in self._resource_store:
raise AlreadyExistError(f"A resource with id {resource.id.hex()} already exists!")
if resource.parent_id is not None:
parent_resource_node = self._resource_store.get(resource.parent_id)
if parent_resource_node is None:
raise NotFoundError(
f"The parent resource with id {resource.parent_id.hex()} does not exist"
)
LOGGER.debug(
f"Creating resource {resource.id.hex()} as child of {resource.parent_id.hex()}"
)
else:
parent_resource_node = None
LOGGER.debug(f"Creating resource {resource.id.hex()}")
resource_node = ResourceNode(resource, parent_resource_node)
self._resource_store[resource.id] = resource_node
if resource.data_id is not None:
self._resource_by_data_id_store[resource.data_id] = resource_node
if parent_resource_node is None:
self._root_resources[resource.id] = resource_node
# Take care of the indexes
for tag in resource.tags:
self._add_resource_tag_to_index(tag, resource_node)
for indexable_attribute, value in resource.get_index_values().items():
self._add_resource_attribute_to_index(indexable_attribute, value, resource_node)
return resource
get_root_resources(self)
async
Get all of the root resources known to this resource service. Any resource created without a parent will be returned by this method.
Returns:
Type | Description |
---|---|
List[ofrak.model.resource_model.ResourceModel] |
All resources with no parents |
Source code in ofrak/service/resource_service.py
async def get_root_resources(self) -> List[ResourceModel]:
return [root_node.model for root_node in self._root_resources.values()]
verify_ids_exist(self, resource_ids)
async
Check if a number of resource IDs exist in the resource store. This is useful for filtering out IDs of resources which have been deleted.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_ids |
Iterable[bytes] |
Iterable of resource IDs to check for |
required |
Returns:
Type | Description |
---|---|
Iterable[bool] |
A boolean for each resource ID, True if it exists in the store and False otherwise |
Source code in ofrak/service/resource_service.py
async def verify_ids_exist(self, resource_ids: Iterable[bytes]) -> Iterable[bool]:
return [resource_id in self._resource_store for resource_id in resource_ids]
get_by_data_ids(self, data_ids)
async
Get the resource models with a given sequence of data IDs.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data_ids |
Iterable[bytes] |
A list of valid data IDs |
required |
Returns:
Type | Description |
---|---|
Iterable[ofrak.model.resource_model.ResourceModel] |
A sequence of resource models each with one of the given data IDs, in the same order which |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource for all of the IDs in |
Source code in ofrak/service/resource_service.py
async def get_by_data_ids(self, data_ids: Iterable[bytes]) -> Iterable[ResourceModel]:
results = []
for data_id in data_ids:
resource_node = self._resource_by_data_id_store.get(data_id)
if resource_node is None:
raise NotFoundError(f"The resource with data ID {data_id.hex()} does not exist")
results.append(resource_node.model)
return results
get_by_ids(self, resource_ids)
async
Get the resource models with a given sequence of resource IDs.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_ids |
Iterable[bytes] |
A list of valid resource IDs |
required |
Returns:
Type | Description |
---|---|
Iterable[ofrak.model.resource_model.ResourceModel] |
A sequence of resource models each with one of the given resource IDs, in the same order which |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource for all of the IDs in |
Source code in ofrak/service/resource_service.py
async def get_by_ids(self, resource_ids: Iterable[bytes]) -> Iterable[ResourceModel]:
results = []
for resource_id in resource_ids:
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
results.append(resource_node.model)
return results
get_by_id(self, resource_id)
async
Get the resource model with a given resource ID.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_id |
bytes |
A valid resource ID |
required |
Returns:
Type | Description |
---|---|
ResourceModel |
The resource model with ID matching |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID |
Source code in ofrak/service/resource_service.py
async def get_by_id(self, resource_id: bytes) -> ResourceModel:
LOGGER.debug(f"Fetching resource {resource_id.hex()}")
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
return resource_node.model
get_depths(self, resource_ids)
async
Get the depth of each resource in resource_ids
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_ids |
Iterable[bytes] |
A list of valid resource IDs |
required |
Returns:
Type | Description |
---|---|
Iterable[int] |
A sequence of resource model depths, in the same order which |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource for all of the IDs in |
Source code in ofrak/service/resource_service.py
async def get_depths(self, resource_ids: Iterable[bytes]) -> Iterable[int]:
results = []
for resource_id in resource_ids:
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
results.append(resource_node.get_depth())
return results
get_ancestors_by_id(self, resource_id, max_count=-1, r_filter=None)
async
Get the resource models of the ancestors of a resource with a given ID. These ancestors may be filtered by an optional filter argument. A maximum count of ancestors may also be given, to cap the number of (filtered or unfiltered) ancestors returned.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_id |
bytes |
ID of resource to get ancestors of |
required |
max_count |
int |
Optional argument to cap the number of models returned; if set to -1 (default) then any number of ancestors may be returned |
-1 |
r_filter |
Optional[ofrak.service.resource_service_i.ResourceFilter] |
Optional resource filter for the resource models returned; if set to |
None |
Returns:
Type | Description |
---|---|
Iterable[ofrak.model.resource_model.ResourceModel] |
As many ancestors of |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID |
Source code in ofrak/service/resource_service.py
async def get_ancestors_by_id(
self,
resource_id: bytes,
max_count: int = -1,
r_filter: Optional[ResourceFilter] = None,
) -> Iterable[ResourceModel]:
LOGGER.debug(f"Fetching ancestor(s) of {resource_id.hex()}")
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
r_filter_logic = AggregateResourceFilterLogic.create(
r_filter,
self._tag_indexes,
self._attribute_indexes,
)
include_root = False if r_filter is None else r_filter.include_self
resources = map(
lambda n: n.model,
filter(r_filter_logic.filter, resource_node.walk_ancestors(include_root)),
)
if max_count < 0:
return resources
return itertools.islice(resources, 0, max_count)
get_descendants_by_id(self, resource_id, max_count=-1, max_depth=-1, r_filter=None, r_sort=None)
async
Get the resource models of the descendants of a resource with a given ID. These descendants may be filtered by an optional filter argument. A maximum count of descendants may also be given, to cap the number of (filtered or unfiltered) descendants returned. A maximum depth may also be given, to limit how deep to search for descendants.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_id |
bytes |
ID of resource to get descendants of |
required |
max_count |
int |
Optional argument to cap the number of models returned; if set to -1 (default) then any number of descendants may be returned |
-1 |
max_depth |
int |
Optional argument to limit the depth to search for descendants; if set to -1 (default) then descendants of any depth may be returned; if set to 1, for example, only children of |
-1 |
r_filter |
Optional[ofrak.service.resource_service_i.ResourceFilter] |
Optional resource filter for the resource models returned; if set to |
None |
r_sort |
Optional[ofrak.service.resource_service_i.ResourceSort] |
Optional logic to order the returned descendants by the value of a specific attribute of each descendant |
None |
Returns:
Type | Description |
---|---|
Iterable[ofrak.model.resource_model.ResourceModel] |
As many descendants of |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID |
Source code in ofrak/service/resource_service.py
async def get_descendants_by_id(
self,
resource_id: bytes,
max_count: int = -1,
max_depth: int = -1,
r_filter: Optional[ResourceFilter] = None,
r_sort: Optional[ResourceSort] = None,
) -> Iterable[ResourceModel]:
# LOGGER.debug(f"Fetching descendant(s) of {resource_id.hex()}")
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
aggregate_sort_logic = ResourceSortLogic.create(r_sort, self._attribute_indexes)
aggregate_filter_logic = AggregateResourceFilterLogic.create(
r_filter, self._tag_indexes, self._attribute_indexes, resource_node, max_depth
)
# This is the planning phase used to determine the best index to use for further filtering
filter_logic: Optional[ResourceFilterLogic] = None
filter_cost = sys.maxsize
sort_cost = sys.maxsize
if aggregate_sort_logic.has_effect():
sort_cost = aggregate_sort_logic.get_match_count()
if sort_cost == 0:
return tuple()
for _filter_logic in aggregate_filter_logic.filters:
_filter_cost = _filter_logic.get_match_count()
if _filter_cost == 0:
return tuple()
if (
aggregate_sort_logic.has_effect()
and aggregate_sort_logic.get_attribute() != _filter_logic.get_attribute()
):
# The resources matching the filter would need to get sorted, making the
# worst case # scenario more expensive
_filter_cost = int(_filter_cost * math.log2(_filter_cost))
if _filter_cost < filter_cost:
filter_cost = _filter_cost
filter_logic = _filter_logic
# Use the estimated cost to pick the fastest way to compute the results
if (
filter_logic is not None
and filter_logic.get_attribute() is not None
and filter_logic.get_attribute() == aggregate_sort_logic.get_attribute()
):
resource_nodes = filter_logic.walk(aggregate_sort_logic.get_direction())
aggregate_filter_logic.ignore_filter(filter_logic)
aggregate_sort_logic = NullResourceSortLogic()
elif sort_cost < filter_cost:
resource_nodes = aggregate_sort_logic.walk()
aggregate_sort_logic = NullResourceSortLogic()
elif filter_logic is not None:
resource_nodes = filter_logic.walk(ResourceSortDirection.ASCENDANT)
# No need to filter on that index since it serves as the root index
aggregate_filter_logic.ignore_filter(filter_logic)
if aggregate_filter_logic.has_effect():
resource_nodes = filter(aggregate_filter_logic.filter, resource_nodes)
resources: Iterable[ResourceModel] = map(lambda n: n.model, resource_nodes)
if aggregate_sort_logic.has_effect():
resources = aggregate_sort_logic.sort(resources)
if max_count >= 0:
resources = itertools.islice(resources, 0, max_count)
return resources
get_siblings_by_id(self, resource_id, max_count=-1, r_filter=None, r_sort=None)
async
Get the resource models of the siblings of a resource with a given ID. These siblings may be filtered by an optional filter argument. A maximum count of siblings may also be given, to cap the number of (filtered or unfiltered) siblings returned.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_id |
bytes |
ID of resource to get siblings of |
required |
max_count |
int |
Optional argument to cap the number of models returned; if set to -1 (default) then any number of siblings may be returned |
-1 |
r_filter |
Optional[ofrak.service.resource_service_i.ResourceFilter] |
Optional resource filter for the resource models returned; if set to None all siblings may be returned (the model for |
None |
r_sort |
Optional[ofrak.service.resource_service_i.ResourceSort] |
Optional logic to order the returned siblings by the value of a specific attribute of each sibling |
None |
Returns:
Type | Description |
---|---|
Iterable[ofrak.model.resource_model.ResourceModel] |
As many siblings of |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID |
NotFoundError |
If the resource with ID |
Source code in ofrak/service/resource_service.py
async def get_siblings_by_id(
self,
resource_id: bytes,
max_count: int = -1,
r_filter: Optional[ResourceFilter] = None,
r_sort: Optional[ResourceSort] = None,
) -> Iterable[ResourceModel]:
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
if resource_node.parent is None:
raise NotFoundError(
f"The resource {resource_id.hex()} does not have siblings as it is a root "
f"resource."
)
return await self.get_descendants_by_id(
resource_node.parent.model.id, max_count, 1, r_filter, r_sort
)
update(self, resource_diff)
async
Modify a stored resource model according to the differences in the given diff object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_diff |
ResourceModelDiff |
Diff object containing changes to a resource model, as well as the resource ID of the model to update |
required |
Returns:
Type | Description |
---|---|
ResourceModel |
The updated resource model (with changes applied) |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID matching the ID in |
Source code in ofrak/service/resource_service.py
async def update(self, resource_diff: ResourceModelDiff) -> ResourceModel:
return self._update(resource_diff)
update_many(self, resource_diffs)
async
Modify a stored resource model according to the differences in the given diff object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_diffs |
Iterable[ofrak.model.resource_model.ResourceModelDiff] |
Diff objects containing changes to resource models, as well as the resource ID of each model to update |
required |
Returns:
Type | Description |
---|---|
Iterable[ofrak.model.resource_model.ResourceModel] |
The updated resource models (with changes applied) |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID matching one of the IDs in |
Source code in ofrak/service/resource_service.py
async def update_many(
self, resource_diffs: Iterable[ResourceModelDiff]
) -> Iterable[ResourceModel]:
return [self._update(resource_diff) for resource_diff in resource_diffs]
rebase_resource(self, resource_id, new_parent_id)
async
Move a resource which was a child to instead be a child of a different resource.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_id |
bytes |
resource ID of the resource to rebase |
required |
new_parent_id |
bytes |
resource ID of the new parent resource for |
required |
Exceptions:
Type | Description |
---|---|
NotFoundError |
If there is not a resource with resource ID |
NotFoundError |
If there is not a resource with resource ID |
Source code in ofrak/service/resource_service.py
async def rebase_resource(self, resource_id: bytes, new_parent_id: bytes):
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
raise NotFoundError(f"The resource {resource_id.hex()} does not exist")
new_parent_resource_node = self._resource_store.get(new_parent_id)
if new_parent_resource_node is None:
raise NotFoundError(f"The new parent resource {resource_id.hex()} does not exist")
former_parent_resource_node = resource_node.parent
if former_parent_resource_node is not None:
former_parent_resource_node.remove_child(resource_node)
new_parent_resource_node.add_child(resource_node)
resource_node.parent = new_parent_resource_node
resource_node.model.parent_id = new_parent_id
delete_resource(self, resource_id)
async
Delete a resource by ID and all of its descendants, removing them from the database. If no resource for the given ID is found, it is assumed the resource has already been deleted (does not raise an error).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_id |
bytes |
The ID of the resource to delete |
required |
Returns:
Type | Description |
---|---|
all of the models that were deleted |
Source code in ofrak/service/resource_service.py
async def delete_resource(self, resource_id: bytes):
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
# Already deleted, probably by an ancestor calling the recursive func below
return []
former_parent_resource_node = resource_node.parent
if former_parent_resource_node is not None:
former_parent_resource_node.remove_child(resource_node)
deleted_models = self._delete_resource_helper(resource_node)
LOGGER.debug(f"Deleted {resource_id.hex()}")
return deleted_models
delete_resources(self, resource_ids)
async
Delete multiple resources by ID and all of their descendants, removing them from the database. If no resource for any given ID is found, it is assumed the resource has already been deleted (does not raise an error).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource_ids |
Iterable[bytes] |
The ID of the resources to delete |
required |
Returns:
Type | Description |
---|---|
all of the models that were deleted |
Source code in ofrak/service/resource_service.py
async def delete_resources(self, resource_ids: Iterable[bytes]):
deleted_models = []
for resource_id in resource_ids:
resource_node = self._resource_store.get(resource_id)
if resource_node is None:
# Already deleted, probably by an ancestor calling the recursive func below
continue
former_parent_resource_node = resource_node.parent
if former_parent_resource_node is not None:
former_parent_resource_node.remove_child(resource_node)
deleted_models.extend(self._delete_resource_helper(resource_node))
if resource_ids:
LOGGER.debug(f"Deleted {', '.join(resource_id.hex() for resource_id in resource_ids)}")
return deleted_models