diff --git a/rsconcept/backend/apps/library/views/library.py b/rsconcept/backend/apps/library/views/library.py index 8142771f..ef874607 100644 --- a/rsconcept/backend/apps/library/views/library.py +++ b/rsconcept/backend/apps/library/views/library.py @@ -67,7 +67,7 @@ class LibraryViewSet(viewsets.ModelViewSet): def perform_destroy(self, instance: m.LibraryItem) -> None: if instance.item_type == m.LibraryItemType.RSFORM: - PropagationFacade.before_delete_schema(instance.pk) + PropagationFacade().before_delete_schema(instance.pk) super().perform_destroy(instance) if instance.item_type == m.LibraryItemType.OPERATION_SCHEMA: schemas = list(OperationSchema.owned_schemasQ(instance)) diff --git a/rsconcept/backend/apps/oss/models/OperationSchema.py b/rsconcept/backend/apps/oss/models/OperationSchema.py index 924d8d3e..60ae579e 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchema.py +++ b/rsconcept/backend/apps/oss/models/OperationSchema.py @@ -18,7 +18,7 @@ from .Substitution import Substitution class OperationSchema: ''' Operations schema API wrapper. No caching, propagation and minimal side effects. ''' - def __init__(self, model: LibraryItem): + def __init__(self, model: LibraryItem) -> None: self.model = model @staticmethod diff --git a/rsconcept/backend/apps/oss/models/OperationSchemaCached.py b/rsconcept/backend/apps/oss/models/OperationSchemaCached.py index ca53cdf0..0eb4f0a2 100644 --- a/rsconcept/backend/apps/oss/models/OperationSchemaCached.py +++ b/rsconcept/backend/apps/oss/models/OperationSchemaCached.py @@ -11,6 +11,7 @@ from .Inheritance import Inheritance from .Operation import Operation from .OperationSchema import OperationSchema from .OssCache import OssCache +from .PropagationContext import PropagationContext from .PropagationEngine import PropagationEngine from .Substitution import Substitution from .utils import CstMapping, CstSubstitution, create_dependant_mapping, extract_data_references @@ -19,10 +20,11 @@ from .utils import CstMapping, CstSubstitution, create_dependant_mapping, extrac class OperationSchemaCached: ''' Operations schema API with caching. ''' - def __init__(self, item_id: int): + def __init__(self, item_id: int, context: PropagationContext) -> None: self.pk = item_id - self.cache = OssCache(item_id) - self.engine = PropagationEngine(self.cache) + self.context = context + self.cache = OssCache(item_id, context) + self.engine = PropagationEngine(self.cache, context) def delete_replica(self, target: int, keep_connections: bool = False, keep_constituents: bool = False): ''' Delete Replica Operation. ''' @@ -78,7 +80,7 @@ class OperationSchemaCached: if old_schema is not None: if has_children: self.before_delete_cst(old_schema.pk, [cst.pk for cst in old_schema.cache.constituents]) - self.cache.remove_schema(old_schema) + self.context.invalidate(old_schema.pk) operation.setQ_result(schema) if schema is not None: @@ -88,7 +90,7 @@ class OperationSchemaCached: operation.save(update_fields=['alias', 'title', 'description']) if schema is not None and has_children: - rsform = RSFormCached(schema.pk) + rsform = self.context.get_schema(schema.pk) self.after_create_cst(rsform, list(rsform.constituentsQ().order_by('order'))) def set_arguments(self, target: int, arguments: list[Operation]) -> None: @@ -173,8 +175,7 @@ class OperationSchemaCached: return False substitutions = operation.getQ_substitutions() new_schema = OperationSchema.create_input(self.pk, self.cache.operation_by_id[operation.pk]) - receiver = RSFormCached(new_schema.pk) - self.cache.insert_schema(receiver) + receiver = self.context.get_schema(new_schema.pk) parents: dict = {} children: dict = {} @@ -209,11 +210,10 @@ class OperationSchemaCached: self.after_create_cst(receiver, receiver_items) return True - def relocate_down(self, source: RSFormCached, destination: RSFormCached, items: list[int]): + def relocate_down(self, destination: RSFormCached, items: list[int]): ''' Move list of Constituents to destination Schema inheritor. ''' self.cache.ensure_loaded_subs() - self.cache.insert_schema(source) - self.cache.insert_schema(destination) + operation = self.cache.get_operation(destination.pk) self.engine.undo_substitutions_cst(items, operation, destination) inheritance_to_delete = [item for item in self.cache.inheritance[operation.pk] if item.parent_id in items] @@ -225,8 +225,6 @@ class OperationSchemaCached: item_ids: list[int]) -> list[Constituenta]: ''' Move list of Constituents upstream to destination Schema. ''' self.cache.ensure_loaded_subs() - self.cache.insert_schema(source) - self.cache.insert_schema(destination) operation = self.cache.get_operation(source.pk) alias_mapping: dict[str, str] = {} @@ -254,7 +252,6 @@ class OperationSchemaCached: exclude: Optional[list[int]] = None ) -> None: ''' Trigger cascade resolutions when new Constituenta is created. ''' - self.cache.insert_schema(source) alias_mapping = create_dependant_mapping(source, cst_list) operation = self.cache.get_operation(source.pk) self.engine.on_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude) @@ -266,7 +263,6 @@ class OperationSchemaCached: def after_update_cst(self, source: RSFormCached, target: int, data: dict, old_data: dict) -> None: ''' Trigger cascade resolutions when Constituenta data is changed. ''' - self.cache.insert_schema(source) operation = self.cache.get_operation(source.pk) depend_aliases = extract_data_references(data, old_data) alias_mapping: CstMapping = {} diff --git a/rsconcept/backend/apps/oss/models/OssCache.py b/rsconcept/backend/apps/oss/models/OssCache.py index 1113d794..78e9303d 100644 --- a/rsconcept/backend/apps/oss/models/OssCache.py +++ b/rsconcept/backend/apps/oss/models/OssCache.py @@ -8,6 +8,7 @@ from apps.rsform.models import RSFormCached from .Argument import Argument from .Inheritance import Inheritance from .Operation import Operation, OperationType +from .PropagationContext import PropagationContext from .Replica import Replica from .Substitution import Substitution @@ -15,10 +16,9 @@ from .Substitution import Substitution class OssCache: ''' Cache for OSS data. ''' - def __init__(self, item_id: int): + def __init__(self, item_id: int, context: PropagationContext) -> None: self._item_id = item_id - self._schemas: list[RSFormCached] = [] - self._schema_by_id: dict[int, RSFormCached] = {} + self._context = context self.operations = list(Operation.objects.filter(oss_id=item_id).only('result_id', 'operation_type')) self.operation_by_id = {operation.pk: operation for operation in self.operations} @@ -64,23 +64,7 @@ class OssCache: ''' Get schema by Operation. ''' if operation.result_id is None: return None - if operation.result_id in self._schema_by_id: - return self._schema_by_id[operation.result_id] - else: - schema = RSFormCached(operation.result_id) - schema.cache.ensure_loaded() - self._insert_new(schema) - return schema - - def get_schema_by_id(self, target: int) -> RSFormCached: - ''' Get schema by Operation. ''' - if target in self._schema_by_id: - return self._schema_by_id[target] - else: - schema = RSFormCached(target) - schema.cache.ensure_loaded() - self._insert_new(schema) - return schema + return self._context.get_schema(operation.result_id) def get_operation(self, schemaID: int) -> Operation: ''' Get operation by schema. ''' @@ -111,12 +95,6 @@ class OssCache: return self.get_inheritor(sub.substitution_id, operation) return self.get_inheritor(parent_cst, operation) - def insert_schema(self, schema: RSFormCached) -> None: - ''' Insert new schema. ''' - if not self._schema_by_id.get(schema.pk): - schema.cache.ensure_loaded() - self._insert_new(schema) - def insert_argument(self, argument: Argument) -> None: ''' Insert new argument. ''' self.graph.add_edge(argument.argument_id, argument.operation_id) @@ -145,19 +123,12 @@ class OssCache: for item in inherit_to_delete: self.inheritance[operation].remove(item) - def remove_schema(self, schema: RSFormCached) -> None: - ''' Remove schema from cache. ''' - self._schemas.remove(schema) - del self._schema_by_id[schema.pk] - def remove_operation(self, operation: int) -> None: ''' Remove operation from cache. ''' target = self.operation_by_id[operation] self.graph.remove_node(operation) self.extend_graph.remove_node(operation) - if target.result_id in self._schema_by_id: - self._schemas.remove(self._schema_by_id[target.result_id]) - del self._schema_by_id[target.result_id] + self._context.invalidate(target.result_id) self.operations.remove(self.operation_by_id[operation]) del self.operation_by_id[operation] if operation in self.replica_original: @@ -182,7 +153,3 @@ class OssCache: def remove_inheritance(self, target: Inheritance) -> None: ''' Remove inheritance from cache. ''' self.inheritance[target.operation_id].remove(target) - - def _insert_new(self, schema: RSFormCached) -> None: - self._schemas.append(schema) - self._schema_by_id[schema.pk] = schema diff --git a/rsconcept/backend/apps/oss/models/PropagationContext.py b/rsconcept/backend/apps/oss/models/PropagationContext.py new file mode 100644 index 00000000..c6828ee3 --- /dev/null +++ b/rsconcept/backend/apps/oss/models/PropagationContext.py @@ -0,0 +1,27 @@ +''' Models: Propagation context. ''' + +from apps.rsform.models import RSFormCached + + +class PropagationContext: + ''' Propagation context. ''' + + def __init__(self) -> None: + self._cache: dict[int, RSFormCached] = {} + + def get_schema(self, item_id: int) -> RSFormCached: + ''' Get schema by ID. ''' + if item_id not in self._cache: + schema = RSFormCached(item_id) + schema.cache.ensure_loaded() + self._cache[item_id] = schema + return self._cache[item_id] + + def clear(self) -> None: + ''' Clear cache. ''' + self._cache = {} + + def invalidate(self, item_id: int | None) -> None: + ''' Invalidate schema by ID. ''' + if item_id in self._cache: + del self._cache[item_id] diff --git a/rsconcept/backend/apps/oss/models/PropagationEngine.py b/rsconcept/backend/apps/oss/models/PropagationEngine.py index 38aad4e0..a82322c2 100644 --- a/rsconcept/backend/apps/oss/models/PropagationEngine.py +++ b/rsconcept/backend/apps/oss/models/PropagationEngine.py @@ -8,6 +8,7 @@ from apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached from .Inheritance import Inheritance from .Operation import Operation from .OssCache import OssCache +from .PropagationContext import PropagationContext from .Substitution import Substitution from .utils import ( CstMapping, @@ -21,8 +22,9 @@ from .utils import ( class PropagationEngine: ''' OSS changes propagation engine. ''' - def __init__(self, cache: OssCache): + def __init__(self, cache: OssCache, context: PropagationContext) -> None: self.cache = cache + self.context = context def on_change_cst_type(self, operation_id: int, cst_id: int, ctype: CstType) -> None: ''' Trigger cascade resolutions when Constituenta type is changed. ''' @@ -213,26 +215,26 @@ class PropagationEngine: self.on_delete_attribution(child_id, deleted) Attribution.objects.filter(pk__in=[attrib.pk for attrib in deleted]).delete() - def on_delete_inherited(self, operation: int, target: list[int]) -> None: + def on_delete_inherited(self, operationID: int, target: list[int]) -> None: ''' Trigger cascade resolutions when Constituenta inheritance is deleted. ''' - children = self.cache.extend_graph.outputs[operation] + children = self.cache.extend_graph.outputs[operationID] if not children: return self.cache.ensure_loaded_subs() for child_id in children: self.delete_inherited(child_id, target) - def delete_inherited(self, operation_id: int, parent_ids: list[int]) -> None: + def delete_inherited(self, operationID: int, parents: list[int]) -> None: ''' Execute deletion of Constituenta inheritance. ''' - operation = self.cache.operation_by_id[operation_id] + operation = self.cache.operation_by_id[operationID] schema = self.cache.get_schema(operation) if schema is None: return - self.undo_substitutions_cst(parent_ids, operation, schema) - target_ids = self.cache.get_inheritors_list(parent_ids, operation_id) - self.on_delete_inherited(operation_id, target_ids) + self.undo_substitutions_cst(parents, operation, schema) + target_ids = self.cache.get_inheritors_list(parents, operationID) + self.on_delete_inherited(operationID, target_ids) if target_ids: - self.cache.remove_cst(operation_id, target_ids) + self.cache.remove_cst(operationID, target_ids) schema.delete_cst(target_ids) def undo_substitutions_cst(self, target_ids: list[int], operation: Operation, schema: RSFormCached) -> None: @@ -254,7 +256,7 @@ class PropagationEngine: if ignore_parents is None: ignore_parents = [] operation_id = target.operation_id - original_schema = self.cache.get_schema_by_id(target.original.schema_id) + original_schema = self.context.get_schema(target.original.schema_id) dependant = [] for cst_id in original_schema.get_dependant([target.original_id]): if cst_id not in ignore_parents: diff --git a/rsconcept/backend/apps/oss/models/PropagationFacade.py b/rsconcept/backend/apps/oss/models/PropagationFacade.py index bba5b2d8..60aa7535 100644 --- a/rsconcept/backend/apps/oss/models/PropagationFacade.py +++ b/rsconcept/backend/apps/oss/models/PropagationFacade.py @@ -5,6 +5,7 @@ from apps.library.models import LibraryItem from apps.rsform.models import Attribution, Constituenta, CstType, RSFormCached from .OperationSchemaCached import CstSubstitution, OperationSchemaCached +from .PropagationContext import PropagationContext def _get_oss_hosts(schemaID: int) -> list[int]: @@ -15,63 +16,61 @@ def _get_oss_hosts(schemaID: int) -> list[int]: class PropagationFacade: ''' Change propagation API. ''' - _oss: dict[int, OperationSchemaCached] = {} + def __init__(self) -> None: + self._context = PropagationContext() + self._oss: dict[int, OperationSchemaCached] = {} - @staticmethod - def get_oss(schemaID: int) -> OperationSchemaCached: + def get_oss(self, schemaID: int) -> OperationSchemaCached: ''' Get OperationSchemaCached for schemaID. ''' - if schemaID not in PropagationFacade._oss: - PropagationFacade._oss[schemaID] = OperationSchemaCached(schemaID) - return PropagationFacade._oss[schemaID] + if schemaID not in self._oss: + self._oss[schemaID] = OperationSchemaCached(schemaID, self._context) + return self._oss[schemaID] - @staticmethod - def reset_cache() -> None: + def get_schema(self, schemaID: int) -> RSFormCached: + ''' Get RSFormCached for schemaID. ''' + return self._context.get_schema(schemaID) + + def reset_cache(self) -> None: ''' Reset cache. ''' - PropagationFacade._oss = {} + self._oss = {} - @staticmethod - def after_create_cst(source: RSFormCached, new_cst: list[Constituenta], + def after_create_cst(self, source: RSFormCached, new_cst: list[Constituenta], exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions when new constituenta is created. ''' hosts = _get_oss_hosts(source.pk) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).after_create_cst(source, new_cst) + self.get_oss(host).after_create_cst(source, new_cst) - @staticmethod - def after_change_cst_type(sourceID: int, target: int, new_type: CstType, + def after_change_cst_type(self, sourceID: int, target: int, new_type: CstType, exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions when constituenta type is changed. ''' hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).after_change_cst_type(sourceID, target, new_type) + self.get_oss(host).after_change_cst_type(sourceID, target, new_type) - @staticmethod + # pylint: disable=too-many-arguments, too-many-positional-arguments def after_update_cst( - source: RSFormCached, - target: int, - data: dict, - old_data: dict, + self, source: RSFormCached, target: int, + data: dict, old_data: dict, exclude: Optional[list[int]] = None ) -> None: ''' Trigger cascade resolutions when constituenta data is changed. ''' hosts = _get_oss_hosts(source.pk) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).after_update_cst(source, target, data, old_data) + self.get_oss(host).after_update_cst(source, target, data, old_data) - @staticmethod - def before_delete_cst(sourceID: int, target: list[int], + def before_delete_cst(self, sourceID: int, target: list[int], exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions before constituents are deleted. ''' hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).before_delete_cst(sourceID, target) + self.get_oss(host).before_delete_cst(sourceID, target) - @staticmethod - def before_substitute(sourceID: int, substitutions: CstSubstitution, + def before_substitute(self, sourceID: int, substitutions: CstSubstitution, exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions before constituents are substituted. ''' if not substitutions: @@ -79,10 +78,9 @@ class PropagationFacade: hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).before_substitute(sourceID, substitutions) + self.get_oss(host).before_substitute(sourceID, substitutions) - @staticmethod - def before_delete_schema(target: int, exclude: Optional[list[int]] = None) -> None: + def before_delete_schema(self, target: int, exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions before schema is deleted. ''' hosts = _get_oss_hosts(target) if not hosts: @@ -91,25 +89,23 @@ class PropagationFacade: ids = list(Constituenta.objects.filter(schema_id=target).order_by('order').values_list('pk', flat=True)) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).before_delete_cst(target, ids) - del PropagationFacade._oss[host] + self.get_oss(host).before_delete_cst(target, ids) + del self._oss[host] - @staticmethod - def after_create_attribution(sourceID: int, + def after_create_attribution(self, sourceID: int, attributions: list[Attribution], exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions when Attribution is created. ''' hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).after_create_attribution(sourceID, attributions) + self.get_oss(host).after_create_attribution(sourceID, attributions) - @staticmethod - def before_delete_attribution(sourceID: int, + def before_delete_attribution(self, sourceID: int, attributions: list[Attribution], exclude: Optional[list[int]] = None) -> None: ''' Trigger cascade resolutions before Attribution is deleted. ''' hosts = _get_oss_hosts(sourceID) for host in hosts: if exclude is None or host not in exclude: - PropagationFacade.get_oss(host).before_delete_attribution(sourceID, attributions) + self.get_oss(host).before_delete_attribution(sourceID, attributions) diff --git a/rsconcept/backend/apps/oss/models/__init__.py b/rsconcept/backend/apps/oss/models/__init__.py index 49e3300d..96a35b96 100644 --- a/rsconcept/backend/apps/oss/models/__init__.py +++ b/rsconcept/backend/apps/oss/models/__init__.py @@ -7,6 +7,7 @@ from .Layout import Layout from .Operation import Operation, OperationType from .OperationSchema import OperationSchema from .OperationSchemaCached import OperationSchemaCached +from .PropagationContext import PropagationContext from .PropagationFacade import PropagationFacade from .Replica import Replica from .Substitution import Substitution diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py index 521e09e0..ffa8ff05 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_attributes.py @@ -1,6 +1,6 @@ ''' Testing API: Change attributes of OSS and RSForms. ''' from apps.library.models import AccessPolicy, Editor, LibraryItem, LocationHead -from apps.oss.models import OperationSchema, OperationType, PropagationFacade +from apps.oss.models import OperationSchema, OperationType from apps.rsform.models import RSForm from apps.users.models import User from shared.EndpointTester import EndpointTester, decl_endpoint @@ -11,7 +11,6 @@ class TestChangeAttributes(EndpointTester): def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.user3 = User.objects.create( username='UserTest3', diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py index 7712b4e3..79996245 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_constituents.py @@ -1,6 +1,6 @@ ''' Testing API: Change constituents in OSS. ''' -from apps.oss.models import OperationSchema, OperationType, PropagationFacade +from apps.oss.models import OperationSchema, OperationType from apps.rsform.models import Attribution, Constituenta, CstType, RSForm from shared.EndpointTester import EndpointTester, decl_endpoint @@ -9,7 +9,6 @@ class TestChangeConstituents(EndpointTester): ''' Testing Constituents change propagation in OSS. ''' def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create( title='Test', diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py index ff94ef38..326fe829 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py @@ -1,6 +1,6 @@ ''' Testing API: Change substitutions in OSS. ''' -from apps.oss.models import OperationSchema, OperationType, PropagationFacade +from apps.oss.models import OperationSchema, OperationType from apps.rsform.models import Constituenta, RSForm from shared.EndpointTester import EndpointTester, decl_endpoint @@ -9,7 +9,6 @@ class TestChangeOperations(EndpointTester): ''' Testing Operations change propagation in OSS. ''' def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create( title='Test', diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_references.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_references.py index fac912d0..ecca7077 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_references.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_references.py @@ -1,6 +1,6 @@ ''' Testing API: Propagate changes through references in OSS. ''' -from apps.oss.models import Inheritance, OperationSchema, OperationType, PropagationFacade +from apps.oss.models import Inheritance, OperationSchema, OperationType from apps.rsform.models import Constituenta, CstType, RSForm from shared.EndpointTester import EndpointTester, decl_endpoint @@ -9,7 +9,6 @@ class ReferencePropagationTestCase(EndpointTester): ''' Test propagation through references in OSS. ''' def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create( diff --git a/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py b/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py index 6d4f65e2..247ef204 100644 --- a/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py +++ b/rsconcept/backend/apps/oss/tests/s_propagation/t_substitutions.py @@ -1,6 +1,6 @@ ''' Testing API: Change substitutions in OSS. ''' -from apps.oss.models import OperationSchema, OperationType, PropagationFacade +from apps.oss.models import OperationSchema, OperationType from apps.rsform.models import Constituenta, RSForm from shared.EndpointTester import EndpointTester, decl_endpoint @@ -10,7 +10,6 @@ class TestChangeSubstitutions(EndpointTester): def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create( title='Test', diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py b/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py index 86486bb8..609ee6e9 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_blocks.py @@ -1,5 +1,5 @@ ''' Testing API: Operation Schema - blocks manipulation. ''' -from apps.oss.models import OperationSchema, OperationType, PropagationFacade +from apps.oss.models import OperationSchema, OperationType from shared.EndpointTester import EndpointTester, decl_endpoint @@ -8,7 +8,6 @@ class TestOssBlocks(EndpointTester): def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) self.owned_id = self.owned.model.pk diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py index 62cc49dd..a82ae6ba 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_operations.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_operations.py @@ -5,7 +5,6 @@ from apps.oss.models import ( Operation, OperationSchema, OperationType, - PropagationFacade, Replica ) from apps.rsform.models import Attribution, RSForm @@ -17,7 +16,6 @@ class TestOssOperations(EndpointTester): def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) self.owned_id = self.owned.model.pk diff --git a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py index 386cd881..23d60f21 100644 --- a/rsconcept/backend/apps/oss/tests/s_views/t_oss.py +++ b/rsconcept/backend/apps/oss/tests/s_views/t_oss.py @@ -1,6 +1,6 @@ ''' Testing API: Operation Schema. ''' from apps.library.models import AccessPolicy, LibraryItemType -from apps.oss.models import OperationSchema, OperationType, PropagationFacade +from apps.oss.models import OperationSchema, OperationType from apps.rsform.models import Constituenta, RSForm from shared.EndpointTester import EndpointTester, decl_endpoint @@ -9,7 +9,6 @@ class TestOssViewset(EndpointTester): ''' Testing OSS view. ''' def setUp(self): - PropagationFacade.reset_cache() super().setUp() self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user) self.owned_id = self.owned.model.pk diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index 01f5382e..b6b200ea 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -14,8 +14,7 @@ from rest_framework.response import Response from apps.library.models import LibraryItem, LibraryItemType from apps.library.serializers import LibraryItemSerializer -from apps.oss.models import PropagationFacade -from apps.rsform.models import Constituenta, RSFormCached +from apps.rsform.models import Constituenta from apps.rsform.serializers import CstTargetSerializer from shared import messages as msg from shared import permissions @@ -421,7 +420,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev schema_clone.access_policy = item.access_policy schema_clone.location = item.location schema_clone.save() - RSFormCached(schema_clone.pk).insert_from(prototype.pk) + + m.PropagationFacade().get_schema(schema_clone.pk).insert_from(prototype.pk) new_operation.result = schema_clone new_operation.save(update_fields=["result"]) @@ -545,7 +545,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev operation: m.Operation = cast(m.Operation, serializer.validated_data['target']) with transaction.atomic(): - oss = PropagationFacade.get_oss(item.pk) + propagation = m.PropagationFacade() + oss = propagation.get_oss(item.pk) if 'layout' in serializer.validated_data: layout = serializer.validated_data['layout'] m.Layout.update_data(pk, layout) @@ -600,12 +601,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)] with transaction.atomic(): - oss = PropagationFacade.get_oss(item.pk) + propagation = m.PropagationFacade() + oss = propagation.get_oss(item.pk) oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) m.Layout.update_data(pk, layout) if old_schema is not None: if serializer.validated_data['delete_schema']: - m.PropagationFacade.before_delete_schema(old_schema.pk) + propagation.before_delete_schema(old_schema.pk) old_schema.delete() elif old_schema.is_synced(item): old_schema.visible = True @@ -641,7 +643,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)] with transaction.atomic(): - oss = PropagationFacade.get_oss(item.pk) + propagation = m.PropagationFacade() + oss = propagation.get_oss(item.pk) m.Layout.update_data(pk, layout) oss.delete_replica(operation.pk, keep_connections, keep_constituents) item.save(update_fields=['time_update']) @@ -727,7 +730,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev old_schema = target_operation.result with transaction.atomic(): - oss = PropagationFacade.get_oss(item.pk) + propagation = m.PropagationFacade() + oss = propagation.get_oss(item.pk) if old_schema is not None: if old_schema.is_synced(item): old_schema.visible = True @@ -770,7 +774,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev layout = serializer.validated_data['layout'] with transaction.atomic(): - oss = PropagationFacade.get_oss(item.pk) + propagation = m.PropagationFacade() + oss = propagation.get_oss(item.pk) oss.execute_operation(operation) m.Layout.update_data(pk, layout) item.save(update_fields=['time_update']) @@ -834,16 +839,17 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev ids = [cst.pk for cst in data['items']] with transaction.atomic(): - oss = PropagationFacade.get_oss(item.pk) - source = RSFormCached(data['source']) - destination = RSFormCached(data['destination']) + propagation = m.PropagationFacade() + oss = propagation.get_oss(item.pk) + source = propagation.get_schema(data['source']) + destination = propagation.get_schema(data['destination']) if data['move_down']: - oss.relocate_down(source, destination, ids) - m.PropagationFacade.before_delete_cst(source.pk, ids) + oss.relocate_down(destination, ids) + propagation.before_delete_cst(source.pk, ids) source.delete_cst(ids) else: new_items = oss.relocate_up(source, destination, ids) - m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.pk]) + propagation.after_create_cst(destination, new_items, exclude=[oss.pk]) item.save(update_fields=['time_update']) return Response(status=c.HTTP_200_OK) diff --git a/rsconcept/backend/apps/rsform/graph.py b/rsconcept/backend/apps/rsform/graph.py index c379b3ad..1d755087 100644 --- a/rsconcept/backend/apps/rsform/graph.py +++ b/rsconcept/backend/apps/rsform/graph.py @@ -8,7 +8,7 @@ ItemType = TypeVar("ItemType") class Graph(Generic[ItemType]): ''' Directed graph. ''' - def __init__(self, graph: Optional[dict[ItemType, list[ItemType]]] = None): + def __init__(self, graph: Optional[dict[ItemType, list[ItemType]]] = None) -> None: if graph is None: self.outputs: dict[ItemType, list[ItemType]] = {} self.inputs: dict[ItemType, list[ItemType]] = {} diff --git a/rsconcept/backend/apps/rsform/models/OrderManager.py b/rsconcept/backend/apps/rsform/models/OrderManager.py index 61c6bab0..b85ccc14 100644 --- a/rsconcept/backend/apps/rsform/models/OrderManager.py +++ b/rsconcept/backend/apps/rsform/models/OrderManager.py @@ -8,7 +8,7 @@ from .SemanticInfo import SemanticInfo class OrderManager: ''' Ordering helper class ''' - def __init__(self, schema: RSFormCached): + def __init__(self, schema: RSFormCached) -> None: self._semantic = SemanticInfo(schema) self._items = schema.cache.constituents self._cst_by_ID = schema.cache.by_id diff --git a/rsconcept/backend/apps/rsform/models/RSForm.py b/rsconcept/backend/apps/rsform/models/RSForm.py index ae1804cf..b411e2a8 100644 --- a/rsconcept/backend/apps/rsform/models/RSForm.py +++ b/rsconcept/backend/apps/rsform/models/RSForm.py @@ -21,7 +21,7 @@ DELETED_ALIAS = 'DEL' class RSForm: ''' RSForm wrapper. No caching, each mutation requires querying. ''' - def __init__(self, model: LibraryItem): + def __init__(self, model: LibraryItem) -> None: assert model.item_type == LibraryItemType.RSFORM self.model = model diff --git a/rsconcept/backend/apps/rsform/models/RSFormCached.py b/rsconcept/backend/apps/rsform/models/RSFormCached.py index aa7ed50d..3f49f8ce 100644 --- a/rsconcept/backend/apps/rsform/models/RSFormCached.py +++ b/rsconcept/backend/apps/rsform/models/RSFormCached.py @@ -20,7 +20,7 @@ from .RSForm import DELETED_ALIAS, RSForm class RSFormCached: ''' RSForm cached. Caching allows to avoid querying for each method call. ''' - def __init__(self, item_id: int): + def __init__(self, item_id: int) -> None: self.pk = item_id self.cache: _RSFormCache = _RSFormCache(self) @@ -400,7 +400,7 @@ class RSFormCached: class _RSFormCache: ''' Cache for RSForm constituents. ''' - def __init__(self, schema: 'RSFormCached'): + def __init__(self, schema: 'RSFormCached') -> None: self._schema = schema self.constituents: list[Constituenta] = [] self.by_id: dict[int, Constituenta] = {} diff --git a/rsconcept/backend/apps/rsform/models/SemanticInfo.py b/rsconcept/backend/apps/rsform/models/SemanticInfo.py index c3f39791..de9579a3 100644 --- a/rsconcept/backend/apps/rsform/models/SemanticInfo.py +++ b/rsconcept/backend/apps/rsform/models/SemanticInfo.py @@ -16,7 +16,7 @@ from .RSFormCached import RSFormCached class SemanticInfo: ''' Semantic information derived from constituents. ''' - def __init__(self, schema: RSFormCached): + def __init__(self, schema: RSFormCached) -> None: schema.cache.ensure_loaded() self._items = schema.cache.constituents self._cst_by_ID = schema.cache.by_id diff --git a/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py b/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py index 1966538c..4d12e157 100644 --- a/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py +++ b/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py @@ -12,7 +12,7 @@ from ..models import Constituenta, CstType class PyConceptAdapter: ''' RSForm adapter for interacting with pyconcept module. ''' - def __init__(self, data: Union[int, dict]): + def __init__(self, data: Union[int, dict]) -> None: try: if 'items' in cast(dict, data): self.data = self._prepare_request_raw(cast(dict, data)) diff --git a/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py b/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py index 4dcacb19..37dbce60 100644 --- a/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py +++ b/rsconcept/backend/apps/rsform/tests/s_models/t_RSFormCached.py @@ -22,8 +22,8 @@ class TestRSFormCached(DBTester): self.assertFalse(schema1.constituentsQ().exists()) self.assertFalse(schema2.constituentsQ().exists()) - Constituenta.objects.create(alias='X1', schema=schema1.model, order=0) - Constituenta.objects.create(alias='X2', schema=schema1.model, order=1) + Constituenta.objects.create(alias='X1', schema_id=schema1.pk, order=0) + Constituenta.objects.create(alias='X2', schema_id=schema1.pk, order=1) self.assertTrue(schema1.constituentsQ().exists()) self.assertFalse(schema2.constituentsQ().exists()) self.assertEqual(schema1.constituentsQ().count(), 2) @@ -32,7 +32,7 @@ class TestRSFormCached(DBTester): def test_insert_last(self): x1 = self.schema.insert_last('X1') self.assertEqual(x1.order, 0) - self.assertEqual(x1.schema, self.schema.model) + self.assertEqual(x1.schema_id, self.schema.pk) def test_create_cst(self): @@ -115,8 +115,8 @@ class TestRSFormCached(DBTester): definition_formal='X2 = X3' ) test_ks = RSFormCached.create(title='Test') - test_ks.insert_from(self.schema.model.pk) - items = Constituenta.objects.filter(schema=test_ks.model).order_by('order') + test_ks.insert_from(self.schema.pk) + items = Constituenta.objects.filter(schema_id=test_ks.pk).order_by('order') self.assertEqual(len(items), 4) self.assertEqual(items[0].alias, 'X2') self.assertEqual(items[1].alias, 'D2') @@ -200,7 +200,7 @@ class TestRSFormCached(DBTester): self.schema.substitute([(x1, x2)]) self.assertEqual(self.schema.constituentsQ().count(), 3) - self.assertEqual(Attribution.objects.filter(container__schema=self.schema.model).count(), 2) + self.assertEqual(Attribution.objects.filter(container__schema_id=self.schema.pk).count(), 2) self.assertTrue(Attribution.objects.filter(container=x2, attribute=d2).exists()) self.assertTrue(Attribution.objects.filter(container=x2, attribute=d1).exists()) diff --git a/rsconcept/backend/apps/rsform/views/rsforms.py b/rsconcept/backend/apps/rsform/views/rsforms.py index 36fca4a4..9cab39dc 100644 --- a/rsconcept/backend/apps/rsform/views/rsforms.py +++ b/rsconcept/backend/apps/rsform/views/rsforms.py @@ -91,9 +91,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr insert_after = data['insert_after'] with transaction.atomic(): - schema = m.RSFormCached(item.pk) + propagation = PropagationFacade() + schema = propagation.get_schema(item.pk) new_cst = schema.create_cst(data, insert_after) - PropagationFacade.after_create_cst(schema, [new_cst]) + propagation.after_create_cst(schema, [new_cst]) item.save(update_fields=['time_update']) return Response( @@ -125,9 +126,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr data = serializer.validated_data['item_data'] with transaction.atomic(): - schema = m.RSFormCached(item.pk) + propagation = PropagationFacade() + schema = propagation.get_schema(item.pk) old_data = schema.update_cst(cst.pk, data) - PropagationFacade.after_update_cst(schema, cst.pk, data, old_data) + propagation.after_update_cst(schema, cst.pk, data, old_data) if 'alias' in data and data['alias'] != cst.alias: cst.refresh_from_db() changed_type = 'cst_type' in data and cst.cst_type != data['cst_type'] @@ -138,7 +140,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr cst.save() schema.apply_mapping(mapping=mapping, change_aliases=False) if changed_type: - PropagationFacade.after_change_cst_type(item.pk, cst.pk, cast(m.CstType, cst.cst_type)) + propagation.after_change_cst_type(item.pk, cst.pk, cast(m.CstType, cst.cst_type)) item.save(update_fields=['time_update']) return Response( status=c.HTTP_200_OK, @@ -208,9 +210,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr ) with transaction.atomic(): - schema = m.RSFormCached(item.pk) + propagation = PropagationFacade() + schema = propagation.get_schema(item.pk) new_cst = schema.produce_structure(cst, cst_parse) - PropagationFacade.after_create_cst(schema, new_cst) + propagation.after_create_cst(schema, new_cst) item.save(update_fields=['time_update']) return Response( status=c.HTTP_200_OK, @@ -245,7 +248,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr original = cast(m.Constituenta, substitution['original']) replacement = cast(m.Constituenta, substitution['substitution']) substitutions.append((original, replacement)) - PropagationFacade.before_substitute(item.pk, substitutions) + PropagationFacade().before_substitute(item.pk, substitutions) schema.substitute(substitutions) item.save(update_fields=['time_update']) @@ -275,7 +278,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr with transaction.atomic(): schema = m.RSForm(item) - PropagationFacade.before_delete_cst(item.pk, [cst.pk for cst in cst_list]) + PropagationFacade().before_delete_cst(item.pk, [cst.pk for cst in cst_list]) schema.delete_cst(cst_list) item.save(update_fields=['time_update']) @@ -305,11 +308,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr attribute = serializer.validated_data['attribute'] with transaction.atomic(): - new_association = m.Attribution.objects.create( + new_attribution = m.Attribution.objects.create( container=container, attribute=attribute ) - PropagationFacade.after_create_attribution(item.pk, [new_association]) + PropagationFacade().after_create_attribution(item.pk, [new_attribution]) item.save(update_fields=['time_update']) return Response( @@ -345,7 +348,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr 'container': msg.invalidAssociation() }) - PropagationFacade.before_delete_attribution(item.pk, target) + PropagationFacade().before_delete_attribution(item.pk, target) m.Attribution.objects.filter(pk__in=[attrib.pk for attrib in target]).delete() item.save(update_fields=['time_update']) @@ -375,7 +378,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr with transaction.atomic(): target = list(m.Attribution.objects.filter(container=serializer.validated_data['target'])) if target: - PropagationFacade.before_delete_attribution(item.pk, target) + PropagationFacade().before_delete_attribution(item.pk, target) m.Attribution.objects.filter(pk__in=[attrib.pk for attrib in target]).delete() item.save(update_fields=['time_update']) @@ -493,10 +496,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata}) serializer.is_valid(raise_exception=True) - result: m.RSForm = serializer.save() + result: m.RSFormCached = serializer.save() return Response( status=c.HTTP_200_OK, - data=s.RSFormParseSerializer(result.model).data + data=s.RSFormParseSerializer(LibraryItem.objects.get(pk=result.pk)).data ) @extend_schema( @@ -651,10 +654,10 @@ class TrsImportView(views.APIView): _prepare_rsform_data(data, request, owner) serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': True}) serializer.is_valid(raise_exception=True) - schema: m.RSForm = serializer.save() + schema: m.RSFormCached = serializer.save() return Response( status=c.HTTP_201_CREATED, - data=LibraryItemSerializer(schema.model).data + data=LibraryItemSerializer(LibraryItem.objects.get(pk=schema.pk)).data ) @@ -687,10 +690,10 @@ def create_rsform(request: Request) -> HttpResponse: _prepare_rsform_data(data, request, owner) serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True}) serializer_rsform.is_valid(raise_exception=True) - schema: m.RSForm = serializer_rsform.save() + schema: m.RSFormCached = serializer_rsform.save() return Response( status=c.HTTP_201_CREATED, - data=LibraryItemSerializer(schema.model).data + data=LibraryItemSerializer(LibraryItem.objects.get(pk=schema.pk)).data ) @@ -732,16 +735,17 @@ def inline_synthesis(request: Request) -> HttpResponse: serializer.is_valid(raise_exception=True) item = cast(LibraryItem, serializer.validated_data['receiver']) - receiver = m.RSFormCached(item.pk) target_cst = cast(list[m.Constituenta], serializer.validated_data['items']) source = cast(LibraryItem, serializer.validated_data['source']) target_ids = [item.pk for item in target_cst] if target_cst else None with transaction.atomic(): + propagation = PropagationFacade() + receiver = propagation.get_schema(item.pk) new_items = receiver.insert_from(source.pk, target_ids) target_ids = [item[0].pk for item in new_items] mapping_ids = {cst.pk: new_cst for (cst, new_cst) in new_items} - PropagationFacade.after_create_cst(receiver, [item[1] for item in new_items]) + propagation.after_create_cst(receiver, [item[1] for item in new_items]) substitutions: list[tuple[m.Constituenta, m.Constituenta]] = [] for substitution in serializer.validated_data['substitutions']: @@ -753,7 +757,7 @@ def inline_synthesis(request: Request) -> HttpResponse: replacement = mapping_ids[replacement.pk] substitutions.append((original, replacement)) - PropagationFacade.before_substitute(receiver.pk, substitutions) + propagation.before_substitute(receiver.pk, substitutions) receiver.substitute(substitutions) item.save(update_fields=['time_update'])