def load_meta(img_file: Path) -> ImageMetadata: """ Load the metadata tuple for a given image file. If no metadata file is present, or it is currently inaccessible, return a blank metadata tuple. :arg img_file: a path pointing to a managed image for which we want to load metadata :return: the associated metadata as a tuple, or a blank metadata tuple """ meta_file = _construct_metadata_path(img_file) if meta_file.exists(): try: with meta_file.open() as mf: metadata = parse_xml(mf.read()) # Check if 'file' is a valid URI, otherwise make it so (for retro-compatibility with older schema) if metadata.file.scheme is None: metadata = _old_to_new_schema(img_file, metadata) except (OSError, ParseError): metadata = ImageMetadata(uuid3(NAMESPACE_URL, str(URI(img_file))), URI(img_file), None, None, None, None) else: metadata = ImageMetadata(uuid3(NAMESPACE_URL, str(URI(img_file))), URI(img_file), None, None, None, None) return metadata
def test_collective_disjunctive_filter(self): # Verify the effectiveness of multi-valued matchers, when evaluated in disjunction chars1 = ["al", "john", "jack"] chars2 = ["jm", "jr"] chars3 = ["jr"] el1 = ImageMetadata(uuid4(), URI("a.png"), "ghi", None, chars1, None) el2 = ImageMetadata(uuid4(), URI("b.png"), "nsh", None, chars2, None) el3 = ImageMetadata(uuid4(), URI("c.png"), "ShT", None, chars3, None) filter_builder = FilterBuilder() # Test disjunctive filtering with inclusion f = filter_builder.character_constraint("jm").character_constraint("al").characters_as_disjunctive(True) \ .get_character_filter() self.assertTrue(f(el1)) self.assertTrue(f(el2)) self.assertFalse(f(el3)) # Test disjunctive filtering with exclusion f = filter_builder.character_constraint("jack", True).get_character_filter() self.assertTrue(f(el1)) self.assertTrue(f(el2)) self.assertTrue(f(el3)) filter_builder = FilterBuilder() f = filter_builder.characters_as_disjunctive(True).character_constraint("john", True) \ .character_constraint("jack", True).get_character_filter() self.assertFalse(f(el1)) self.assertTrue(f(el2)) self.assertTrue(f(el3))
def test_single_element_filter(self): # Verify the effectiveness of single-valued matchers id1, filename1, author1, universe1 = uuid4(), URI("01.png"), "bdhnd", "fotwf" id2, filename2, author2, universe2 = uuid4(), URI("02.png"), "shndl", None id3, filename3, author3, universe3 = uuid4(), URI("03.png"), "okn", "ph" el1 = ImageMetadata(id1, filename1, author1, universe1, None, None) el2 = ImageMetadata(id2, filename2, author2, universe2, None, None) el3 = ImageMetadata(id3, filename3, author3, universe3, None, None) filter_builder = FilterBuilder() # Test constraints satisfied filter_builder.filename_constraint(filename1.path.name) \ .filename_constraint(filename2.path.name) \ .filename_constraint(filename3.path.name) filename_filter = filter_builder.get_filename_filter() self.assertTrue(filename_filter(el1)) self.assertTrue(filename_filter(el2)) self.assertTrue(filename_filter(el3)) # Test implicit exclusion filter_builder.author_constraint(author1) author_filter = filter_builder.get_author_filter() self.assertTrue(author_filter(el1)) self.assertFalse(author_filter(el2)) self.assertFalse(author_filter(el3)) # Test explicit exclusion filter_builder.id_constraint(str(id2), True) id_filter = filter_builder.get_id_filter() self.assertTrue(id_filter(el1)) self.assertFalse(id_filter(el2)) self.assertTrue(id_filter(el3))
def test_empty_filter_single(self): # Check the effects of the absence of constraints on single-valued matchers el1 = ImageMetadata(uuid4(), URI('xxx'), None, None, None, None) el2 = ImageMetadata(uuid4(), URI('kkk'), None, None, None, None) empty_filter = FilterBuilder().get_id_filter() self.assertTrue(empty_filter(el1)) self.assertTrue(empty_filter(el2))
def test_none_match_collective(self): # Check the effects of None constraints on multi-valued matchers el1 = ImageMetadata(uuid4(), URI('aaa'), None, None, None, None) el2 = ImageMetadata(uuid4(), URI('yyy'), None, None, None, ["fta"]) none_filter = FilterBuilder().tag_constraint(None).get_tag_filter() self.assertTrue(none_filter(el1)) self.assertFalse(none_filter(el2))
def test_empty_filter_collective(self): # Check the effects of the absence of constraints on multi-valued matchers el1 = ImageMetadata(uuid4(), URI('xxx'), None, None, None, ["nl", "ll"]) el2 = ImageMetadata(uuid4(), URI('kkk'), None, None, None, None) empty_filter = FilterBuilder().get_tag_filter() self.assertTrue(empty_filter(el1)) self.assertTrue(empty_filter(el2))
def test_none_match_single(self): # Check the effects of None constraints on single-valued matchers el1 = ImageMetadata(uuid4(), URI('fff'), None, 'u', None, None) el2 = ImageMetadata(uuid4(), URI('zzz'), None, None, None, None) none_filter = FilterBuilder().universe_constraint(None).get_universe_filter() self.assertFalse(none_filter(el1)) self.assertTrue(none_filter(el2))
def test_without_optional_elements(self): img_id = uuid.UUID('03012ba3-086c-4604-bd6a-aa3e1a78f389') file = uri.URI("test.png") self.assertEqual( ImageMetadata(img_id, file, None, None, None, None), xm.parse_xml( xm.generate_xml( ImageMetadata(img_id, file, None, None, None, None))))
def test_with_optional_elements(self): img_id = uuid.UUID('03012ba3-086c-4604-bd6a-aa3e1a78f389') file = uri.URI("test.png") author = "John Doe" universe = "Example" characters = ["M", "Q"] tags = ["a", "bee"] self.assertEqual( ImageMetadata(img_id, file, author, universe, characters, tags), xm.parse_xml( xm.generate_xml( ImageMetadata(img_id, file, author, universe, characters, tags))))
def _old_to_new_schema(img_path: Path, old_meta: ImageMetadata): return ImageMetadata(img_id=old_meta.img_id, file=URI(img_path), author=old_meta.author, universe=old_meta.universe, characters=old_meta.characters, tags=old_meta.tags)
def parse_xml(data: str) -> ImageMetadata: """Parse an XML containing image metadata. :param data: a string containing valid image metadata :return: an image metadata object""" image_elem = ElTree.fromstring(data) img_id = image_elem.get('id') file = image_elem.get('file') # If we were presented with a legacy XML not containing 'file', use the legacy name 'filename' if file is None: file = image_elem.get('filename') author = image_elem.find("./author") universe = image_elem.find("./universe") characters = [ char.text for char in image_elem.findall("./characters/character") ] tags = [tag.text for tag in image_elem.findall("./tags/tag")] return ImageMetadata( img_id=UUID(img_id), file=URI(file), author=author.text if author is not None else None, universe=universe.text if universe is not None else None, characters=characters if len(characters) != 0 else None, tags=tags if len(tags) != 0 else None)
def test_generate_minimal(self): img_id = uuid.UUID('03012ba3-086c-4604-bd6a-aa3e1a78f389') file = uri.URI("test.png") minimal_xml = xm.generate_xml( ImageMetadata(img_id, file, None, None, None, None)) expected = '<image id="{i}" file="{f}" />'.format(i=img_id, f=file) self.assertEqual(expected, minimal_xml)
def meta_extractor(v: View) -> ImageMetadata: characters = v.get_characters() characters = characters.split(', ') if characters is not None else None tags = v.get_tags() tags = tags.split(', ') if tags is not None else None return ImageMetadata(v.image_id, URI(v._image_path), v.get_author(), v.get_universe(), characters, tags)
def write(self) -> None: """ Persist the updated metadata. :raise OSError: when the metadata file couldn't be opened """ meta_obj = ImageMetadata(self._id, URI(self._image_path), self.author, self.universe, self.characters, self.tags) write_meta(meta_obj, self._image_path)
def test_legacy_load(self): image_uri = URI(self.test_path / "test.png") with (self.test_path / "test.xml").open('w') as f: f.write( "<image id=\"97ed6183-73a0-46ea-b51d-0721b0fbd357\" filename=\"test.png\"></image>" ) loaded = load_meta(Path(image_uri.path)) self.assertEqual( ImageMetadata(UUID('97ed6183-73a0-46ea-b51d-0721b0fbd357'), image_uri, None, None, None, None), loaded)
def test_load_actual(self): image_uri = URI(self.test_path / "test.png") with (self.test_path / "test.xml").open('w') as f: f.write( "<image id=\"97ed6183-73a0-46ea-b51d-0721b0fbd357\" file=\"" + str(image_uri) + "\">" + "<author>a</author><universe>u</universe>" + "<characters><character>x</character><character>y</character></characters>" + "<tags><tag>f</tag><tag>a</tag></tags></image>") self.assertEqual( ImageMetadata(UUID('97ed6183-73a0-46ea-b51d-0721b0fbd357'), image_uri, "a", "u", ["x", "y"], ["f", "a"]), load_meta(Path(self.test_dir.name) / "test.png"))
def test_collective_conjunctive_filter(self): # Verify the effectiveness of multi-valued matchers, when evaluated in conjunction tags1 = ["y", "an", "hry"] tags2 = ["an", "mb", "sty", "rp"] tags3 = ["ll", "vnl"] el1 = ImageMetadata(uuid4(), URI("a.png"), "ghi", None, None, tags1) el2 = ImageMetadata(uuid4(), URI("b.png"), "nsh", None, None, tags2) el3 = ImageMetadata(uuid4(), URI("c.png"), "ShT", None, None, tags3) filter_builder = FilterBuilder() # Test conjunctive filtering with inclusion f = filter_builder.tag_constraint("an").get_tag_filter() self.assertTrue(f(el1)) self.assertTrue(f(el2)) self.assertFalse(f(el3)) # Test conjunctive filtering with exclusion f = filter_builder.tag_constraint("y", True).get_tag_filter() self.assertFalse(f(el1)) self.assertTrue(f(el2)) self.assertFalse((f(el3)))
def test_filter(self): filenames_meta = [("included.png", "included"), ("excluded.jpg", "excluded")] for name, meta in filenames_meta: img_path = (Path(self.test_dir.name) / name) img_path.touch() # Use the author field as the filtering target write_meta(ImageMetadata(uuid4(), name, meta, None, None, None), img_path) specimen = Carousel(Path(self.test_dir.name), [lambda meta: meta.author == "included"]) self.assertEqual([Path(self.test_dir.name) / "included.png"], specimen._image_files)
def test_metadata_read(self): # Write some metadata for one of the images meta1 = ImageMetadata( img_id=UUID('f32ed6ad-1162-4ea6-b243-1e6c91fb7eda'), file=URI(self.test_path / '01.png'), author="a", universe="p", characters=["x", "y"], tags=["t", "f"]) write_meta(meta1, (self.test_path / '01.png')) specimen = GtkView(self.test_path) # Collect metadata from the specimen results = dict() for _ in range(0, 2): specimen.load_next() results[specimen.filename] = TestView.meta_extractor(specimen) self.assertEqual(meta1, results["01.png"]) self.assertEqual( ImageMetadata(results["02.png"].img_id, URI(self.test_path / "02.png"), None, None, None, None), results["02.png"])
def test_store(self): image_uri = URI(self.test_path / "test.png") write_meta( ImageMetadata(UUID('97ed6183-73a0-46ea-b51d-0721b0fbd357'), image_uri, "a", "u", ["x", "y"], ["f", "a"]), Path(self.test_dir.name) / "test.png") with (self.test_path / "test.xml") as f: result = f.read_text() self.assertEqual( "<image id=\"97ed6183-73a0-46ea-b51d-0721b0fbd357\" file=\"" + str(image_uri) + "\">" + "<author>a</author><universe>u</universe>" + "<characters><character>x</character><character>y</character></characters>" + "<tags><tag>f</tag><tag>a</tag></tags></image>", result)
def test_generate_single_element_and_subtree(self): img_id = uuid.UUID('03012ba3-086c-4604-bd6a-aa3e1a78f389') file = uri.URI("test.png") universe = "unknown" tags = ["a", "b"] new_xml = xm.generate_xml( ImageMetadata(img_id, file, None, universe, None, tags)) expected = ('<image id="{i}" file="{f}">' + '<universe>{u}</universe>' + '<tags><tag>{t1}</tag><tag>{t2}</tag></tags>' + '</image>').format(i=img_id, f=file, u=universe, t1=tags[0], t2=tags[1]) self.assertEqual(expected, new_xml)
def test_metadata_update(self): target_filename = '02.png' specimen = GtkView(self.test_path) # Scan until we find our target specimen.load_next() while specimen.filename != target_filename: specimen.load_next() # Verify that no metadata is present self.assertIsNone(specimen.get_tags()) # Set metadata and check coherence specimen.set_author(":DD") specimen.set_universe("\tu") specimen.set_characters("3, f,p\n") specimen.set_tags("fa,s \vjo,\u200dl") specimen.write() self.assertEqual( ImageMetadata(specimen.image_id, URI(self.test_path / target_filename), ":DD", "u", ["3", "f", "p"], ["fa", "s jo", "l"]), load_meta(self.test_path / target_filename))
def test_parse_legacy(self): xml_string_legacy = "<image id=\"03012ba3-086c-4604-bd6a-aa3e1a78f389\" filename=\"tt.png\"></image>" metadata = ImageMetadata( uuid.UUID('03012ba3-086c-4604-bd6a-aa3e1a78f389'), uri.URI("tt.png"), None, None, None, None) self.assertEqual(metadata, xm.parse_xml(xml_string_legacy))