Source code for MyCapytain.common.metadata

# -*- coding: utf-8 -*-
"""
.. module:: MyCapytain.common.metadata
   :synopsis: Metadata related objects

.. moduleauthor:: Thibault Clérice <leponteineptique@gmail.com>


"""
from __future__ import unicode_literals


from collections import defaultdict, OrderedDict
from past.builtins import basestring
from builtins import range, object
from copy import copy


[docs]class Metadatum(object): """ Metadatum object represent a single field of metadata :param name: Name of the field :type name: basestring :param children: List of tuples, where first element is the key, and second the value :type children: List :Example: >>> a = Metadatum(name="label", [("lat", "Amores"), ("fre", "Les Amours")]) >>> print(a["lat"]) # == "Amores" .. automethod:: __getitem__ .. automethod:: __setitem__ .. automethod:: __iter__ """ def __init__(self, name, children=None): """ Initiate a Metadatum object """ self.name = name self.children = OrderedDict() self.default = None if children is not None and isinstance(children, list): for tup in children: self[tup[0]] = tup[1]
[docs] def __getitem__(self, key): """ Add an iterable access method Int typed key access to the *n* th registered key in the instance. If string based key does not exist, see for a default. :param key: Key of wished value :type key: basestring, tuple, int :returns: An element of children whose index is key :raises: KeyError if key is unknown (when using Int based key or when default is not set) :Example: >>> a = Metadatum(name="label", [("lat", "Amores"), ("fre", "Les Amours")]) >>> print(a["lat"]) # Amores >>> print(a[("lat", "fre")]) # Amores, Les Amours >>> print(a[0]) # Amores >>> print(a["dut"]) # Amores """ if isinstance(key, int): items = list(self.children.keys()) if key + 1 > len(items): raise KeyError() else: key = items[key] elif isinstance(key, tuple): return tuple([self[k] for k in key]) if key not in self.children: if self.default is None: raise KeyError() else: return self.children[self.default] else: return self.children[key]
[docs] def __setitem__(self, key, value): """ Register index key and value for the instance :param key: Index key(s) for the metadata :type key: basestring, list, tuple :param value: Values for the metadata :type value: basestring, list, tuple :returns: An element of children whose index is key :raises: `TypeError` if key is not basestring or tuple of basestring :raises: `ValueError` if key and value are list and are not the same size :Example: >>> a = Metadatum(name="label") >>> a["eng"] = "Illiad" >>> print(a["eng"]) # Illiad >>> a[("fre", "grc")] = ("Illiade", "Ἰλιάς") >>> print(a["fre"], a["grc"]) # Illiade, Ἰλιάς >>> a[("ger", "dut")] = "Iliade" >>> print(a["ger"], a["dut"]) # Iliade, Iliade """ if isinstance(key, tuple): if not isinstance(value, (tuple, list)): value = [value]*len(key) if len(value) < len(key): raise ValueError("Less values than keys detected") for i in range(0, len(key)): self[key[i]] = value[i] elif not isinstance(key, basestring): raise TypeError( "Only basestring or tuple instances are accepted as key") else: self.children[key] = value if self.default is None: self.default = key
[docs] def setDefault(self, key): """ Set a default key when a field does not exist :param key: An existing key of the instance :type key: basestring :returns: Default key :raises: `ValueError` If key is not registered :Example: >>> a = Metadatum(name="label", [("lat", "Amores"), ("fre", "Les Amours")]) >>> a.setDefault("fre") >>> print(a["eng"]) # == "Les Amours" """ if key not in self.children: raise ValueError("Can not set a default to an unknown key") else: self.default = key return self.default
[docs] def __iter__(self): """ Iter method of Metadatum :Example: >>> a = Metadata(name="label", [("lat", "Amores"), ("fre", "Les Amours")]) >>> for key, value in a: >>> print(key, value) # Print ("lat", "Amores") and then ("fre", "Les Amours") """ i = 0 for key in self.children: yield (key, self.children[key]) i += 1
def __len__(self): """ Get the length of the current Metadatum object :return: Number of variant of the metadatum :rtype: int :Example: >>> a = Metadata(name="label", [("lat", "Amores"), ("fre", "Les Amours")]) >>> len(a) == 2 """ return len(self.children)
[docs]class Metadata(object): """ A metadatum aggregation object provided to centralize metadata :param key: A metadata field name :type key: List.<basestring> :ivar metadata: Dictionary of metadatum .. automethod:: __getitem__ .. automethod:: __setitem__ .. automethod:: __iter__ .. automethod:: __len__ .. automethod:: __add__ """ def __init__(self, keys=None): """ Initiate the object """ self.metadata = defaultdict(Metadatum) self.__keys = [] if keys is not None: for key in keys: self[key] = Metadatum(name=key)
[docs] def __getitem__(self, key): """ Add a quick access system through getitem on the instance :param key: Index key representing a set of metadatum :type key: basestring, int, tuple :returns: An element of children whose index is key :raises: `KeyError` If key is not registered or recognized :Example: >>> a = Metadata() >>> m1 = Metadatum(name="title", [("lat", "Amores"), ("fre", "Les Amours")]) >>> m2 = Metadatum(name="author", [("lat", "Ovidius"), ("fre", "Ovide")]) >>> a[("title", "author")] = (m1, m2) >>> a["title"] == m1 >>> a[0] == m1 >>> a[("title", "author")] == (m1, m2) """ if isinstance(key, int): if key + 1 > len(self.__keys): raise KeyError() else: key = self.__keys[key] elif isinstance(key, tuple): return tuple([self[k] for k in key]) if key not in self.metadata: raise KeyError() else: return self.metadata[key]
[docs] def __setitem__(self, key, value): """ Set a new metadata field :param key: Name of metadatum field :type key: basestring, tuple :param value: Metadum dictionary :type value: Metadatum :returns: An element of children whose index is key :raises: `TypeError` if key is not basestring or tuple of basestring :raises: `ValueError` if key and value are list and are not the same size :Example: >>> a = Metadata() >>> a["title"] = Metadatum(name="title", [("lat", "Amores"), ("fre", "Les Amours")]) >>> print(a["title"]["lat"]) # Amores >>> a[("title", "author")] = ( >>> Metadatum(name="title", [("lat", "Amores"), ("fre", "Les Amours")]), >>> Metadatum(name="author", [("lat", "Ovidius"), ("fre", "Ovide")]) >>> ) >>> print(a["title"]["lat"], a["author"]["fre"]) # Amores, Ovide """ if isinstance(key, tuple): if len(value) < len(key): raise ValueError("Less values than keys detected") for i in range(0, len(key)): self[key[i]] = value[i] elif not isinstance(key, basestring): raise TypeError( "Only basestring or tuple instances are accepted as key") else: if not isinstance(value, Metadatum) and isinstance(value, list): self.metadata[key] = Metadatum(key, value) elif isinstance(value, Metadatum): self.metadata[key] = value if key in self.metadata and key not in self.__keys: self.__keys.append(key)
[docs] def __iter__(self): """ Iter method of Metadata :Example: >>> a = Metadata(("title", "desc", "author")) >>> for key, value in a: >>> print(key, value) # Print ("title", "<Metadatum object>") then ("desc", "<Metadatum object>")... """ i = 0 for key in self.__keys: yield (key, self.metadata[key]) i += 1
[docs] def __add__(self, other): """ Merge Metadata objects together :param other: Metadata object to merge with the current one :type other: Metadata :returns: The merge result of both metadata object :rtype: Metadata :Example: >>> a = Metadata(name="label") >>> b = Metadata(name="title") >>> a + b == Metadata(name=["label", "title"]) """ result = copy(self) for metadata_key, metadatum in other: if metadata_key in self.__keys: for key, value in metadatum: result[metadata_key][key] = value else: result[metadata_key] = metadatum return result
[docs] def __len__(self): """ Returns the number of Metadatum registered in the object :rtype: int :returns: Number of metadatum objects :Example: >>> a = Metadata(("title", "description", "author")) >>> print(len(a)) # 3 """ return len( [ k for k in self.__keys if isinstance(self.metadata[k], Metadatum) ] )