Source code for pacifica.uploader.metadata.metadata
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""MetaData class to handle input and output of metadata format."""
from __future__ import absolute_import
import json
from collections import namedtuple
from .mjson import generate_namedtuple_encoder, generate_namedtuple_decoder
[docs]class MetaData(list):
"""
Class to hold a list of MetaObj and FileObj objects.
This class is a sub-class of ``list`` that implements
the index protocol (``__getitem__``, ``__setitem__`` and ``__delitem__``) as a proxy
to the indices of the value of the ``metaID`` field of the associated instance of
the ``pacifica.uploader.metadata.MetaObj`` class.
Instances of this class are upper-level objects that
provide the metadata for interacting with the designated
`Pacifica Ingest <https://github.com/pacifica/pacifica-ingest>`_ server.
"""
[docs] def __init__(self, *args, **kwargs):
"""Call the super constructor and add a metaID index to it as well."""
super(MetaData, self).__init__(*args, **kwargs)
self._meta_index_map = {}
if args:
for key in range(len(args[0])):
item = args[0][key]
if getattr(item, 'metaID', False):
self._meta_index_map[item.metaID] = key
def __delitem__(self, key):
"""Delete the item from the array and hash."""
item = self[key]
super(MetaData, self).__delitem__(key)
if getattr(item, 'metaID', False):
del self._meta_index_map[item.metaID]
def __setitem__(self, key, value):
"""Set the item and if metaID exists save the index into a map."""
old_val = self[key]
if isinstance(key, int):
true_key = key
else:
if getattr(value, 'metaID', False):
true_key = int(self._meta_index_map[old_val.metaID])
else:
raise IndexError('No metaID {}'.format(
getattr(value, 'metaID', False)))
super(MetaData, self).__setitem__(true_key, value)
if getattr(old_val, 'metaID', False):
del self._meta_index_map[old_val.metaID]
if getattr(value, 'metaID', False):
self._meta_index_map[value.metaID] = true_key
def __getitem__(self, key):
"""Get the node based on metaID."""
if isinstance(key, int):
return super(MetaData, self).__getitem__(key)
if key in self._meta_index_map:
return self[self._meta_index_map[key]]
raise IndexError('No such key {}'.format(key))
[docs] def append(self, value):
"""Append the value to the list."""
super(MetaData, self).append(value)
if getattr(value, 'metaID', False):
self._meta_index_map[value.metaID] = len(self) - 1
[docs] def extend(self, iterable):
"""Extend the array from the values in iterable."""
for value in iterable:
self.append(value)
[docs] def remove(self, value):
"""Remove the value from the list."""
super(MetaData, self).remove(value)
if getattr(value, 'metaID', False):
del self._meta_index_map[value.metaID]
[docs] def pop(self, key=-1):
"""Remove the key from the list and return it."""
if key == -1:
key = len(self) - 1
value = super(MetaData, self).pop(key)
if getattr(value, 'metaID', False):
del self._meta_index_map[value.metaID]
return value
[docs] def insert(self, key, value):
"""Insert the value to the list."""
super(MetaData, self).insert(key, value)
for ikey, ivalue in self._meta_index_map.items():
if ivalue >= key:
self._meta_index_map[ikey] = ivalue + 1
if getattr(value, 'metaID', False):
self._meta_index_map[value.metaID] = key
[docs] def is_valid(self):
"""Return true if all the values of MetaObjs are something."""
return {bool(obj.value) for obj in self if isinstance(obj, MetaObj)} == {True}
META_KEYS = [
'sourceTable',
'destinationTable',
'metaID',
'displayType',
'displayTitle',
'queryDependency',
'valueField',
'queryFields',
'displayFormat',
'key',
'value',
'directoryOrder',
'query_results'
]
_MetaObj = namedtuple('MetaObj', META_KEYS)
# Set the defaults to None for these attributes
_MetaObj.__new__.__defaults__ = (None,) * len(_MetaObj._fields)
[docs]class MetaObj(_MetaObj):
"""
MetaObj class holding a specific metadata element.
Instances of this class represent units of metadata
whose representation is disjoint to a file, i.e., units of metadata that are
describe but are not stored as part of a file.
"""
FILE_KEYS = [
'destinationTable',
'name',
'source',
'subdir',
'size',
'hashtype',
'hashsum',
'mimetype',
'ctime',
'mtime'
]
_FileObj = namedtuple('FileObj', FILE_KEYS)
# Set the defaults to None for these attributes
_FileObj.__new__.__defaults__ = (None,) * len(_FileObj._fields)
[docs]class FileObj(_FileObj):
"""
FileObj class for holding file metadata.
Instances of this class represent individual files,
including both the data and metadata for the file. During a file upload,
instances of this class are automatically associated
with new instances of the ``pacifica.uploader.metadata.MetaData`` class.
The above named fields are identical to those of the ``pacifica.metadata.orm.Files`` class,
provided by the
`Pacifica Metadata <https://github.com/pacifica/pacifica-metadata>`_ library.
"""
[docs]def file_or_meta_obj(**json_data):
"""Determine if this is a File or Meta object and return result."""
if json_data.get('destinationTable') == 'Files':
return FileObj(**json_data)
return MetaObj(**json_data)
MetaObjEncoder = generate_namedtuple_encoder(MetaObj)
FileObjEncoder = generate_namedtuple_encoder(FileObj)
MetaObjDecoder = generate_namedtuple_decoder(file_or_meta_obj)
[docs]class MetaDataEncoder(json.JSONEncoder):
"""Class to encode a MetaData object into json."""
[docs] def encode(self, o):
"""Encode the MetaData object into a json list."""
if isinstance(o, MetaData):
json_parts = []
for mobj in o:
if not hasattr(mobj, 'destinationTable'):
return json.JSONEncoder.default(self, mobj)
encoder_class = FileObjEncoder if mobj.destinationTable == 'Files' else MetaObjEncoder
json_parts.append(json.loads(
json.dumps(mobj, cls=encoder_class)))
return json.dumps(json_parts)
return json.JSONEncoder.default(self, o)
[docs]class MetaDataDecoder(json.JSONDecoder):
"""Class to decode a json string into a MetaData object."""
# pylint: disable=arguments-differ
[docs] def decode(self, s):
"""Decode the string into a MetaData object."""
json_data = json.loads(s)
if isinstance(json_data, list):
return MetaData([MetaObjDecoder().decode(json.dumps(obj)) for obj in json_data])
raise TypeError('Unable to turn {} into a list'.format(s))
[docs]def metadata_decode(json_str):
"""
Decode the json string into MetaData object.
This method deserializes the given
JSON source, ``json_str``, and then returns a new instance of the
``pacifica.uploader.metadata.MetaData`` class.
The new instance is automatically associated with new instances of the
``pacifica.uploader.metadata.MetaObj`` and ``pacifica.uploader.metadata.FileObj`` classes.
"""
return json.loads(json_str, cls=MetaDataDecoder)
[docs]def metadata_encode(md_obj):
"""
Encode the MetaData object into a json string.
This method encodes the given
instance of the ``pacifica.uploader.metadata.MetaData`` class, ``md_obj``, as a JSON object,
and then returns its JSON serialization.
Associated instances of the ``pacifica.uploader.metadata.MetaObj`` and
``pacifica.uploader.metadata.FileObj`` classes are automatically included in the JSON
object and the resulting JSON serialization.
"""
return json.dumps(md_obj, cls=MetaDataEncoder)