def get(self, nsid: Union[str, Nsid]) -> NamespaceNodeBase: """ Description: return a node object specified by NSID """ log = LoggerAdapter(logger, dict(name_ext=f"{self.__class__.__name__}.get")) self._validate_namespace_nsid_head(nsid) _nsid_ = Nsid(nsid) current_node = self.root nsid_segments = list_nsid_segments(nsid)[ 1:] #- skip initial root segment n = 0 while current_node.nsid != _nsid_: log.debug(f"target {_nsid_=} != {current_node.nsid=}") try: nsid_segment = nsid_segments[n] except IndexError as err: raise NamespaceInternalError( f"while looking for nsid \"{_nsid_}\", ran out of nsid_segments: {nsid_segments} at index {n}" ) from err try: current_node = getattr(current_node, nsid_segment) if not isinstance(current_node, NamespaceNodeBase): warn( "Rogue node type detected in the namespace. Will most likely cause errors." ) except AttributeError: raise NamespaceLookupError( f"{current_node} has no attribute named '{nsid_segment}'") n += 1 log.debug(f"found {_nsid_=} == {current_node.nsid=}") return current_node
def add_exactly_one(self, nsid: Union[str, Nsid], node_factory=NamespaceNodeBase, *args, **kwargs): """ Description: add one and only one new node to this namespace and return the new node Input: nsid: nsid of new node to create node_factory: factory method to call to create the node *args, **kwargs: passed through to node_factory Output: exactly one new node created or raises an exception if: - it looks like it will create more than one node: NamespaceLookupError or - if it didn't look like it but somehow it did: NamespaceInternalError """ nsid_segments = list_nsid_segments(nsid, skip_root=True) if len(nsid_segments) > 1: try: #- if the parent exists, this will add only one self.get(get_parent_nsid(nsid)) except NamespaceLookupError: err_msg = f"add_exactly_one: error: input \"{nsid}\" would create more" +\ f" than one new node. ({len(nsid_segments)} > 1)" raise ValueError(err_msg) new_nodes = self.add(nsid, node_factory, *args, **kwargs) if len(new_nodes) > 1: raise NamespaceInternalError( f"created more than one new node! ({new_nodes})") return new_nodes[0]
def add(self, nsid: Union[str, Nsid], node_factory: Union[callable, None] = None, *args, **kwargs) -> List[NamespaceNodeBase]: """ Description: add a new nsid to this namespace Input: nsid: the nsid to create in this namespace node_factory: what factory to use to create the node NOTE: parent nodes will be created with this namespaces default_node_factory method *args: passed into the node_factory as args **kwargs: passed into the node_factory as kwargs """ log = LoggerAdapter(logger, dict(name_ext=f"{self.__class__.__name__}.add")) if find_common_prefix(str(self.root.nsid), nsid) is None: err_msg = f'child nsid ({nsid}) must share a common prefix with Namespace root node nsid' err_msg += f'({str(self.root.nsid)})' raise InvalidNsidError(err_msg) _nsid = Nsid(nsid) if node_factory is None: node_factory = self.default_node_factory #- find the deepest existing ancestor of the node we wish to add deepest_ancestor = self.root for current_nsid in get_nsid_ancestry(nsid): try: deepest_ancestor = self.get(current_nsid) except NamespaceLookupError: break else: #- we never hit break, so every single nsid in the entire ancestry exists, including the one we want to add raise NamespaceCollisionError( f'A node with the nsid "{nsid}" already exists in the namespace.' ) #- if here, we have a valid deepest ancestor to start from child_nsid_tail = strip_common_prefix(str(deepest_ancestor.nsid), str(_nsid))[1] common_prefix = find_common_prefix(str(deepest_ancestor.nsid), str(_nsid)) created_nodes = list( ) #- keep track of all the nodes we create to return them nsid_segments = list_nsid_segments(child_nsid_tail) for i, child_attribute_name in enumerate(nsid_segments): new_node_nsid = make_child_nsid(str(deepest_ancestor.nsid), child_attribute_name) #- use the node factory on the last node only if i == len(nsid_segments) - 1: log.debug(f"creating node: {node_factory=})") try: new_node = node_factory(*args, nsid=new_node_nsid, namespace=self, **kwargs) except TypeError as e: raise TypeError( f"node_factory failed to create node: {str(e)}") from e else: new_node = self.default_node_factory(nsid=new_node_nsid, namespace=self) created_nodes.append(new_node) setattr(deepest_ancestor, child_attribute_name, new_node) deepest_ancestor = getattr(deepest_ancestor, child_attribute_name) return created_nodes
def test_list_nsid_segments2(self): nsid1 = '.' self.assertEqual(['.'], list_nsid_segments(nsid1))
def test_list_nsid_segments(self): nsid1 = '.a.b.c' self.assertEqual(['.','a','b','c'], list_nsid_segments(nsid1))
def test_list_nsid_segments3(self): nsid = '.a' self.assertEqual(['.', 'a'], list_nsid_segments(nsid))