def test_siblingsId(self, simple): """ Test prev property """ # Normal passage checking p, n = self.FULL_EPIGRAMMATA.getTextualNode(["2"], simple=simple).siblingsId self.assertEqual(("1", "3"), (str(p), str(n)), "Middle node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode(["1"], simple=simple).siblingsId self.assertEqual((None, "2"), (p, str(n)), "First node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode(["14"], simple=simple).siblingsId self.assertEqual(("13", None), (str(p), n), "Last node should have right siblings") # Ranges if simple is False: # Start p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("1-2"), simple=simple).siblingsId self.assertEqual((None, "3-4"), (p, str(n)), "First node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("1-5"), simple=simple).siblingsId self.assertEqual((None, "6-10"), (p, str(n)), "First node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("1-9"), simple=simple).siblingsId self.assertEqual((None, "10-14"), (p, str(n)), "First node should have right siblings") # End p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("12-14"), simple=simple).siblingsId self.assertEqual(("9-11", None), (str(p), n), "Last node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("11-14"), simple=simple).siblingsId self.assertEqual(("7-10", None), (str(p), n), "Last node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("5-14"), simple=simple).siblingsId self.assertEqual(("1-4", None), (str(p), n), "Should take the rest") # Middle p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("5-6"), simple=simple).siblingsId self.assertEqual(("3-4", "7-8"), (str(p), str(n)), "Middle node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("5-8"), simple=simple).siblingsId self.assertEqual(("1-4", "9-12"), (str(p), str(n)), "Middle node should have right siblings") p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("5-10"), simple=simple).siblingsId self.assertEqual(("1-4", "11-14"), (str(p), str(n)), "Middle node should have right siblings") # NONE ! p, n = self.FULL_EPIGRAMMATA.getTextualNode( Reference("1-14"), simple=simple).siblingsId self.assertEqual((None, None), (p, n), "If whole range, nothing !")
def test_get_passage_hypercontext_complex_xpath(self): simple = self.text_complex.getTextualNode(Reference("pr.1-1.2")) str_simple = simple.tostring(encoding=str) text = CapitainsCtsText(resource=str_simple, citation=self.text_complex.citation) self.assertIn( "Pervincis tandem", text.getTextualNode(Reference("pr.1"), simple=True).export(output=Mimetypes.PLAINTEXT, exclude=["tei:note" ]).strip(), "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" ) self.assertEqual( text.getTextualNode( Reference("1.2"), simple=True).export(output=Mimetypes.PLAINTEXT).strip(), "lusimus quos in Suebae gratiam virgunculae,", "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" ) self.assertEqual( list(map(lambda x: str(x), text.getValidReff(level=2))), ["pr.1", "1.1", "1.2"], "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" )
def test_get_prevnext_urn(self): """ Check the GetPrevNext request """ text = Text(urn="urn:cts:latinLit:phi1294.phi002.perseus-lat2", retriever=self.parent) p, n = text.getPrevNextUrn(Reference("1.pr.1")) self.assertEqual( p, None ) self.assertEqual( n, "1.pr.2" ) response = text.getPassagePlus(Reference("1.pr.10")) self.assertEqual( str(response.prev.reference), "1.pr.9", "Check Range works on normal middle GetPassage" ) self.assertEqual( str(response.next.reference), "1.pr.11" ) response = text.getPassagePlus(Reference("1.pr.10-1.pr.11")) self.assertEqual( str(response.prev.reference), "1.pr.8-1.pr.9", "Check Range works on GetPassagePlus and compute right prev" ) self.assertEqual( str(response.next.reference), "1.pr.12-1.pr.13", "Check Range works on GetPassagePlus and compute right next" ) if self.cache is not None: self.assertGreater( len(self.cache.cache._cache), 0, "There should be something cached" )
def test_equality(self): a = Reference("1.1@[0]") b = Reference("1.1@[0]") c = Reference("1.1@[1]") d = "1.1@[0]" self.assertEqual(a, b) self.assertNotEqual(a, c) self.assertNotEqual(a, d)
def test_highest(self): self.assertEqual( str((Reference("1.1-1.2.8")).highest), "1.1", "1.1 is higher" ) self.assertEqual( str((Reference("1.1-2")).highest), "2", "2 is higher" )
def getTextualNode(self, subreference=None, simple=False): """ Finds a passage in the current text :param subreference: Identifier of the subreference / passages :type subreference: Union[list, Reference] :param simple: If set to true, retrieves nodes up to the given one, cleaning non required siblings. :type simple: boolean :rtype: CapitainsCtsPassage, ContextPassage :returns: Asked passage """ if subreference is None: return self._getSimplePassage() if isinstance(subreference, str): subreference = Reference(subreference) if isinstance(subreference, list): start, end = subreference, subreference subreference = Reference(".".join(subreference)) elif not subreference.end: start, end = subreference.start.list, subreference.start.list else: start, end = subreference.start.list, subreference.end.list if len(start) > len(self.citation): raise CitationDepthError("URN is deeper than citation scheme") if simple is True: return self._getSimplePassage(subreference) citation_start = [citation for citation in self.citation][len(start)-1] citation_end = [citation for citation in self.citation][len(end)-1] start, end = citation_start.fill(passage=start), citation_end.fill(passage=end) start, end = normalizeXpath(start.split("/")[2:]), normalizeXpath(end.split("/")[2:]) xml = self.textObject.xml if isinstance(xml, etree._Element): root = copyNode(xml) else: root = copyNode(xml.getroot()) root = passageLoop(xml, root, start, end) if self.urn: urn, subreference = URN("{}:{}".format(self.urn, subreference)), subreference else: urn, subreference = None, subreference return CapitainsCtsPassage( urn=urn, resource=root, text=self, citation=self.citation, reference=subreference )
def test_fill(self): c = Citation( name="line", scope="/TEI/text/body/div/div[@n=\"?\"]", xpath="//l[@n=\"?\"]" ) self.assertEqual(c.fill(Reference("1.2")), "/TEI/text/body/div/div[@n='1']//l[@n='2']") self.assertEqual(c.fill(Reference("1.1")), "/TEI/text/body/div/div[@n='1']//l[@n='1']") self.assertEqual(c.fill(None), "/TEI/text/body/div/div[@n]//l[@n]") self.assertEqual(c.fill("1", xpath=True), "//l[@n='1']") self.assertEqual(c.fill("2", xpath=True), "//l[@n='2']") self.assertEqual(c.fill(None, xpath=True), "//l[@n]") self.assertEqual(c.fill([None, None]), "/TEI/text/body/div/div[@n]//l[@n]") self.assertEqual(c.fill(["1", None]), "/TEI/text/body/div/div[@n='1']//l[@n]")
def test_missing_text_in_passage_emptiness(self): a = URN("urn:cts:greekLit:textgroup.work:1-2") self.assertEqual(str(a), "urn:cts:greekLit:textgroup.work:1-2") self.assertEqual(a.upTo(URN.COMPLETE), "urn:cts:greekLit:textgroup.work:1-2") self.assertEqual(a.upTo(URN.NAMESPACE), "urn:cts:greekLit") self.assertEqual(a.upTo(URN.TEXTGROUP), "urn:cts:greekLit:textgroup") self.assertEqual(a.upTo(URN.WORK), "urn:cts:greekLit:textgroup.work") self.assertEqual(a.upTo(URN.NO_PASSAGE), "urn:cts:greekLit:textgroup.work") self.assertEqual(a.upTo(URN.PASSAGE), "urn:cts:greekLit:textgroup.work:1-2") self.assertEqual(a.upTo(URN.PASSAGE_START), "urn:cts:greekLit:textgroup.work:1") self.assertEqual(a.upTo(URN.PASSAGE_END), "urn:cts:greekLit:textgroup.work:2") self.assertEqual(a.reference, Reference("1-2")) self.assertEqual(a.reference.start, Reference("1")) self.assertEqual(a.reference.end, Reference("2")) self.assertIsNone(a.version)
def test_getpassage_variabletypes(self, requests): text = CtsText("urn:cts:latinLit:phi1294.phi002.perseus-lat2", self.endpoint, citation=self.citation) requests.return_value.text = GET_PASSAGE # Test with -1 _ = text.getTextualNode(subreference=Reference("1.1")) requests.assert_called_with( "http://services.perseids.org/remote/cts", params={ "request": "GetPassage", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.1" }) # Test with -1 _ = text.getTextualNode(subreference=URN( "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.2")) requests.assert_called_with( "http://services.perseids.org/remote/cts", params={ "request": "GetPassage", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.2" }) # Test with -1 _ = text.getTextualNode(subreference=["1", "1", "1"]) requests.assert_called_with( "http://services.perseids.org/remote/cts", params={ "request": "GetPassage", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.1.1" })
def test_get_passage_autoparse(self, simple): a = self.TEI.getTextualNode(Reference("2.5.5"), simple=simple) self.assertEqual( a.export(output=Mimetypes.PLAINTEXT), "Saepe domi non es, cum sis quoque, saepe negaris: ", "CtsTextMetadata are automatically parsed in GetPassage hypercontext = False" )
def test_ensure_passage_is_not_removed(self): """ In range, passage in between could be removed from the original text by error """ self.TEI.getTextualNode(Reference("1.pr.1-1.2.5")) orig_refs = self.TEI.getValidReff(level=3) self.assertIn("1.pr.1", orig_refs) self.assertIn("1.1.1", orig_refs) self.assertIn("1.2.4", orig_refs) self.assertIn("1.2.5", orig_refs) self.TEI.getTextualNode(Reference("1.pr-1.2")) orig_refs = self.TEI.getValidReff(level=3) self.assertIn("1.pr.1", orig_refs) self.assertIn("1.1.1", orig_refs) self.assertIn("1.2.4", orig_refs) self.assertIn("1.2.5", orig_refs)
def getTextualNode(self, textId, subreference=None, prevnext=False, metadata=False): """ Retrieve a text node from the API :param textId: PrototypeText Identifier :type textId: str :param subreference: Passage Reference :type subreference: str :param prevnext: Retrieve graph representing previous and next passage :type prevnext: boolean :param metadata: Retrieve metadata about the passage and the text :type metadata: boolean :return: Passage :rtype: Passage """ key = _cache_key("Nautilus", self.name, "Passage", textId, subreference) o = self.cache.get(key) if o is not None: return o text, text_metadata = self.__getText__(textId) if subreference is not None: subreference = Reference(subreference) passage = text.getTextualNode(subreference) passage.set_metadata_from_collection(text_metadata) self.cache.set(key, passage) return passage
def test_getvalidreff(self, requests): text = Text("urn:cts:latinLit:phi1294.phi002.perseus-lat2", self.endpoint, citation=self.citation) requests.return_value.text = GET_VALID_REFF # Test with -1 text.getValidReff(level=-1) requests.assert_called_with( "http://services.perseids.org/api/cts", params={ "request": "GetValidReff", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2", "level": "3" }) # Test with level 2 text.getValidReff(level=2) requests.assert_called_with( "http://services.perseids.org/api/cts", params={ "request": "GetValidReff", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2", "level": "2" }) # Test with no level text.getValidReff() requests.assert_called_with( "http://services.perseids.org/api/cts", params={ "request": "GetValidReff", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2", "level": "1" }) # Test with a ref as str text.getValidReff(reference="1.pr") requests.assert_called_with( "http://services.perseids.org/api/cts", params={ "request": "GetValidReff", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.pr", "level": "1" }) # Test with a ref as subreference reffs = text.getValidReff(reference=Reference("1.pr")) requests.assert_called_with( "http://services.perseids.org/api/cts", params={ "request": "GetValidReff", "urn": "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.pr", "level": "1" }) # Test the parsing self.assertEqual( reffs[0], "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.pr.1")
def test_properties(self): a = Reference("[email protected]@Atreus[3]") self.assertEqual(str(a.start), "1.1@Achilles") self.assertEqual(a.start.list, ["1", "1"]) self.assertEqual(a.start.subreference[0], "Achilles") self.assertEqual(str(a.end), "1.10@Atreus[3]") self.assertEqual(a.end.list, ["1", "10"]) self.assertEqual(a.end.subreference[1], 3) self.assertEqual(a.end.subreference, ("Atreus", 3))
def getTextualNode(self, subreference=None): """ Special GetPassage implementation for SimplePassage (Simple is True by default) :param subreference: :return: """ if not isinstance(subreference, Reference): subreference = Reference(subreference) return self.textObject.getTextualNode(subreference)
def test_Unicode_Support(self): a = Reference("1.1@καὶ[0]-1.10@Ἀλκιβιάδου[3]") self.assertEqual(str(a.start), "1.1@καὶ[0]") self.assertEqual(a.start.list, ["1", "1"]) self.assertEqual(a.start.subreference[0], "καὶ") self.assertEqual(str(a.end), "1.10@Ἀλκιβιάδου[3]") self.assertEqual(a.end.list, ["1", "10"]) self.assertEqual(a.end.subreference[1], 3) self.assertEqual(a.end.subreference, ("Ἀλκιβιάδου", 3))
def test_get_passage(self, simple): a = self.TEI.getTextualNode(["1", "pr", "2"], simple=simple) self.assertEqual( a.export(output=Mimetypes.PLAINTEXT), "tum, ut de illis queri non possit quisquis de se bene ") # With reference a = self.TEI.getTextualNode(Reference("2.5.5"), simple=simple) self.assertEqual(a.export(output=Mimetypes.PLAINTEXT), "Saepe domi non es, cum sis quoque, saepe negaris: ")
def test_properties(self): a = URN( "urn:cts:greekLit:tlg0012.tlg001.mth-01:[email protected]@the[2]") self.assertEqual(a.urn_namespace, "cts") self.assertEqual(a.namespace, "greekLit") self.assertEqual(a.textgroup, "tlg0012") self.assertEqual(a.work, "tlg001") self.assertEqual(a.version, "mth-01") self.assertEqual(a.reference, Reference("[email protected]@the[2]"))
def test_Text_text_function(self, simple): simple = self.seneca.getTextualNode(Reference("1"), simple=simple) str_simple = simple.tostring(encoding=str) text = CapitainsCtsText(resource=str_simple, citation=self.seneca.citation) self.assertEqual( text.export(output=Mimetypes.PLAINTEXT, exclude=["tei:note"]).strip(), "Di coniugales tuque genialis tori,", "Ensure text methods works on CtsTextMetadata object")
def test_get_passage_hyper_context_double_slash_xpath(self): simple = self.seneca.getTextualNode(Reference("1-10")) str_simple = simple.export(output=Mimetypes.XML.Std) text = CapitainsCtsText(resource=str_simple, citation=self.seneca.citation) self.assertEqual( text.getTextualNode(Reference("1"), simple=True).export(output=Mimetypes.PLAINTEXT, exclude=["tei:note" ]).strip(), "Di coniugales tuque genialis tori,", "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" ) self.assertEqual( text.getTextualNode( Reference("10"), simple=True).export(output=Mimetypes.PLAINTEXT).strip(), "aversa superis regna manesque impios", "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" ) self.assertEqual( list(map(lambda x: str(x), text.getValidReff(level=1))), ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" ) simple = self.seneca.getTextualNode(Reference("1")) str_simple = simple.tostring(encoding=str) text = CapitainsCtsText(resource=str_simple, citation=self.seneca.citation) self.assertEqual( text.getTextualNode(Reference("1"), simple=True).export(output=Mimetypes.PLAINTEXT, exclude=["tei:note" ]).strip(), "Di coniugales tuque genialis tori,", "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" ) self.assertEqual( list(map(lambda x: str(x), text.getValidReff(level=1))), ["1"], "Ensure passage finding with context is fully TEI / Capitains compliant (Different level range CapitainsCtsPassage)" )
def test_text_resource(self): """ Test to get the text resource to perform other queries """ Repository = NautilusCTSResolver(["./tests/testing_data/farsiLit"]) text, metadata = Repository.__getText__( "urn:cts:farsiLit:hafez.divan.perseus-eng1") self.assertEqual(len(text.citation), 4, "Object has a citation property of length 4") self.assertEqual( text.getTextualNode( Reference("1.1.1.1")).export(output=Mimetypes.PLAINTEXT), "Ho ! Saki, pass around and offer the bowl (of love for God) : ### ", "It should be possible to retrieve text")
def test_no_end_text_emptiness(self): a = URN("urn:cts:greekLit:textgroup.work.text:1") self.assertEqual(str(a), "urn:cts:greekLit:textgroup.work.text:1") self.assertEqual(a.upTo(URN.COMPLETE), "urn:cts:greekLit:textgroup.work.text:1") self.assertEqual(a.upTo(URN.NAMESPACE), "urn:cts:greekLit") self.assertEqual(a.upTo(URN.TEXTGROUP), "urn:cts:greekLit:textgroup") self.assertEqual(a.upTo(URN.WORK), "urn:cts:greekLit:textgroup.work") self.assertEqual(a.upTo(URN.VERSION), "urn:cts:greekLit:textgroup.work.text") self.assertEqual(a.upTo(URN.PASSAGE), "urn:cts:greekLit:textgroup.work.text:1") self.assertEqual(a.upTo(URN.NO_PASSAGE), "urn:cts:greekLit:textgroup.work.text") self.assertEqual(a.reference, Reference("1")) self.assertIsNone(a.reference.end)
def getSiblings(self, textId, subreference): """ Retrieve the siblings of a textual node :param textId: CtsTextMetadata Identifier :type textId: str :param subreference: CapitainsCtsPassage Reference :type subreference: str :return: Tuple of references :rtype: (str, str) """ text, inventory = self.__getText__(textId) passage = text.getTextualNode(Reference(subreference)) return passage.siblingsId
def test_set(self): a = URN("urn:cts:latinLit:phi1294.phi002.perseus-lat2") a.reference = Reference("1.1") self.assertEqual(str(a), "urn:cts:latinLit:phi1294.phi002.perseus-lat2:1.1") a.reference = "2.2" self.assertEqual(str(a), "urn:cts:latinLit:phi1294.phi002.perseus-lat2:2.2") a.version = "perseus-eng2" self.assertEqual(str(a), "urn:cts:latinLit:phi1294.phi002.perseus-eng2:2.2") a.work = "phi001" self.assertEqual(str(a), "urn:cts:latinLit:phi1294.phi001.perseus-eng2:2.2") a.textgroup = "phi1293" self.assertEqual(str(a), "urn:cts:latinLit:phi1293.phi001.perseus-eng2:2.2") a.namespace = "greekLit" self.assertEqual(str(a), "urn:cts:greekLit:phi1293.phi001.perseus-eng2:2.2")
def siblingsId(self): """ Siblings Identifiers of the passage :rtype: (str, str) """ if not self.__text__: raise MissingAttribute("CapitainsCtsPassage was iniated without CtsTextMetadata object") if self.__prevnext__ is not None: return self.__prevnext__ document_references = list(map(str, self.__text__.getReffs(level=self.depth))) range_length = 1 if self.reference.end is not None: range_length = len(self.getReffs()) start = document_references.index(str(self.reference.start)) if start == 0: # If the passage is already at the beginning _prev = None elif start - range_length < 0: _prev = Reference(document_references[0]) else: _prev = Reference(document_references[start-1]) if start + 1 == len(document_references): # If the passage is already at the end _next = None elif start + range_length > len(document_references): _next = Reference(document_references[-1]) else: _next = Reference(document_references[start + 1]) self.__prevnext__ = (_prev, _next) return self.__prevnext__
def reference(self, value): """ Set up ID property :param value: Representation of the passage subreference as a list :type value: list, tuple, Reference .. note:: `Passage.id = [..]` will update automatically the URN property as well if correct """ _value = None if isinstance(value, (list, tuple)): _value = Reference(".".join(value)) elif isinstance(value, basestring): _value = Reference(value) elif isinstance(value, Reference): _value = value if _value and self.__reference != _value: self.__reference = _value if self._URN and len(self._URN): if len(value): self._URN = URN("{}:{}".format( self._URN.upTo(URN.NO_PASSAGE), str(_value))) else: self._URN = URN(self._URN["text"])
def test_set(self): a = URN("urn:cts:greekLit:textgroup") a.textgroup = "tg" self.assertEqual(a.textgroup, "tg") self.assertEqual(str(a), "urn:cts:greekLit:tg") a.namespace = "ns" self.assertEqual(a.namespace, "ns") self.assertEqual(str(a), "urn:cts:ns:tg") a.work = "wk" self.assertEqual(a.work, "wk") self.assertEqual(str(a), "urn:cts:ns:tg.wk") a.reference = "1-2" self.assertEqual(a.reference, Reference("1-2")) self.assertEqual(str(a), "urn:cts:ns:tg.wk:1-2") a.version = "vs" self.assertEqual(a.version, "vs") self.assertEqual(str(a), "urn:cts:ns:tg.wk.vs:1-2")
def getReffs(self, level=1, subreference=None): """ Reference available at a given level :param level: Depth required. If not set, should retrieve first encountered level (1 based) :type level: Int :param subreference: Subreference (optional) :type subreference: str :rtype: List.basestring :returns: List of levels """ if hasattr(self, "__depth__"): level = level + self.depth if not subreference: if hasattr(self, "reference"): subreference = self.reference else: subreference = Reference(subreference) return self.getValidReff(level, subreference)
def _getPassageContext(self, reference): """ Retrieves nodes up to the given one, cleaning non required siblings. :param reference: Identifier of the subreference / passages :type reference: list, reference :returns: Asked passage :rtype: ContextPassage """ if isinstance(reference, list): start, end = reference, reference reference = Reference(".".join(reference)) elif not reference.end: start, end = reference.start.list, reference.start.list else: start, end = reference.start.list, reference.end.list if len(start) > len(self.citation): raise ReferenceError("URN is deeper than citation scheme") citation_start = [citation for citation in self.citation][len(start) - 1] citation_end = [citation for citation in self.citation][len(end) - 1] start, end = citation_start.fill(passage=start), citation_end.fill( passage=end) start, end = normalizeXpath(start.split("/")[2:]), normalizeXpath( end.split("/")[2:]) if isinstance(self.xml, etree._Element): root = copyNode(self.xml) else: root = copyNode(self.xml.getroot()) root = passageLoop(self.xml, root, start, end) if self.urn: urn, reference = URN("{}:{}".format(self.urn, reference)), reference else: urn, reference = None, reference return ContextPassage(urn=urn, resource=root, parent=self, citation=self.citation)
def test_get_parent(self): a = Reference("1.1") b = Reference("1") c = Reference("1.1-2.3") d = Reference("1.1-1.2") e = Reference("1.1@Something[0]-1.2@SomethingElse[2]") f = Reference("1-2") self.assertEqual(str(a.parent), "1") self.assertEqual(b.parent, None) self.assertEqual(str(c.parent), "1-2") self.assertEqual(str(d.parent), "1") self.assertEqual(str(e.parent), "1@Something[0]-1@SomethingElse[2]") self.assertEqual(f.parent, None)