ConceptPortal-public/rsconcept/backend/apps/oss/models/OperationSchemaCached.py
2025-08-05 14:26:56 +03:00

805 lines
35 KiB
Python

''' Models: OSS API. '''
# pylint: disable=duplicate-code
from typing import Optional
from rest_framework.serializers import ValidationError
from apps.library.models import LibraryItem
from apps.rsform.graph import Graph
from apps.rsform.models import INSERT_LAST, Constituenta, CstType, OrderManager, RSFormCached
from .Argument import Argument
from .Inheritance import Inheritance
from .Operation import Operation, OperationType
from .OperationSchema import (
CstMapping,
CstSubstitution,
OperationSchema,
cst_mapping_to_alias,
extract_data_references,
map_cst_update_data
)
from .Reference import Reference
from .Substitution import Substitution
class OperationSchemaCached:
''' Operations schema API with caching. '''
def __init__(self, model: LibraryItem):
self.model = model
self.cache = OssCache(self)
def delete_reference(self, target: int, keep_connections: bool = False, keep_constituents: bool = False):
''' Delete Reference Operation. '''
if not keep_connections:
self.delete_operation(target, keep_constituents)
return
self.cache.ensure_loaded_subs()
operation = self.cache.operation_by_id[target]
reference_target = self.cache.reference_target.get(target)
if reference_target:
for arg in operation.getQ_as_argument():
arg.argument_id = reference_target
arg.save()
self.cache.remove_operation(target)
operation.delete()
def delete_operation(self, target: int, keep_constituents: bool = False):
''' Delete Operation. '''
self.cache.ensure_loaded_subs()
operation = self.cache.operation_by_id[target]
children = self.cache.extend_graph.outputs[target]
if operation.result is not None and len(children) > 0:
ids = list(Constituenta.objects.filter(schema=operation.result).values_list('pk', flat=True))
if not keep_constituents:
self._cascade_delete_inherited(operation.pk, ids)
else:
inheritance_to_delete: list[Inheritance] = []
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation)
if child_schema is None:
continue
self._undo_substitutions_cst(ids, child_operation, child_schema)
for item in self.cache.inheritance[child_id]:
if item.parent_id in ids:
inheritance_to_delete.append(item)
for item in inheritance_to_delete:
self.cache.remove_inheritance(item)
Inheritance.objects.filter(pk__in=[item.pk for item in inheritance_to_delete]).delete()
self.cache.remove_operation(target)
operation.delete()
def set_input(self, target: int, schema: Optional[LibraryItem]) -> None:
''' Set input schema for operation. '''
operation = self.cache.operation_by_id[target]
has_children = len(self.cache.extend_graph.outputs[target]) > 0
old_schema = self.cache.get_schema(operation)
if schema is None and old_schema is None or \
(schema is not None and old_schema is not None and schema.pk == old_schema.model.pk):
return
if old_schema is not None:
if has_children:
self.before_delete_cst(old_schema.model.pk, [cst.pk for cst in old_schema.cache.constituents])
self.cache.remove_schema(old_schema)
operation.setQ_result(schema)
if schema is not None:
operation.alias = schema.alias
operation.title = schema.title
operation.description = schema.description
operation.save(update_fields=['alias', 'title', 'description'])
if schema is not None and has_children:
rsform = RSFormCached(schema)
self.after_create_cst(rsform, list(rsform.constituentsQ().order_by('order')))
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
''' Set arguments of target Operation. '''
self.cache.ensure_loaded_subs()
operation = self.cache.operation_by_id[target]
processed: list[Operation] = []
updated: list[Argument] = []
deleted: list[Argument] = []
for current in operation.getQ_arguments():
if current.argument not in arguments:
deleted.append(current)
else:
processed.append(current.argument)
current.order = arguments.index(current.argument)
updated.append(current)
if len(deleted) > 0:
self.before_delete_arguments(operation, [x.argument for x in deleted])
for deleted_arg in deleted:
self.cache.remove_argument(deleted_arg)
Argument.objects.filter(pk__in=[x.pk for x in deleted]).delete()
Argument.objects.bulk_update(updated, ['order'])
added: list[Operation] = []
for order, arg in enumerate(arguments):
if arg not in processed:
processed.append(arg)
new_arg = Argument.objects.create(operation=operation, argument=arg, order=order)
self.cache.insert_argument(new_arg)
added.append(arg)
if len(added) > 0:
self.after_create_arguments(operation, added)
def set_substitutions(self, target: int, substitutes: list[dict]) -> None:
''' Clear all arguments for target Operation. '''
self.cache.ensure_loaded_subs()
operation = self.cache.operation_by_id[target]
schema = self.cache.get_schema(operation)
processed: list[dict] = []
deleted: list[Substitution] = []
for current in operation.getQ_substitutions():
subs = [
x for x in substitutes
if x['original'] == current.original and x['substitution'] == current.substitution
]
if len(subs) == 0:
deleted.append(current)
else:
processed.append(subs[0])
if len(deleted) > 0:
if schema is not None:
for sub in deleted:
self._undo_substitution(schema, sub)
else:
for sub in deleted:
self.cache.remove_substitution(sub)
Substitution.objects.filter(pk__in=[x.pk for x in deleted]).delete()
added: list[Substitution] = []
for sub_item in substitutes:
if sub_item not in processed:
new_sub = Substitution.objects.create(
operation=operation,
original=sub_item['original'],
substitution=sub_item['substitution']
)
added.append(new_sub)
self._process_added_substitutions(schema, added)
def execute_operation(self, operation: Operation) -> bool:
''' Execute target Operation. '''
schemas: list[int] = [
arg.argument.result_id
for arg in Argument.objects
.filter(operation=operation)
.select_related('argument')
.only('argument__result_id')
.order_by('order')
if arg.argument.result_id is not None
]
if len(schemas) == 0:
return False
substitutions = operation.getQ_substitutions()
receiver = OperationSchema.create_input(self.model, self.cache.operation_by_id[operation.pk])
self.cache.insert_schema(receiver)
parents: dict = {}
children: dict = {}
for operand in schemas:
items = list(Constituenta.objects.filter(schema_id=operand).order_by('order'))
new_items = receiver.insert_copy(items)
for (i, cst) in enumerate(new_items):
parents[cst.pk] = items[i]
children[items[i].pk] = cst
translated_substitutions: list[tuple[Constituenta, Constituenta]] = []
for sub in substitutions:
original = children[sub.original.pk]
replacement = children[sub.substitution.pk]
translated_substitutions.append((original, replacement))
receiver.substitute(translated_substitutions)
for cst in Constituenta.objects.filter(schema=receiver.model).order_by('order'):
parent = parents.get(cst.pk)
assert parent is not None
Inheritance.objects.create(
operation_id=operation.pk,
child=cst,
parent=parent
)
OrderManager(receiver).restore_order()
receiver.reset_aliases()
receiver.resolve_all_text()
if len(self.cache.extend_graph.outputs[operation.pk]) > 0:
receiver_items = list(Constituenta.objects.filter(schema=receiver.model).order_by('order'))
self.after_create_cst(receiver, receiver_items)
receiver.model.save(update_fields=['time_update'])
return True
def relocate_down(self, source: RSFormCached, 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.model.pk)
self._undo_substitutions_cst(items, operation, destination)
inheritance_to_delete = [item for item in self.cache.inheritance[operation.pk] if item.parent_id in items]
for item in inheritance_to_delete:
self.cache.remove_inheritance(item)
Inheritance.objects.filter(operation_id=operation.pk, parent_id__in=items).delete()
def relocate_up(self, source: RSFormCached, destination: RSFormCached,
items: list[Constituenta]) -> 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.model.pk)
alias_mapping: dict[str, str] = {}
for item in self.cache.inheritance[operation.pk]:
if item.parent_id in destination.cache.by_id:
source_cst = source.cache.by_id[item.child_id]
destination_cst = destination.cache.by_id[item.parent_id]
alias_mapping[source_cst.alias] = destination_cst.alias
new_items = destination.insert_copy(items, initial_mapping=alias_mapping)
for index, cst in enumerate(new_items):
new_inheritance = Inheritance.objects.create(
operation=operation,
child=items[index],
parent=cst
)
self.cache.insert_inheritance(new_inheritance)
self.after_create_cst(destination, new_items, exclude=[operation.pk])
destination.model.save(update_fields=['time_update'])
return new_items
def after_create_cst(
self, source: RSFormCached,
cst_list: list[Constituenta],
exclude: Optional[list[int]] = None
) -> None:
''' Trigger cascade resolutions when new Constituenta is created. '''
self.cache.insert_schema(source)
alias_mapping = OperationSchema.create_dependant_mapping(source, cst_list)
operation = self.cache.get_operation(source.model.pk)
self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude)
def after_change_cst_type(self, schemaID: int, target: int, new_type: CstType) -> None:
''' Trigger cascade resolutions when Constituenta type is changed. '''
operation = self.cache.get_operation(schemaID)
self._cascade_change_cst_type(operation.pk, target, new_type)
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.model.pk)
depend_aliases = extract_data_references(data, old_data)
alias_mapping: CstMapping = {}
for alias in depend_aliases:
cst = source.cache.by_alias.get(alias)
if cst is not None:
alias_mapping[alias] = cst
self._cascade_update_cst(
operation=operation.pk,
cst_id=target,
data=data,
old_data=old_data,
mapping=alias_mapping
)
def before_delete_cst(self, sourceID: int, target: list[int]) -> None:
''' Trigger cascade resolutions before Constituents are deleted. '''
operation = self.cache.get_operation(sourceID)
self._cascade_delete_inherited(operation.pk, target)
def before_substitute(self, schemaID: int, substitutions: CstSubstitution) -> None:
''' Trigger cascade resolutions before Constituents are substituted. '''
operation = self.cache.get_operation(schemaID)
self._cascade_before_substitute(substitutions, operation)
def before_delete_arguments(self, target: Operation, arguments: list[Operation]) -> None:
''' Trigger cascade resolutions before arguments are deleted. '''
if target.result_id is None:
return
for argument in arguments:
parent_schema = self.cache.get_schema(argument)
if parent_schema is not None:
self._execute_delete_inherited(target.pk, [cst.pk for cst in parent_schema.cache.constituents])
def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None:
''' Trigger cascade resolutions after arguments are created. '''
schema = self.cache.get_schema(target)
if schema is None:
return
for argument in arguments:
parent_schema = self.cache.get_schema(argument)
if parent_schema is None:
continue
self._execute_inherit_cst(
target_operation=target.pk,
source=parent_schema,
items=list(parent_schema.constituentsQ().order_by('order')),
mapping={}
)
# pylint: disable=too-many-arguments, too-many-positional-arguments
def _cascade_inherit_cst(
self, target_operation: int,
source: RSFormCached,
items: list[Constituenta],
mapping: CstMapping,
exclude: Optional[list[int]] = None
) -> None:
children = self.cache.extend_graph.outputs[target_operation]
if len(children) == 0:
return
for child_id in children:
if not exclude or child_id not in exclude:
self._execute_inherit_cst(child_id, source, items, mapping)
def _execute_inherit_cst(
self,
target_operation: int,
source: RSFormCached,
items: list[Constituenta],
mapping: CstMapping
) -> None:
operation = self.cache.operation_by_id[target_operation]
destination = self.cache.get_schema(operation)
if destination is None:
return
self.cache.ensure_loaded_subs()
new_mapping = self._transform_mapping(mapping, operation, destination)
alias_mapping = cst_mapping_to_alias(new_mapping)
insert_where = self._determine_insert_position(items[0].pk, operation, source, destination)
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
for index, cst in enumerate(new_cst_list):
new_inheritance = Inheritance.objects.create(
operation=operation,
child=cst,
parent=items[index]
)
self.cache.insert_inheritance(new_inheritance)
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
self._cascade_inherit_cst(operation.pk, destination, new_cst_list, new_mapping)
def _cascade_change_cst_type(self, operation_id: int, cst_id: int, ctype: CstType) -> None:
children = self.cache.extend_graph.outputs[operation_id]
if len(children) == 0:
return
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
successor_id = self.cache.get_inheritor(cst_id, child_id)
if successor_id is None:
continue
child_schema = self.cache.get_schema(child_operation)
if child_schema is None:
continue
if child_schema.change_cst_type(successor_id, ctype):
self._cascade_change_cst_type(child_id, successor_id, ctype)
# pylint: disable=too-many-arguments, too-many-positional-arguments
def _cascade_update_cst(
self,
operation: int,
cst_id: int,
data: dict, old_data: dict,
mapping: CstMapping
) -> None:
children = self.cache.extend_graph.outputs[operation]
if len(children) == 0:
return
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
successor_id = self.cache.get_inheritor(cst_id, child_id)
if successor_id is None:
continue
child_schema = self.cache.get_schema(child_operation)
assert child_schema is not None
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
alias_mapping = cst_mapping_to_alias(new_mapping)
successor = child_schema.cache.by_id.get(successor_id)
if successor is None:
continue
new_data = map_cst_update_data(successor, data, old_data, alias_mapping)
if len(new_data) == 0:
continue
new_old_data = child_schema.update_cst(successor.pk, new_data)
if len(new_old_data) == 0:
continue
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
self._cascade_update_cst(
operation=child_id,
cst_id=successor_id,
data=new_data,
old_data=new_old_data,
mapping=new_mapping
)
def _cascade_delete_inherited(self, operation: int, target: list[int]) -> None:
children = self.cache.extend_graph.outputs[operation]
if len(children) == 0:
return
self.cache.ensure_loaded_subs()
for child_id in children:
self._execute_delete_inherited(child_id, target)
def _execute_delete_inherited(self, operation_id: int, parent_ids: list[int]) -> None:
operation = self.cache.operation_by_id[operation_id]
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._cascade_delete_inherited(operation_id, target_ids)
if len(target_ids) > 0:
self.cache.remove_cst(operation_id, target_ids)
schema.delete_cst(target_ids)
def _cascade_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None:
children = self.cache.extend_graph.outputs[operation.pk]
if len(children) == 0:
return
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation)
if child_schema is None:
continue
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
if len(new_substitutions) == 0:
continue
self._cascade_before_substitute(new_substitutions, child_operation)
child_schema.substitute(new_substitutions)
def _cascade_partial_mapping(
self,
mapping: CstMapping,
target: list[int],
operation: int,
schema: RSFormCached
) -> None:
alias_mapping = cst_mapping_to_alias(mapping)
schema.apply_partial_mapping(alias_mapping, target)
children = self.cache.extend_graph.outputs[operation]
if len(children) == 0:
return
self.cache.ensure_loaded_subs()
for child_id in children:
child_operation = self.cache.operation_by_id[child_id]
child_schema = self.cache.get_schema(child_operation)
if child_schema is None:
continue
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
if not new_mapping:
continue
new_target = self.cache.get_inheritors_list(target, child_id)
if len(new_target) == 0:
continue
self._cascade_partial_mapping(new_mapping, new_target, child_id, child_schema)
def _transform_mapping(self, mapping: CstMapping, operation: Operation, schema: RSFormCached) -> CstMapping:
if len(mapping) == 0:
return mapping
result: CstMapping = {}
for alias, cst in mapping.items():
if cst is None:
result[alias] = None
continue
successor_id = self.cache.get_successor(cst.pk, operation.pk)
if successor_id is None:
continue
successor = schema.cache.by_id.get(successor_id)
if successor is None:
continue
result[alias] = successor
return result
def _determine_insert_position(
self, prototype_id: int,
operation: Operation,
source: RSFormCached,
destination: RSFormCached
) -> int:
''' Determine insert_after for new constituenta. '''
prototype = source.cache.by_id[prototype_id]
prototype_index = source.cache.constituents.index(prototype)
if prototype_index == 0:
return 0
prev_cst = source.cache.constituents[prototype_index - 1]
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
if inherited_prev_id is None:
return INSERT_LAST
prev_cst = destination.cache.by_id[inherited_prev_id]
prev_index = destination.cache.constituents.index(prev_cst)
return prev_index + 1
def _transform_substitutions(
self,
target: CstSubstitution,
operation: int,
schema: RSFormCached
) -> CstSubstitution:
result: CstSubstitution = []
for current_sub in target:
sub_replaced = False
new_substitution_id = self.cache.get_inheritor(current_sub[1].pk, operation)
if new_substitution_id is None:
for sub in self.cache.substitutions[operation]:
if sub.original_id == current_sub[1].pk:
sub_replaced = True
new_substitution_id = self.cache.get_inheritor(sub.original_id, operation)
break
new_original_id = self.cache.get_inheritor(current_sub[0].pk, operation)
original_replaced = False
if new_original_id is None:
for sub in self.cache.substitutions[operation]:
if sub.original_id == current_sub[0].pk:
original_replaced = True
sub.original_id = current_sub[1].pk
sub.save()
new_original_id = new_substitution_id
new_substitution_id = self.cache.get_inheritor(sub.substitution_id, operation)
break
if sub_replaced and original_replaced:
raise ValidationError({'propagation': 'Substitution breaks OSS substitutions.'})
for sub in self.cache.substitutions[operation]:
if sub.substitution_id == current_sub[0].pk:
sub.substitution_id = current_sub[1].pk
sub.save()
if new_original_id is not None and new_substitution_id is not None:
result.append((schema.cache.by_id[new_original_id], schema.cache.by_id[new_substitution_id]))
return result
def _undo_substitutions_cst(self, target_ids: list[int], operation: Operation, schema: RSFormCached) -> None:
to_process = []
for sub in self.cache.substitutions[operation.pk]:
if sub.original_id in target_ids or sub.substitution_id in target_ids:
to_process.append(sub)
for sub in to_process:
self._undo_substitution(schema, sub, target_ids)
def _undo_substitution(
self,
schema: RSFormCached,
target: Substitution,
ignore_parents: Optional[list[int]] = None
) -> None:
if ignore_parents is None:
ignore_parents = []
operation_id = target.operation_id
original_schema = self.cache.get_schema_by_id(target.original.schema_id)
dependant = []
for cst_id in original_schema.get_dependant([target.original_id]):
if cst_id not in ignore_parents:
inheritor_id = self.cache.get_inheritor(cst_id, operation_id)
if inheritor_id is not None:
dependant.append(inheritor_id)
self.cache.substitutions[operation_id].remove(target)
target.delete()
new_original: Optional[Constituenta] = None
if target.original_id not in ignore_parents:
full_cst = Constituenta.objects.get(pk=target.original_id)
cst_mapping = OperationSchema.create_dependant_mapping(original_schema, [full_cst])
self._execute_inherit_cst(operation_id, original_schema, [full_cst], cst_mapping)
new_original_id = self.cache.get_inheritor(target.original_id, operation_id)
assert new_original_id is not None
new_original = schema.cache.by_id[new_original_id]
if len(dependant) > 0:
substitution_id = self.cache.get_inheritor(target.substitution_id, operation_id)
assert substitution_id is not None
substitution_inheritor = schema.cache.by_id[substitution_id]
mapping = {substitution_inheritor.alias: new_original}
self._cascade_partial_mapping(mapping, dependant, operation_id, schema)
def _process_added_substitutions(self, schema: Optional[RSFormCached], added: list[Substitution]) -> None:
if len(added) == 0:
return
if schema is None:
for sub in added:
self.cache.insert_substitution(sub)
return
cst_mapping: CstSubstitution = []
for sub in added:
original_id = self.cache.get_inheritor(sub.original_id, sub.operation_id)
substitution_id = self.cache.get_inheritor(sub.substitution_id, sub.operation_id)
if original_id is None or substitution_id is None:
raise ValueError('Substitutions not found.')
original_cst = schema.cache.by_id[original_id]
substitution_cst = schema.cache.by_id[substitution_id]
cst_mapping.append((original_cst, substitution_cst))
self.before_substitute(schema.model.pk, cst_mapping)
schema.substitute(cst_mapping)
for sub in added:
self.cache.insert_substitution(sub)
class OssCache:
''' Cache for OSS data. '''
def __init__(self, oss: OperationSchemaCached):
self._oss = oss
self._schemas: list[RSFormCached] = []
self._schema_by_id: dict[int, RSFormCached] = {}
self.operations = list(Operation.objects.filter(oss=oss.model).only('result_id', 'operation_type'))
self.operation_by_id = {operation.pk: operation for operation in self.operations}
self.graph = Graph[int]()
self.extend_graph = Graph[int]()
for operation in self.operations:
self.graph.add_node(operation.pk)
self.extend_graph.add_node(operation.pk)
references = Reference.objects.filter(reference__oss=self._oss.model).only('reference_id', 'target_id')
self.reference_target = {ref.reference_id: ref.target_id for ref in references}
arguments = Argument.objects \
.filter(operation__oss=self._oss.model) \
.only('operation_id', 'argument_id') \
.order_by('order')
for argument in arguments:
self.graph.add_edge(argument.argument_id, argument.operation_id)
self.extend_graph.add_edge(argument.argument_id, argument.operation_id)
target = self.reference_target.get(argument.argument_id)
if target is not None:
self.extend_graph.add_edge(target, argument.operation_id)
self.is_loaded_subs = False
self.substitutions: dict[int, list[Substitution]] = {}
self.inheritance: dict[int, list[Inheritance]] = {}
def ensure_loaded_subs(self) -> None:
''' Ensure cache is fully loaded. '''
if self.is_loaded_subs:
return
self.is_loaded_subs = True
for operation in self.operations:
self.inheritance[operation.pk] = []
self.substitutions[operation.pk] = []
for sub in Substitution.objects.filter(operation__oss=self._oss.model).only(
'operation_id', 'original_id', 'substitution_id', 'original__schema_id'):
self.substitutions[sub.operation_id].append(sub)
for item in Inheritance.objects.filter(operation__oss=self._oss.model).only(
'operation_id', 'parent_id', 'child_id'):
self.inheritance[item.operation_id].append(item)
def get_schema(self, operation: Operation) -> Optional[RSFormCached]:
''' 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.from_id(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.from_id(target)
schema.cache.ensure_loaded()
self._insert_new(schema)
return schema
def get_operation(self, schemaID: int) -> Operation:
''' Get operation by schema. '''
for operation in self.operations:
if operation.result_id == schemaID and operation.operation_type != OperationType.REFERENCE:
return operation
raise ValueError(f'Operation for schema {schemaID} not found')
def get_inheritor(self, parent_cst: int, operation: int) -> Optional[int]:
''' Get child for parent inside target RSFrom. '''
for item in self.inheritance[operation]:
if item.parent_id == parent_cst:
return item.child_id
return None
def get_inheritors_list(self, target: list[int], operation: int) -> list[int]:
''' Get child for parent inside target RSFrom. '''
result = []
for item in self.inheritance[operation]:
if item.parent_id in target:
result.append(item.child_id)
return result
def get_successor(self, parent_cst: int, operation: int) -> Optional[int]:
''' Get child for parent inside target RSFrom including substitutions. '''
for sub in self.substitutions[operation]:
if sub.original_id == parent_cst:
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.model.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)
self.extend_graph.add_edge(argument.argument_id, argument.operation_id)
target = self.reference_target.get(argument.argument_id)
if target is not None:
self.extend_graph.add_edge(target, argument.operation_id)
def insert_inheritance(self, inheritance: Inheritance) -> None:
''' Insert new inheritance. '''
self.inheritance[inheritance.operation_id].append(inheritance)
def insert_substitution(self, sub: Substitution) -> None:
''' Insert new substitution. '''
self.substitutions[sub.operation_id].append(sub)
def remove_cst(self, operation: int, target: list[int]) -> None:
''' Remove constituents from operation. '''
subs_to_delete = [
sub for sub in self.substitutions[operation]
if sub.original_id in target or sub.substitution_id in target
]
for sub in subs_to_delete:
self.substitutions[operation].remove(sub)
inherit_to_delete = [item for item in self.inheritance[operation] if item.child_id in target]
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.model.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.operations.remove(self.operation_by_id[operation])
del self.operation_by_id[operation]
if operation in self.reference_target:
del self.reference_target[operation]
if self.is_loaded_subs:
del self.substitutions[operation]
del self.inheritance[operation]
def remove_argument(self, argument: Argument) -> None:
''' Remove argument from cache. '''
self.graph.remove_edge(argument.argument_id, argument.operation_id)
self.extend_graph.remove_edge(argument.argument_id, argument.operation_id)
target = self.reference_target.get(argument.argument_id)
if target is not None:
if not Argument.objects.filter(argument_id=target, operation_id=argument.operation_id).exists():
self.extend_graph.remove_edge(target, argument.operation_id)
def remove_substitution(self, target: Substitution) -> None:
''' Remove substitution from cache. '''
self.substitutions[target.operation_id].remove(target)
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.model.pk] = schema