def test_topic_occurrences(): # Instantiate and open topic store. store = TopicStore(username, password) store.open() # Retrieve topic from store. topic2 = store.get_topic(TOPIC_MAP_IDENTIFIER, 'test-topic1', resolve_attributes=RetrievalOption.RESOLVE_ATTRIBUTES, resolve_occurrences=RetrievalOption.RESOLVE_OCCURRENCES) store.close() assert topic2.identifier == 'test-topic1' assert topic2.instance_of == 'topic' assert len(topic2.base_names) == 1 assert topic2.first_base_name.name == 'Test Topic 1' assert topic2.first_base_name.language is Language.SPA assert len(topic2.attributes) == 1 assert len(topic2.occurrences) >= 1 assert topic2.occurrences[0].identifier == 'test-occurrence1' assert topic2.occurrences[0].topic_identifier == 'test-topic1' assert topic2.occurrences[0].instance_of == 'occurrence' assert topic2.occurrences[0].scope == '*' # Universal scope. assert topic2.occurrences[0].resource_ref == 'http://example.com/resource.pdf' assert topic2.occurrences[0].resource_data is None assert topic2.occurrences[0].language is Language.DEU assert len(topic2.occurrences[0].attributes) == 0
def test_topic(): topic1 = Topic(identifier='test-topic1', base_name='Test Topic 1', language=Language.SPA) # Instantiate and open topic store. store = TopicStore(username, password) store.open() # Persist topic to store. if not store.topic_exists(TOPIC_MAP_IDENTIFIER, 'test-topic1'): store.set_topic(TOPIC_MAP_IDENTIFIER, topic1) # Retrieve topic from store. topic2 = store.get_topic(TOPIC_MAP_IDENTIFIER, 'test-topic1', resolve_attributes=RetrievalOption.RESOLVE_ATTRIBUTES) store.close() assert topic2.identifier == 'test-topic1' assert topic2.instance_of == 'topic' assert len(topic2.base_names) == 1 assert topic2.first_base_name.name == 'Test Topic 1' assert topic2.first_base_name.language is Language.SPA assert len(topic2.attributes) == 1 assert len(topic2.occurrences) == 0
class SceneStore: def __init__(self, username, password, host='localhost', port=5432): self.topic_store = TopicStore(username, password, host, port) def open(self): self.topic_store.open() def close(self): self.topic_store.close() def get_character(self, topic_map_identifier, identifier): result = None topic = self.topic_store.get_topic( topic_map_identifier, identifier, resolve_attributes=RetrievalOption.RESOLVE_ATTRIBUTES) if topic: result = Character(topic.identifier, topic.first_base_name.name) result.description = topic.get_attribute_by_name( 'description').value if topic.get_attribute_by_name( 'description') else None result.location = topic.get_attribute_by_name('location').value result.rotation = topic.get_attribute_by_name('rotation').value result.scale = topic.get_attribute_by_name('scale').value occurrences = self.topic_store.get_topic_occurrences( topic_map_identifier, identifier) for occurrence in occurrences: result.add_asset( Asset(occurrence.instance_of, occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ('description', 'location', 'rotation', 'scale') ] result.add_attributes(attributes) return result def get_prop(self, topic_map_identifier, identifier): result = None topic = self.topic_store.get_topic( topic_map_identifier, identifier, resolve_attributes=RetrievalOption.RESOLVE_ATTRIBUTES) if topic: result = Prop(topic.identifier, topic.first_base_name.name) result.description = topic.get_attribute_by_name( 'description').value if topic.get_attribute_by_name( 'description') else None result.location = topic.get_attribute_by_name('location').value result.rotation = topic.get_attribute_by_name('rotation').value result.scale = topic.get_attribute_by_name('scale').value occurrences = self.topic_store.get_topic_occurrences( topic_map_identifier, identifier) for occurrence in occurrences: result.add_asset( Asset(occurrence.instance_of, occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ('description', 'location', 'rotation', 'scale') ] result.add_attributes(attributes) return result def get_scene(self, topic_map_identifier, identifier): result = None topic = self.topic_store.get_topic( topic_map_identifier, identifier, resolve_attributes=RetrievalOption.RESOLVE_ATTRIBUTES) if topic: result = Scene(topic.identifier, topic.first_base_name.name) result.description = topic.get_attribute_by_name( 'description').value if topic.get_attribute_by_name( 'description') else None result.location = topic.get_attribute_by_name('location').value result.rotation = topic.get_attribute_by_name('rotation').value result.scale = topic.get_attribute_by_name('scale').value result.ordinal = topic.get_attribute_by_name( 'ordinal').value if topic.get_attribute_by_name( 'ordinal') else None result.add_associations( self.topic_store.get_topic_associations( topic_map_identifier, identifier)) # Add scene's entities (props and characters). if result.associations: groups = self.topic_store.get_association_groups( topic_map_identifier, identifier) for instance_of in groups.dict: for role in groups.dict[instance_of]: for topic_ref in groups[instance_of, role]: if topic_ref == identifier: continue if instance_of == 'navigation': path = Path(role, topic_ref) result.add_path(path) elif instance_of == 'prop': result.add_entity( self.get_prop(topic_map_identifier, topic_ref)) elif instance_of == 'character': result.add_entity( self.get_prop(topic_map_identifier, topic_ref)) elif instance_of == 'categorization': # Tags. result.add_tag(topic_ref) occurrences = self.topic_store.get_topic_occurrences( topic_map_identifier, identifier) for occurrence in occurrences: result.add_asset( Asset(occurrence.instance_of, occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ('description', 'location', 'rotation', 'scale', 'ordinal') ] result.add_attributes(attributes) result.entities_tags = self.get_entities_tags( topic_map_identifier, identifier) return result def get_quest(self, topic_map_identifier, identifier): result = None # TODO: Implement. return result def set_character(self, topic_map_identifier, character, scene_identifier): topic = Topic(character.identifier, character.instance_of, character.name) self.topic_store.set_topic(topic_map_identifier, topic) description_attribute = Attribute( 'description', character.description, topic.identifier) if character.description else None location_attribute = Attribute('location', character.location, topic.identifier) rotation_attribute = Attribute('rotation', character.rotation, topic.identifier) scale_attribute = Attribute('scale', character.scale, topic.identifier) attributes = [ x for x in [ description_attribute, location_attribute, rotation_attribute, scale_attribute ] if x is not None ] self.topic_store.set_attributes(topic_map_identifier, attributes) for asset in character.assets: occurrence = Occurrence(instance_of=asset.instance_of, topic_identifier=topic.identifier, resource_ref=asset.reference, resource_data=asset.data) self.topic_store.set_occurrence(topic_map_identifier, occurrence) scene_association = Association( instance_of='character', src_topic_ref=topic.identifier, # The character's reference. dest_topic_ref=scene_identifier, src_role_spec='included-in', dest_role_spec='includes') self.topic_store.set_association(topic_map_identifier, scene_association) book_association = Association( instance_of='character', src_topic_ref=topic.identifier, # The character's reference. dest_topic_ref='genesis', src_role_spec='included-in', dest_role_spec='includes', scope='book') self.topic_store.set_association(topic_map_identifier, book_association) def set_prop(self, topic_map_identifier, prop, scene_identifier): topic = Topic(prop.identifier, prop.instance_of, prop.name) self.topic_store.set_topic(topic_map_identifier, topic) description_attribute = Attribute( 'description', prop.description, topic.identifier) if prop.description else None location_attribute = Attribute('location', prop.location, topic.identifier) rotation_attribute = Attribute('rotation', prop.rotation, topic.identifier) scale_attribute = Attribute('scale', prop.scale, topic.identifier) attributes = [ x for x in [ description_attribute, location_attribute, rotation_attribute, scale_attribute ] if x is not None ] self.topic_store.set_attributes(topic_map_identifier, attributes) for asset in prop.assets: occurrence = Occurrence(instance_of=asset.instance_of, topic_identifier=topic.identifier, resource_ref=asset.reference, resource_data=asset.data) self.topic_store.set_occurrence(topic_map_identifier, occurrence) scene_association = Association( instance_of='prop', src_topic_ref=topic.identifier, # The prop's reference. dest_topic_ref=scene_identifier, src_role_spec='included-in', dest_role_spec='includes') self.topic_store.set_association(topic_map_identifier, scene_association) book_association = Association( instance_of='prop', src_topic_ref=topic.identifier, # The prop's reference. dest_topic_ref='genesis', src_role_spec='included-in', dest_role_spec='includes', scope='book') self.topic_store.set_association(topic_map_identifier, book_association) def set_scene(self, topic_map_identifier, scene): topic = Topic(scene.identifier, scene.instance_of, scene.name) self.topic_store.set_topic(topic_map_identifier, topic) description_attribute = Attribute( 'description', scene.description, topic.identifier) if scene.description else None location_attribute = Attribute('location', scene.location, topic.identifier) rotation_attribute = Attribute('rotation', scene.rotation, topic.identifier) scale_attribute = Attribute('scale', scene.scale, topic.identifier) ordinal_attribute = Attribute( 'ordinal', scene.ordinal, topic.identifier) if scene.ordinal else None attributes = [ x for x in [ description_attribute, location_attribute, rotation_attribute, scale_attribute, ordinal_attribute ] if x is not None ] self.topic_store.set_attributes(topic_map_identifier, attributes) for asset in scene.assets: occurrence = Occurrence(instance_of=asset.instance_of, topic_identifier=topic.identifier, resource_ref=asset.reference, resource_data=asset.data) self.topic_store.set_occurrence(topic_map_identifier, occurrence) book_association = Association( instance_of='scene', src_topic_ref=topic.identifier, # The scene's reference. dest_topic_ref='genesis', src_role_spec='included-in', dest_role_spec='includes', scope='book') self.topic_store.set_association(topic_map_identifier, book_association) def set_quest(self, topic_map_identifier, quest, scene_identifier): # TODO: Implement. pass def set_navigation(self, topic_map_identifier, src_scene_identifier, dest_scene_identifier, src_scene_role='previous', dest_scene_role='next'): association = Association(instance_of='navigation', src_topic_ref=src_scene_identifier, dest_topic_ref=dest_scene_identifier, src_role_spec=src_scene_role, dest_role_spec=dest_scene_role) self.topic_store.set_association(topic_map_identifier, association) def get_entities_tags(self, topic_map_identifier, identifier): result = {} # Map from topics with tags to tags with topics. For example, the below topic -> tags mappings: # topic1 -> tag1, tag2, tag3 # topic2 -> tag2, tag4 # topic3 -> tag3, tag4, tag5 # topic4 -> tag4, tag5, tag6, tag7 # topic5 -> tag1, tag8 # # Should become the following tag -> topics mappings: # tag1 -> topic1, topic5 # tag2 -> topic1, topic2 # tag3 -> topic1, topic3 # tag4 -> topic2, topic3, topic4 # tag5 -> topic3, topic4 # tag6 -> topic4 # tag7 -> topic4 # tag8 -> topic5 topic_tags = {} groups = self.topic_store.get_association_groups( topic_map_identifier, identifier) for instance_of in groups.dict: for role in groups.dict[instance_of]: for topic_ref in groups[instance_of, role]: if topic_ref == identifier: continue if instance_of == 'prop' or instance_of == 'character': topic_tags[topic_ref] = self.topic_store.get_tags( topic_map_identifier, topic_ref) for topic, tags in topic_tags.items(): for tag in tags: if tag not in result.keys(): result[tag] = { topic } # Topics set. Will guarantee that topic identifiers are unique for each tag. else: result[tag].add(topic) return result # ========== TOPIC STORE PROXY METHODS ========== def get_association( self, topic_map_identifier, identifier, language=None, resolve_attributes=RetrievalOption.DONT_RESOLVE_ATTRIBUTES, resolve_occurrences=RetrievalOption.DONT_RESOLVE_OCCURRENCES): return self.topic_store.get_association(topic_map_identifier, identifier, language, resolve_attributes, resolve_occurrences) def get_association_groups(self, topic_map_identifier, identifier='', associations=None, instance_of=None, scope=None): return self.topic_store.get_association_groups(topic_map_identifier, identifier, associations, instance_of=instance_of, scope=scope) def get_attributes(self, topic_map_identifier, entity_identifier, scope=None, language=None): return self.topic_store.get_attributes(topic_map_identifier, entity_identifier, scope, language) def get_attribute(self, topic_map_identifier, identifier): return self.topic_store.get_attribute(topic_map_identifier, identifier) def get_occurrence( self, topic_map_identifier, identifier, inline_resource_data=RetrievalOption.DONT_INLINE_RESOURCE_DATA, resolve_attributes=RetrievalOption.DONT_RESOLVE_ATTRIBUTES): return self.topic_store.get_occurrence(topic_map_identifier, identifier, inline_resource_data, resolve_attributes) def get_topic_occurrences( self, topic_map_identifier, identifier, instance_of=None, scope=None, language=None, inline_resource_data=RetrievalOption.DONT_INLINE_RESOURCE_DATA, resolve_attributes=RetrievalOption.DONT_RESOLVE_ATTRIBUTES): return self.topic_store.get_topic_occurrences(topic_map_identifier, identifier, instance_of, scope, language, inline_resource_data, resolve_attributes) def get_topic( self, topic_map_identifier, identifier, language=None, resolve_attributes=RetrievalOption.DONT_RESOLVE_ATTRIBUTES, resolve_occurrences=RetrievalOption.DONT_RESOLVE_OCCURRENCES): return self.topic_store.get_topic(topic_map_identifier, identifier, language, resolve_attributes, resolve_occurrences) def get_topic_identifiers(self, topic_map_identifier, query, offset=0, limit=100): return self.topic_store.get_topic_identifiers( topic_map_identifier, query, instance_of=['prop', 'character', 'scene'], offset=offset, limit=limit) def get_topics(self, topic_map_identifier, instance_of=None, language=None, offset=0, limit=100, resolve_attributes=RetrievalOption.DONT_RESOLVE_ATTRIBUTES): return self.topic_store.get_topics(topic_map_identifier, instance_of, language, offset, limit, resolve_attributes) def get_topics_network(self, topic_map_identifier, identifier, maximum_depth=10, cumulative_depth=0, accumulative_tree=None, accumulative_nodes=None): return self.topic_store.get_topics_network( topic_map_identifier, identifier, maximum_depth, cumulative_depth, accumulative_tree, accumulative_nodes, instance_of=['prop', 'character', 'scene', 'navigation'], scope='*') def get_topic_map(self, identifier): return self.topic_store.get_topic_map(identifier) def get_topic_maps(self): return self.topic_store.get_topic_maps() def set_attribute(self, topic_map_identifier, attribute, ontology_mode=OntologyMode.LENIENT): self.topic_store.set_attribute(topic_map_identifier, attribute, ontology_mode) def set_tags(self, topic_map_identifier, identifier, tags): self.topic_store.set_tags(topic_map_identifier, identifier, tags) def set_occurrence(self, topic_map_identifier, occurrence, ontology_mode=OntologyMode.STRICT): self.topic_store.set_occurrence(topic_map_identifier, occurrence, ontology_mode) def set_topic(self, topic_map_identifier, topic, ontology_mode=OntologyMode.STRICT): self.topic_store.set_topic(topic_map_identifier, topic, ontology_mode)
class StoryStore: def __init__( self, username: str, password: str, host: str = "localhost", port: int = 5432, dbname: str = "storydb", source_directory: str = None, destination_directory: str = None, ) -> None: self.topic_store = TopicStore(username, password, host, port, dbname) self.source_directory = source_directory self.destination_directory = destination_directory def open(self) -> StoryStore: self.topic_store.open() return self def close(self) -> None: self.topic_store.close() @staticmethod def to_boolean(value): if isinstance(value, str) and value: if value.lower() in ["true", "t", "1"]: return True elif value.lower() in ["false", "f", "0"]: return False raise StoryDbError( f"The [{value}] is not recognized as a boolean value") # ========== CONTEXT MANAGER ========== def __enter__(self) -> StoryStore: return self.open() def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.close() # ========== ENTITY ========== # Persist participant, thing, place and event entities def set_entity(self, map_identifier: int, entity: Entity, event_identifier: str = None) -> None: if (self.source_directory is None) or (self.destination_directory is None): raise StoryDbError("Missing resource directories") if not self.topic_store.topic_exists(map_identifier, entity.identifier): topic = Topic(entity.identifier, entity.instance_of, entity.name) timestamp = str(datetime.now()) modification_attribute = Attribute( "modification-timestamp", timestamp, topic.identifier, data_type=DataType.TIMESTAMP, ) self.topic_store.set_topic(map_identifier, topic) self.topic_store.set_attribute(map_identifier, modification_attribute) if hasattr(entity, "description") and entity.description: text_occurrence = Occurrence( instance_of="text", topic_identifier=entity.identifier, resource_data=entity.description, ) self.topic_store.set_occurrence(map_identifier, text_occurrence) if hasattr(entity, "animation") and entity.animation: entity.add_attribute( Attribute( "animation", entity.animation, entity.identifier, data_type=DataType.STRING, )) # Create the file directory for this topic map and topic if it doesn't already exist file_directory = os.path.join(self.destination_directory, str(map_identifier), entity.identifier) if not os.path.isdir(file_directory): os.makedirs(file_directory) for resource in entity.resources: occurrence = Occurrence( instance_of=resource.instance_of, topic_identifier=entity.identifier, resource_ref=resource.reference, resource_data=resource.data, ) title_attribute = Attribute( "title", resource.title, occurrence.identifier, data_type=DataType.STRING, ) self.topic_store.set_occurrence(map_identifier, occurrence) self.topic_store.set_attribute(map_identifier, title_attribute) # Copy resource file to appropriate (topic) directory if occurrence.resource_ref: source_file_path = os.path.join(self.source_directory, occurrence.resource_ref) destination_file_path = os.path.join( self.destination_directory, str(map_identifier), entity.identifier, occurrence.resource_ref, ) if not os.path.isfile(destination_file_path): shutil.copy(source_file_path, destination_file_path) if hasattr(entity, "tags"): self.topic_store.set_tags(map_identifier, entity.identifier, entity.tags) self.topic_store.set_attributes(map_identifier, entity.attributes) if event_identifier: association = Association( instance_of=entity.instance_of, src_topic_ref=entity.identifier, dest_topic_ref=event_identifier, src_role_spec="included-in", dest_role_spec="includes", ) self.topic_store.set_association(map_identifier, association) # ========== PARTICIPANT ========== def get_participant(self, map_identifier: int, identifier: str) -> Optional[Participant]: result = None topic = self.topic_store.get_topic( map_identifier, identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic: result = Participant(topic.identifier, topic.first_base_name.name) result.description = ( topic.get_attribute_by_name("description").value if topic.get_attribute_by_name("description") else None) result.animation = (topic.get_attribute_by_name("animation").value if topic.get_attribute_by_name("animation") else None) occurrences = self.topic_store.get_topic_occurrences( map_identifier, identifier) for occurrence in occurrences: if occurrence.instance_of == "text": text_data = self.topic_store.get_occurrence_data( map_identifier, occurrence.identifier) result.add_resource( Resource( occurrence.instance_of, reference=occurrence.resource_ref, data=text_data, )) else: result.add_resource( Resource(occurrence.instance_of, reference=occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ("description", "animation") ] result.add_attributes(attributes) result.add_tags( self.topic_store.get_tags(map_identifier, identifier)) return result # ========== THING ========== def get_thing(self, map_identifier: int, identifier: str) -> Optional[Thing]: result = None topic = self.topic_store.get_topic( map_identifier, identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic: result = Thing(topic.identifier, topic.first_base_name.name) result.description = ( topic.get_attribute_by_name("description").value if topic.get_attribute_by_name("description") else None) result.animation = (topic.get_attribute_by_name("animation").value if topic.get_attribute_by_name("animation") else None) occurrences = self.topic_store.get_topic_occurrences( map_identifier, identifier) for occurrence in occurrences: if occurrence.instance_of == "text": text_data = self.topic_store.get_occurrence_data( map_identifier, occurrence.identifier) result.add_resource( Resource( occurrence.instance_of, reference=occurrence.resource_ref, data=text_data, )) else: result.add_resource( Resource(occurrence.instance_of, reference=occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ("description", "animation") ] result.add_attributes(attributes) result.add_tags( self.topic_store.get_tags(map_identifier, identifier)) return result # ========== PLACE ========== def get_place(self, map_identifier: int, identifier: str) -> Optional[Place]: result = None topic = self.topic_store.get_topic( map_identifier, identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) if topic: result = Place(topic.identifier, topic.first_base_name.name) result.description = ( topic.get_attribute_by_name("description").value if topic.get_attribute_by_name("description") else None) result.animation = (topic.get_attribute_by_name("animation").value if topic.get_attribute_by_name("animation") else None) result.auto_rotate = (self.to_boolean( topic.get_attribute_by_name("auto-rotate").value) if topic.get_attribute_by_name("auto-rotate") else None) result.view_labels = (self.to_boolean( topic.get_attribute_by_name("view-labels").value) if topic.get_attribute_by_name("view-labels") else None) associations = self.topic_store.get_topic_associations( map_identifier, identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, ) for association in associations: if association.instance_of == "spatial-navigation": destination_identifier = association.get_member_by_role( "to").topic_refs[0] if destination_identifier != identifier: description = self.topic_store.get_topic( map_identifier, destination_identifier).first_base_name.name navigation_identifier = association.get_attribute_by_name( "navigation-identifier").value result.add_path( Path( navigation_identifier=navigation_identifier, event_identifier=destination_identifier, description=description, )) occurrences = self.topic_store.get_topic_occurrences( map_identifier, identifier) for occurrence in occurrences: if occurrence.instance_of == "text": text_data = self.topic_store.get_occurrence_data( map_identifier, occurrence.identifier) result.add_resource( Resource( occurrence.instance_of, reference=occurrence.resource_ref, data=text_data, )) else: result.add_resource( Resource(occurrence.instance_of, reference=occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ("description", "animation", "auto-rotate", "view-labels") ] result.add_attributes(attributes) return result def set_place(self, map_identifier: int, place: Place, event_identifier: str = None) -> None: place.add_attribute( Attribute( "auto-rotate", place.auto_rotate, place.identifier, data_type=DataType.BOOLEAN, )) place.add_attribute( Attribute( "view-labels", place.view_labels, place.identifier, data_type=DataType.BOOLEAN, )) self.set_entity(map_identifier, place, event_identifier) # ========== EVENT ========== def get_event( self, map_identifier: int, identifier: str, resolve_sub_events: ResolveMode = ResolveMode.RESOLVE_SUB_EVENTS, resolve_causes: ResolveMode = ResolveMode.RESOLVE_CAUSES, resolve_effects: ResolveMode = ResolveMode.RESOLVE_EFFECTS, call_level: int = 0, ) -> Optional[Event]: result = None topic = self.topic_store.get_topic( map_identifier, identifier, resolve_attributes=RetrievalMode.RESOLVE_ATTRIBUTES, resolve_occurrences=RetrievalMode.RESOLVE_OCCURRENCES, ) if topic: rank = topic.get_attribute_by_name("rank").value # What? action_property = topic.get_attribute_by_name( "action-property").value result = Event( topic.identifier, action_property, rank=int(rank), name=topic.first_base_name.name, ) topic_occurrences = self.topic_store.get_topic_occurrences( map_identifier, identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA) for occurrence in topic_occurrences: if occurrence.instance_of == "text" and occurrence.resource_data: result.description = occurrence.resource_data.decode() associations = self.topic_store.get_topic_associations( map_identifier, identifier) # Who, where, what and why? groups = self.topic_store.get_association_groups( map_identifier, identifier, associations=associations) if len(groups) > 0: for instance_of in groups.dict: for role in groups.dict[instance_of]: for topic_ref in groups[instance_of, role]: if topic_ref == identifier: continue elif instance_of == "thing": result.add_thing( self.get_thing(map_identifier, topic_ref)) elif instance_of == "participant": result.add_participant( self.get_participant( map_identifier, topic_ref)) elif instance_of == "place": result.where = self.get_place( map_identifier, topic_ref) elif (resolve_causes is ResolveMode.RESOLVE_CAUSES and role == "cause" and instance_of == "temporal-navigation"): if call_level < 1: # Recursive call cause = self.get_event( map_identifier, topic_ref, call_level=call_level + 1) result.add_cause(cause) elif (resolve_effects is ResolveMode.RESOLVE_EFFECTS and role == "effect" and instance_of == "temporal-navigation"): if call_level < 1: # Recursive call effect = self.get_event( map_identifier, topic_ref, call_level=call_level + 1) result.add_effect(effect) elif (resolve_sub_events is ResolveMode.RESOLVE_SUB_EVENTS and role == "included-in" and instance_of == "event"): if call_level < 1: # Recursive call sub_event = self.get_event( map_identifier, topic_ref, call_level=call_level + 1) result.events[topic_ref] = sub_event # When? from_time_point = topic.get_attribute_by_name( "from-time-point").value to_time_point = topic.get_attribute_by_name("to-time-point").value result.when = TimeInterval(from_time_point, to_time_point) for occurrence in topic.occurrences: if occurrence.instance_of == "text": text_data = self.topic_store.get_occurrence_data( map_identifier, occurrence.identifier) result.add_resource( Resource( occurrence.instance_of, reference=occurrence.resource_ref, data=text_data, )) else: result.add_resource( Resource(occurrence.instance_of, reference=occurrence.resource_ref)) attributes = [ attribute for attribute in topic.attributes if attribute.name not in ("description") ] result.add_attributes(attributes) # Tags-to-entities mapping result.entities_tags = self.get_entities_tags( map_identifier, identifier, associations=associations) return result def set_event(self, map_identifier: int, event: Event, parent_event: Event = None) -> None: if not self.topic_store.topic_exists(map_identifier, event.identifier): event.add_attribute( Attribute("rank", str(event.rank), event.identifier, data_type=DataType.NUMBER)) # What? event.add_attribute( Attribute( "action-property", event.action_property, event.identifier, data_type=DataType.STRING, )) # When? if event.when is None: timestamp = str(datetime.now()) event.when = TimeInterval(timestamp, timestamp) event.add_attribute( Attribute( "from-time-point", event.when.from_time_point, event.identifier, data_type=DataType.TIMESTAMP, )) event.add_attribute( Attribute( "to-time-point", event.when.to_time_point, event.identifier, data_type=DataType.TIMESTAMP, )) self.set_entity(map_identifier, event) # What? if parent_event: association = Association( instance_of="event", src_topic_ref=event.identifier, dest_topic_ref=parent_event.identifier, src_role_spec="included-in", dest_role_spec="includes", ) self.topic_store.set_association(map_identifier, association) for key, value in event.events.items(): self.set_event(map_identifier, value, event) # Recursive call # Who? for key, value in event.participants.items(): self.set_entity(map_identifier, value, event.identifier) for key, value in event.things.items(): self.set_entity(map_identifier, value, event.identifier) # Where? if event.where: self.set_place(map_identifier, event.where, event.identifier) # ========== TAG ========== def get_entities_tags( self, map_identifier: int, identifier: str, associations: Optional[List[Association]] = None, ) -> Dict[str, Set[str]]: result: Dict[str, Set[str]] = {} # Map from topics with tags to tags with topics. For example, the below topic -> tags mappings: # topic1 -> tag1, tag2, tag3 # topic2 -> tag2, tag4 # topic3 -> tag3, tag4, tag5 # topic4 -> tag4, tag5, tag6, tag7 # topic5 -> tag1, tag8 # # Should become the following tag -> topics mappings: # tag1 -> topic1, topic5 # tag2 -> topic1, topic2 # tag3 -> topic1, topic3 # tag4 -> topic2, topic3, topic4 # tag5 -> topic3, topic4 # tag6 -> topic4 # tag7 -> topic4 # tag8 -> topic5 topic_tags = {} groups = self.topic_store.get_association_groups( map_identifier, identifier, associations=associations) if groups: for instance_of in groups.dict: for role in groups.dict[instance_of]: for topic_ref in groups[instance_of, role]: if topic_ref == identifier: continue if instance_of in ("participant", "thing"): topic_tags[topic_ref] = self.topic_store.get_tags( map_identifier, topic_ref) for topic, tags in topic_tags.items(): for tag in tags: if tag not in result.keys(): result[tag] = { topic } # Topics set. Will guarantee that topic identifiers are unique for each tag. else: result[tag].add(topic) return result def get_tag(self, map_identifier: int, identifier: str) -> Optional[Tag]: result = None topic = self.topic_store.get_topic( map_identifier, identifier, resolve_occurrences=RetrievalMode.RESOLVE_OCCURRENCES, ) if topic: result = Tag(topic.identifier) for occurrence in topic.occurrences: if occurrence.instance_of == "text": text_data = self.topic_store.get_occurrence_data( map_identifier, occurrence.identifier) result.description = text_data return result # ========== TIMELINE ========== def get_timeline(self, map_identifier: int, event_identifier: str, timeline: List[Event] = None) -> List[Event]: if timeline is None: result = [] else: result = timeline event = self.get_event( map_identifier, event_identifier, ResolveMode.DONT_RESOLVE_SUB_EVENTS, ResolveMode.RESOLVE_CAUSES, ) result.append(event) # Recursive call for effect_identifier in event.effects.keys(): self.get_timeline(map_identifier, effect_identifier, result) return result # ========== NARRATIVE ========== def get_narrative( self, map_identifier: int, event_identifier: str, description_identifier: str = "home") -> Optional[Narrative]: result = None topic = self.topic_store.get_topic(map_identifier, description_identifier) if topic: topic_occurrences = self.topic_store.get_topic_occurrences( map_identifier, description_identifier, inline_resource_data=RetrievalMode.INLINE_RESOURCE_DATA) description = "" for occurrence in topic_occurrences: if occurrence.instance_of == "text" and occurrence.resource_data: if occurrence.resource_data: description = occurrence.resource_data.decode() result = Narrative(topic.first_base_name.name, description) result.timeline = self.get_timeline(map_identifier, event_identifier) result.timeline = sorted(result.timeline, key=lambda event: event.rank) return result # ========== CONNECTION ========== def set_spatial_connection( self, map_identifier: int, source_identifier: str, destination_identifier: str, navigation_identifier: str, ) -> None: association = Association( instance_of="spatial-navigation", src_topic_ref=source_identifier, dest_topic_ref=destination_identifier, src_role_spec="from", dest_role_spec="to", ) attribute = Attribute( "navigation-identifier", navigation_identifier, association.identifier, data_type=DataType.STRING, ) self.topic_store.set_association(map_identifier, association) self.topic_store.set_attribute(map_identifier, attribute) def set_temporal_connection(self, map_identifier: int, source_identifier: str, destination_identifier: str) -> None: association = Association( instance_of="temporal-navigation", src_topic_ref=source_identifier, dest_topic_ref=destination_identifier, src_role_spec="cause", dest_role_spec="effect", ) self.topic_store.set_association(map_identifier, association) # ========== MISCELLANEOUS ========== def initialise(self, map_identifier: int, user_identifier: int) -> None: base_topics = { ("event", "Event"), ("thing", "Thing"), ("participant", "Participant"), ("place", "Place"), ("included-in", "Included In"), ("includes", "Includes"), ("cause", "Cause"), ("effect", "Effect"), ("temporal-navigation", "Temporal Navigation"), ("spatial-navigation", "Spatial Navigation"), ("from", "From"), ("to", "To"), } for item in base_topics: topic = Topic( identifier=item[TopicField.IDENTIFIER.value], name=item[TopicField.BASE_NAME.value], ) self.topic_store.set_topic(map_identifier, topic, TaxonomyMode.LENIENT) self.topic_store.initialise_topic_map(map_identifier, user_identifier)