resource_model.py
ofrak.model.resource_model
Data (ResourceAttributes)
dataclass
Special attributes class for accessing info about a resource's binary data. Users should never access or modify this directly! Changing the fields of this data structure will not change any about the resource's data! At best, it will do nothing, and at worst it will screw up sorting/filtering.
MutableResourceModel (ResourceModel)
__hash__(self)
special
Return hash(self).
Source code in ofrak/model/resource_model.py
def __hash__(self):
return self.id.__hash__()
add_component(self, component_id, version)
Mark that a component has run on a resource.
Source code in ofrak/model/resource_model.py
def add_component(
self,
component_id: bytes,
version: int,
):
"""
Mark that a component has run on a resource.
"""
self.is_modified = True
self.component_versions[component_id] = version
self.diff.component_versions_added.add((component_id, version))
add_component_for_attributes(self, component_id, version, attribute_type)
Mark that a component has added attributes to a resource
Source code in ofrak/model/resource_model.py
def add_component_for_attributes(
self,
component_id: bytes,
version: int,
attribute_type: Type[ResourceAttributes],
):
"""
Mark that a component has added attributes to a resource
"""
self.components_by_attributes[attribute_type] = (component_id, version)
self.diff.attributes_component_added.add((attribute_type, component_id, version))
ResourceAttributeDependency
__init__(self, dependent_resource_id, component_id, attributes)
special
Create a dependency that says the resource dependent_resource_id
has some attributes
attributes
(added by component_id
) which depend on some information in another
resource. That information is either a range of the data or some attributes of that other
resource.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
dependent_resource_id |
bytes |
ID of the resource which has a dependency |
required |
component_id |
bytes |
Type of attributes on the resource which has a dependency |
required |
attributes |
Type[ofrak.model.resource_model.ResourceAttributes] |
Component which ran on the resource to create the attributes |
required |
Source code in ofrak/model/resource_model.py
def __init__(
self,
dependent_resource_id: bytes,
component_id: bytes,
attributes: Type[ResourceAttributes],
):
"""
Create a dependency that says the resource ``dependent_resource_id`` has some attributes
``attributes`` (added by ``component_id``) which depend on some information in another
resource. That information is either a range of the data or some attributes of that other
resource.
:param dependent_resource_id: ID of the resource which has a dependency
:param component_id: Type of attributes on the resource which has a dependency
:param attributes: Component which ran on the resource to create the attributes
"""
self.dependent_resource_id = dependent_resource_id
self.component_id = component_id
self.attributes = attributes
ResourceAttributes
DATACLASS_PARAMS
Wraps immutable attributes attached to a resource. While not enforced programmatically, only analyzers should add/replace attributes attached to a resource. Additionally, a ResourceAttributes instance also defines which component attached the attributes to a specific resource.
replace_updated(resource_attributes, updated_attributes)
staticmethod
Replace the fields of resource_attributes
with the updated values found in
updated_attributes
, returning a new object. The fields having non-None
values in
updated_attributes
are considered to be updated and will be replaced in
resource_attributes
if they exist there.
Both arguments must be dataclass
instances.
updated_attributes
is typically a descendant of
ComponentConfig.
To do
This currently assumes that values can't be updated to None
, but that could happen.
Exceptions:
Type | Description |
---|---|
TypeError |
if any of |
Source code in ofrak/model/resource_model.py
@staticmethod
def replace_updated(resource_attributes: RA, updated_attributes: Any) -> RA:
"""
Replace the fields of `resource_attributes` with the updated values found in
`updated_attributes`, returning a new object. The fields having non-`None` values in
`updated_attributes` are considered to be updated and will be replaced in
`resource_attributes` if they exist there.
Both arguments must be `dataclass` instances.
`updated_attributes` is typically a descendant of
[ComponentConfig][ofrak.model.component_model.ComponentConfig].
!!! todo "To do"
This currently assumes that values can't be updated to `None`, but that could happen.
:raises TypeError: if any of `resource_attributes` or `updated_attributes` isn't a
dataclass instance.
"""
for obj in (resource_attributes, updated_attributes):
if not (dataclasses.is_dataclass(obj) and not isinstance(obj, type)):
raise TypeError(f"{obj.__name__} must be a dataclass instance")
updated_fields = {
field: val
for field, val in dataclasses.asdict(updated_attributes).items()
if val is not None
}
updated_attributes = dataclasses.replace(
resource_attributes,
**updated_fields,
)
return updated_attributes
ResourceContext (ABC)
Resource context.
ResourceIndexedAttribute (Generic)
Descriptor class for values in resource attributes which can be indexed. When a field Foo
of a ResourceAttributes type A
is indexed,
it is possible to include an r_filter
or r_sort
in a query to the resource service which
filters the returned resource by the value of foo
each of them have.
This class should not be explicitly instantiated, instead created using the @index decorator.
For example:
@dataclass
class A(ResourceAttributes):
x: int
@index
def Foo(self) -> int:
return self.x
__init__(self, getter_func, uses_indexes=())
special
Parameters:
Name | Type | Description | Default |
---|---|---|---|
getter_func |
Callable[[Any], ~X] |
Getter function for the property |
required |
uses_indexes |
Iterable[ResourceIndexedAttribute] |
Additional index types that are required to calculate the value of this index |
() |
Exceptions:
Type | Description |
---|---|
TypeError |
if the getter function does not have a return type annotation |
TypeError |
if the getter does not return an indexable type |
Source code in ofrak/model/resource_model.py
def __init__(
self,
getter_func: Callable[[Any], X],
uses_indexes: Iterable["ResourceIndexedAttribute"] = (),
):
"""
:param getter_func: Getter function for the property
:param uses_indexes: Additional index types that are required to calculate the value
of this index
:raises TypeError: if the getter function does not have a return type annotation
:raises TypeError: if the getter does not return an indexable type
"""
_validate_indexed_type(getter_func)
self.fget: Callable[[Any], X] = getter_func
self.attributes_owner: Optional[Type[ResourceAttributes]] = None
self.uses_indexes = uses_indexes
self.used_by_indexes: List["ResourceIndexedAttribute"] = []
self.index_name: str = getter_func.__name__
for other_index in self.uses_indexes:
other_index.used_by_indexes.append(self)
ResourceModel
Parameters:
Name | Type | Description | Default |
---|---|---|---|
id |
ModelIdType |
required | |
data_id |
ModelDataIdType |
required | |
parent_id |
ModelParentIdType |
required | |
tags |
Optional[Set[ResourceTag]] |
required | |
attributes |
Optional[ModelAttributesType] |
required | |
data_dependencies |
Optional[ModelAttributesType] |
Stores the dependencies of other resources on specific data ranges within this resource |
required |
attribute_dependencies |
Optional[ModelAttributeDependenciesType] |
Stores the dependencies of other resources on ResourceAttributes of this resource |
required |
component_versions |
Optional[ModelComponentVersionsType] |
Stores the version of ComponentInterface which has been run on this resource |
required |
components_by_attributes |
Optional[ModelComponentsByAttributesType] |
For each ResourceAttributes, stores the id of the ComponentInterface which was run to create it |
required |
index(index_value_getter=None, *, uses_indexes=())
Create a new indexable attribute for a ResourceAttributes.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
index_value_getter |
Callable[[Any], ~X] |
Method of ResourceAttributes which returns the value of the index for that instance. |
None |
uses_indexes |
Iterable[ofrak.model.resource_model.ResourceIndexedAttribute] |
Additional index types that are required to calculate the value of this index. |
() |
Returns:
Type | Description |
---|---|
Union[Callable[[Callable[[Any], ~X]], ofrak.model.resource_model.ResourceIndexedAttribute[~X]], ofrak.model.resource_model.ResourceIndexedAttribute[~X]] |
ResourceIndexedAttribute instance |
Source code in ofrak/model/resource_model.py
def index(
index_value_getter: Callable[[Any], X] = None,
*,
uses_indexes: Iterable[ResourceIndexedAttribute] = (),
) -> Union[
Callable[[Callable[[Any], X]], ResourceIndexedAttribute[X]], ResourceIndexedAttribute[X]
]:
"""
Create a new indexable attribute for a
[ResourceAttributes][ofrak.model.resource_model.ResourceAttributes].
:param index_value_getter: Method of
[ResourceAttributes][ofrak.model.resource_model.ResourceAttributes] which returns the
value of the index for that instance.
:param uses_indexes: Additional index types that are required to calculate the value
of this index.
:return: [ResourceIndexedAttribute][ofrak.model.resource_model.ResourceIndexedAttribute]
instance
"""
# See if we're being called as @index or @index().
if index_value_getter is None:
# We're called with parens.
def wrap(_index_value_getter) -> ResourceIndexedAttribute[X]:
return ResourceIndexedAttribute[X](_index_value_getter, uses_indexes)
return wrap # type: ignore
# We're called as @index without parens.
return ResourceIndexedAttribute[X](index_value_getter)
_validate_indexed_type(getter_func)
private
Verify the getter function returns a valid indexable type - a primitive type which can be compared.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
getter_func |
Callable[[Any], ~X] |
required |
Returns:
Type | Description |
---|---|
Exceptions:
Type | Description |
---|---|
TypeError |
if the getter function does not have a return type annotation |
TypeError |
if the getter does not return an indexable type |
Source code in ofrak/model/resource_model.py
def _validate_indexed_type(getter_func: Callable[[Any], X]):
"""
Verify the getter function returns a valid indexable type - a primitive type which can be
compared.
:param getter_func:
:raises TypeError: if the getter function does not have a return type annotation
:raises TypeError: if the getter does not return an indexable type
:return:
"""
if not hasattr(getter_func, "__annotations__"):
raise TypeError(
f"Index {getter_func.__name__} must have type annotations, including return type"
)
annotations = getattr(getter_func, "__annotations__")
if "return" not in annotations:
raise TypeError(
f"Index {getter_func.__name__} must have type annotations, including return type"
)
index_type = annotations["return"]
if type(index_type) is str:
# handles case where type annotations is "stringified" and not the actual type, e.g.
# def foo(self) -> "int": ...
index_type_name = index_type
else:
index_type_name = index_type.__name__
if index_type_name not in _INDEXABLE_TYPES:
raise TypeError(
f"Type of index {getter_func.__name__} is {index_type}, which is not "
f"one of {_INDEXABLE_TYPES.values()}; cannot index by this value!"
)