class SoftwareSystem(Element): def __init__(self): super().__init__() self._location = Location.UNSPECIFIED self._containers = OrderedSet() def get_parent(self): return None # TODO: This is duplicated in Person def get_location(self): return self._location # TODO: This is duplicated in Person def set_location(self, location): if not isinstance(location, Location): raise TypeError("{!r} is not a {}".format(location, Location.__name__)) self._location = location if location is not None else Location.UNSPECIFIED def add_existing_container(self, container): from structurizr.model.container import Container if not isinstance(container, Container): raise TypeError("{!r} is not a {}".format(container, Container.__name__)) self._containers.add(container) def get_containers(self): return self._containers.copy() def add_container(self, name, description, technology): return self.get_model().add_container(self, name, description, technology) def get_container_with_name(self, name): return next((c for c in self.get_containers() if c.get_name() == name), None) def get_container_with_id(self, id): return next((c for c in self.get_containers() if c.get_id() == id), None) # TODO: This is duplicated in Person def get_canonical_name(self): return CANONICAL_NAME_SEPARATOR + self._format_for_canonical_name(self.get_name()) def get_required_tags(self): return OrderedSet([tags.ELEMENT, tags.SOFTWARE_SYSTEM])
def gen_aliases(name, domain, do_alias = True): # prefixes = ["RESTAURANT", "LODGE", "HOTEL", "GUESTHOUSE", "HOUSE", "CAFE", "BAR"] # suffixes = ["RESTAURANT", "LODGE", "HOTEL", "GUESTHOUSE", "HOUSE", "CAFE", "BAR"] prefixes = [domain.upper()] suffixes = [domain.upper()] synonyms_sets = [["&", " & ", "AND", " AND "]] oneway_synonyms_sets = {"-": ["", " "], "(^LA | LA | LA$)": [" THE "]} if do_alias: prefixes = list(set(prefixes + ["RESTAURANT", "HOTEL", "GUESTHOUSE", "HOUSE", "CAFE", "BAR", "THE"])) suffixes = list(set(suffixes + ["RESTAURANT", "HOTEL", "GUESTHOUSE", "HOUSE", "CAFE", "BAR"])) synonyms_sets.append(["GUESTHOUSE", "GUEST HOUSE"]) aliases = OrderedSet() aliases_tmp = aliases.copy() name_upper = name.upper() aliases_tmp.add(name_upper) # print (aliases) # print () # generate alias by replacing synonyms with each other while(len(aliases_tmp) != len(aliases)): aliases = aliases_tmp.copy() for alias in aliases: for synonyms in synonyms_sets: for idx in range(len(synonyms)): replacing_syn = synonyms[idx] # print (replacing_syn) matching_syn = synonyms.copy() # matching_syn.pop(idx) # print (matching_syn) rmSuffix_name = re.sub(("|").join(matching_syn), replacing_syn, alias) rmSuffix_name_strip = re.sub(" +", " ", rmSuffix_name).strip() aliases_tmp.add(rmSuffix_name_strip) # pprint (aliases_tmp) # print () # generate for one-way synonym replaced aliases aliases = OrderedSet() while(len(aliases_tmp) != len(aliases)): aliases = aliases_tmp.copy() for alias in aliases: for matching_syn in oneway_synonyms_sets: for replacing_syn in oneway_synonyms_sets[matching_syn]: rmSuffix_name = re.sub(matching_syn, replacing_syn, alias) rmSuffix_name_strip = re.sub(" +", " ", rmSuffix_name).strip() aliases_tmp.add(rmSuffix_name_strip) # generate alias by removing suffixes aliases = OrderedSet() while(len(aliases_tmp) != len(aliases)): aliases = aliases_tmp.copy() for alias in aliases: for suffix in suffixes: rmSuffix_name = re.sub("(.*) " + suffix + "$", "\g<1>", alias) rmSuffix_name_strip = re.sub(" +", " ", rmSuffix_name).strip() aliases_tmp.add(rmSuffix_name_strip) # generate alias by removing prefixes aliases = OrderedSet() while(len(aliases_tmp) != len(aliases)): aliases = aliases_tmp.copy() for alias in aliases: for prefix in prefixes: rmPrefix_name = re.sub("^" + prefix + " (.*)", "\g<1>", alias) rmPrefix_name_strip = re.sub(" +", " ", rmPrefix_name).strip() aliases_tmp.add(rmPrefix_name_strip) return aliases_tmp
class Element(Taggable): def __init__(self): super().__init__() self._model = None self._id = "" self._name = None self._description = None self._url = None self._relationships = OrderedSet() def get_model(self): return self._model def set_model(self, model): self._model = model def get_id(self): return self._id def set_id(self, id): self._id = id def get_name(self): return self._name def set_name(self, name): if not name: raise ValueError("The name of an element must not be None or empty.") self._name = name def get_url(self): return self._url def set_url(self, url): if url and url.strip(): if is_well_formed_url(url): self._url = url else: raise ValueError("{} is not a valid URL".format(url)) @abstractmethod def get_canonical_name(self): raise NotImplementedError def _format_for_canonical_name(self, name): return name.replace(CANONICAL_NAME_SEPARATOR, "") def get_description(self): return self._description def set_description(self, description): self._description = description @abstractmethod def get_parent(self): raise NotImplementedError def get_relationships(self): return self._relationships.copy() def uses(self, destination, description, technology=None, interaction_style=None): """Adds a unidirectional "uses" style relationship between this element and a software system or component. Args: destination: The target of the relationship description: A description of the relationship (e.g. "uses", "gets data from", "sends data to") technology: The technology details (e.g. JSON/HTTPS) interaction_style: The interaction style (sync vs async) Returns: The relationship that has just been created and added to the model """ return self.get_model().add_relationship(self, destination, description, technology, interaction_style) def delivers(self, destination, description, technology=None, interaction_style=None): """Adds a unidirectional "uses" style relationship between this element and a person. Args: destination: The target of the relationship description: A description of the relationship (e.g. "uses", "gets data from", "sends data to") technology: The technology details (e.g. JSON/HTTPS) interaction_style: The interaction style (sync vs async) Returns: The relationship that has just been created and added to the model """ def has_afferent_relationships(self): """Determines whether this element has afferent (incoming) relationships. """ return any(r.get_destination() == self for r in self.get_model().get_relationships()) def has_efferent_relationships(self, element): return self.get_efferent_relationship_with(element) is not None def get_efferent_relationship_with(self, element): next((r for r in self._relationships if r.get_destination() == element), None) def has(self, relationship): return relationship in self._relationships def add_relationship(self, relationship): self._relationships.add(relationship) def __str__(self): return "{{{id} | {name} | {description}}}".format( id=self.get_id(), name=self.get_name(), description=self.get_description()) # TODO: We should probably override __hash__ here too. Java has the same rule that if you # TODO: override .equals() you must ensure objects have the same .hashCode(), although the # TODO: Java Structurizr does not do this. Possibly an upstream bug. def __eq__(self, rhs): if self is rhs: return True if not isinstance(0, type(self)): return NotImplemented return self.get_canonical_name() == rhs.get_canonical_name() def __ne__(self, rhs): return not (self == rhs)
class Model: def __init__(self): self._id_generator = _SequentialIntegerGeneratorStrategy() self._elements_by_id = {} self._relationships_by_id = {} self._enterprise = None self._people = OrderedSet() self._software_systems = OrderedSet() def get_enterprise(self): return self._enterprise def set_enterprise(self, enterprise): if not isinstance(enterprise, Enterprise): raise TypeError("{!r} is not an {}".format(enterprise, Enterprise.__name__)) self._enterprise = enterprise def add_software_system(self, name, description, location=Location.UNSPECIFIED): if self.get_software_system_with_name(name) is not None: raise ValueError( "A software system named {} already exists".format(name)) software_system = SoftwareSystem() software_system.set_location(location) software_system.set_name(name) software_system.set_description(description) self._software_systems.add(software_system) software_system.set_id(self._id_generator.generate_id(software_system)) self._add_element_to_internal_structures(software_system) return software_system def add_person(self, name, description, location=Location.UNSPECIFIED): if self.get_person_with_name(name) is not None: raise ValueError("A person named {} already exists".format(name)) person = Person() person.set_location(location) person.set_name(name) person.set_description(description) self._people.add(person) person.set_id(self._id_generator.generate_id(person)) self._add_element_to_internal_structures(person) return person def add_container(self, software_system, name, description, technology): if not isinstance(software_system, SoftwareSystem): raise TypeError("{} is not a {}".format(software_system, SoftwareSystem.__name__)) if self.get_container_with_name(name) is not None: raise ValueError( "A software system named {} already exists".format(name)) container = Container() container.set_name(name) container.set_description(description) container.set_technology(technology) container.set_parent(software_system) software_system.add_existing_container(container) container.set_id(self._id_generator.generate_id(container)) self._add_element_to_internal_structures(container) def add_component_of_type(self, container, name, type_name, description, technology): if not isinstance(container, Container): raise TypeError("{} is not a {}".format(container, Container.__name__)) component = Component() component.set_name(name) component.set_type(type_name) component.set_description(description) component.set_technology(technology) component.set_parent(container) container.add_existing_component(component) component.set_id(self._id_generator.generate_id(component)) self._add_element_to_internal_structures(component) def add_component(self, container, name, description): if not isinstance(container, Container): raise TypeError("{} is not a {}".format(container, Container.__name__)) component = Component() component.set_name(name) component.set_description(description) component.set_parent(container) container.add_existing_component(component) component.set_id(self._id_generator.generate_id(component)) self._add_element_to_internal_structures(component) def add_relationship(self, source, destination, description, technology=None, interaction_style=InteractionStyle.SYNCHRONOUS): relationship = Relationship(source, destination, description, technology, interaction_style) if self.add_existing_relationship(relationship): return relationship return None def add_existing_relationship(self, relationship): if not relationship.get_source().has(relationship): relationship.set_id(self._id_generator.generate_id(relationship)) relationship.get_source().add_relationship(relationship) self._add_relationship_to_internal_structures(relationship) return True return False def _add_element_to_internal_structures(self, element): self._elements_by_id[element.get_id()] = element element.set_model(self) self._id_generator.found(element.get_id()) def _add_relationship_to_internal_structures(self, relationship): self._relationships_by_id[relationship.get_id()] = relationship self._id_generator.found(relationship.get_id()) def get_elements(self): return set(self._elements_by_id.values() ) # TODO: Returning a copy again here? def get_element(self, id): return self._elements_by_id[id] def get_relationships(self): return set(self._relationships_by_id.values()) def get_relationship(self, id): return self._relationships_by_id[id] def get_people(self): return self._people.copy() def get_software_systems(self): return self._software_systems.copy() # TODO: Omitting the hydrate stuff for now until I have a better understanding def contains(self, element): return element in self._elements_by_id.values() def get_software_system_with_name(self, name): return next( (ss for ss in self._software_systems if ss.get_name() == name), None) def get_software_system_with_id(self, id): return next((ss for ss in self._software_systems if ss.get_id() == id), None) def get_person_with_name(self, name): return next((p for p in self._people if p.get_name() == name), None) def add_implicit_relationships(self): implicit_relationships = set() for relationship in self.get_relationships(): source = relationship.get_source() destination = relationship.get_destination() while source != None: while destination != None: if not source.has_efferent_relationships_with(destination): if self._propagated_relationship_is_allowed( source, destination): implicit_relationship = self.add_relationship( source, destination, "") if implicit_relationship is not None: implicit_relationship.add( implicit_relationship) destination = destination.get_parent() destination = relationship.get_destination() source = source.get_parent() return implicit_relationships def _propagated_relationship_is_allowed(self, source, destination): if source == destination: return False if source.get_parent() is not None: if destination == source.get_parent(): return False if source.get_parent().get_parent() is not None: if destination == source.get_parent().get_parent(): return False if destination.get_parent() is not None: if source == destination.get_parent(): return False if destination.get_parent().get_parent() is not None: if source == destination.get_parent().get_parent(): return False return True def is_empty(self): return (len(self._people) != 0) or (len(self._software_systems) != 0)