Example #1
0
def test_attribute():
    attribute1 = Attribute('name', 'true', 'test-entity1',
                           identifier='test-attribute1',
                           data_type=DataType.BOOLEAN,
                           language=Language.FRA)

    # Instantiate and open topic store.
    store = TopicStore(username, password)
    store.open()

    # Persist attribute to store.
    if not store.attribute_exists(TOPIC_MAP_IDENTIFIER, 'test-entity1', 'name'):
        store.set_attribute(TOPIC_MAP_IDENTIFIER, attribute1)

    # Retrieve attribute from store.
    attribute2 = store.get_attribute(TOPIC_MAP_IDENTIFIER, 'test-attribute1')

    store.close()

    assert attribute2.identifier == 'test-attribute1'
    assert attribute2.name == 'name'
    assert attribute2.value == 'true'
    assert attribute2.entity_identifier == 'test-entity1'
    assert attribute2.scope == '*'  # Universal scope.
    assert attribute2.data_type is DataType.BOOLEAN
    assert attribute2.language is Language.FRA
Example #2
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)
Example #3
0
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)