Source code for meteorpi_fdb.generators

__author__ = 'tom'
import uuid
from contextlib import closing

from yaml import safe_load
import os.path as path
import meteorpi_model as mp
from backports.functools_lru_cache import lru_cache


[docs]def first_from_generator(generator): """Pull the first value from a generator and return it, closing the generator :param generator: A generator, this will be mapped onto a list and the first item extracted. :return: None if there are no items, or the first item otherwise. :internal: """ try: result = next(generator) except StopIteration: result = None finally: generator.close() return result
[docs]def first_non_null(values): """ Retrieve the first, non-null item in the specified list :param values: a list of values from which the first non-null is returned :return: the first non-null item :raises: ValueError if there isn't any such item in the list. """ for item in values: if item is not None: return item raise ValueError("No non-null item in supplied list.")
[docs]class MeteorDatabaseGenerators(object): """ Generator functions used to retrieve, and cache, items from the database. """
[docs] def __init__(self, db): self.con = db.con self.db = db
[docs] def cache_info(self): """ Retrieve cache info for the LRU caches used by this database :return: A dict of cache name (status, event, file, export, user) to cache info """ return {'status': self._get_camera_status_with_cache.cache_info(), 'event': self._get_event_with_cache.cache_info(), 'file': self._get_file_with_cache.cache_info(), 'export': self._get_export_configuration_with_cache.cache_info()}
[docs] def cache_clear(self, cache): """ Clear a named cache :param cache: The cache to clear, one of (status, event, file, export, user) """ if cache == 'file': self._get_file_with_cache.cache_clear() elif cache == 'event': self._get_event_with_cache.cache_clear() elif cache == 'export': self._get_event_with_cache.cache_clear() elif cache == 'status': self._get_camera_status_with_cache.cache_clear() else: raise ValueError("Unknown cache {0}, must be one of [file,event,export,status]".format(cache))
[docs] def file_generator(self, sql, sql_args): """Generator for FileRecord :param sql: A SQL statement which must return rows with, in order, internal ID, camera ID, mime type, semantic type, file time, file size, file ID, file name and file status ID. :param sql_args: Any variables required to populate the query provided in 'sql' :return: A generator which produces FileRecord instances from the supplied SQL, closing any opened cursors on completion. """ with closing(self.con.cursor()) as cursor: cursor.execute(sql, sql_args) for (internalID, cameraID, mimeType, semanticType, fileTime, fileSize, fileID, fileName, statusID, md5Hex) in cursor: yield self._get_file_with_cache(internalID, cameraID, mimeType, semanticType, fileTime, fileSize, fileID, fileName, statusID, md5Hex)
[docs] def event_generator(self, sql, sql_args): """Generator for Event :param sql: A SQL statement which must return rows with, in order: camera ID, event ID, internal ID, event time, event semantic type, status ID :param sql_args: Any variables required to populate the query provided in 'sql' :return: A generator which produces Event instances from the supplied SQL, closing any opened cursors on completion. """ with closing(self.con.cursor()) as cursor: cursor.execute(sql, sql_args) for (cameraID, eventID, internalID, eventTime, eventType, statusID) in cursor: yield self._get_event_with_cache(cameraID, eventID, internalID, eventTime, eventType, statusID)
[docs] def camera_status_generator(self, sql, sql_args): """ Generator for :class:`meteorpi_model.CameraStatus` :param sql: A SQL statement which must return rows with, in order: lens, sensor, instURL, instName, locationLatitude, locationLongitude, locationGPS, locationError, orientationAltitude, orientationAzimuth, orientationError, orientationRotation, widthOfField, validFrom, softwareVersion, internalID, statusID, cameraID :param sql_args: Any arguments required to populate the query provided in 'sql' :return: A generator which produces :class:`meteorpi_model.CameraStatus` instances from the supplied SQL, closing any opened cursors on completion """ with closing(self.con.cursor()) as cursor: cursor.execute(sql, sql_args) for (lens, sensor, instURL, instName, locationLatitude, locationLongitude, locationGPS, locationError, orientationAltitude, orientationAzimuth, orientationError, orientationRotation, widthOfField, validFrom, softwareVersion, internalID, statusID, cameraID) in cursor: cs = self._get_camera_status_with_cache(lens, sensor, instURL, instName, locationLatitude, locationLongitude, locationGPS, locationError, orientationAltitude, orientationAzimuth, orientationError, orientationRotation, widthOfField, validFrom, softwareVersion, internalID, statusID, cameraID) yield cs
[docs] def export_configuration_generator(self, sql, sql_args): """ Generator for :class:`meteorpi_model.ExportConfiguration` :param sql: A SQL statement which must return rows with, in order: internalID, exportConfigID, exportType, searchString, targetURL, targetUser, targetPassword, exportName, description, active :param sql_args: Any variables required to populate the query provided in 'sql' :return: A generator which produces :class:`meteorpi_model.ExportConfiguration` instances from the supplied SQL, closing any opened cursors on completion. """ with closing(self.con.cursor()) as cursor: cursor.execute(sql, sql_args) for (internalID, exportConfigID, exportType, searchString, targetURL, targetUser, targetPassword, exportName, description, active) in cursor: yield self._get_export_configuration_with_cache(internalID, exportConfigID, exportType, searchString, targetURL, targetUser, targetPassword, exportName, description, active)
@lru_cache(maxsize=128) def _get_file_with_cache(self, internalID, cameraID, mimeType, semanticType, fileTime, fileSize, fileID, fileName, statusID, md5Hex): fr = mp.FileRecord( camera_id=cameraID, mime_type=mimeType, semantic_type=mp.NSString.from_string(semanticType), status_id=uuid.UUID(bytes=statusID), md5=md5Hex) fr.file_id = uuid.UUID(bytes=fileID) fr.file_size = fileSize fr.file_time = mp.milliseconds_to_utc_datetime(fileTime) fr.file_name = fileName fr.get_path = lambda: path.join(self.db.file_store_path, fr.file_id.hex) with closing(self.con.cursor()) as meta_cur: meta_cur.execute( 'SELECT metaKey, stringValue, floatValue, dateValue ' 'FROM t_fileMeta t ' 'WHERE t.fileID = (?) ' 'ORDER BY metaIndex ASC', (internalID,)) for (metaKey, stringValue, floatValue, dateValue) in meta_cur: fr.meta.append( mp.Meta(key=mp.NSString.from_string(metaKey), value=first_non_null( [stringValue, floatValue, mp.milliseconds_to_utc_datetime(dateValue)]))) return fr @lru_cache(maxsize=128) def _get_event_with_cache(self, cameraID, eventID, internalID, eventTime, eventType, statusID): event = mp.Event( camera_id=cameraID, event_time=mp.milliseconds_to_utc_datetime(eventTime), event_id=uuid.UUID(bytes=eventID), event_type=mp.NSString.from_string(eventType), status_id=uuid.UUID(bytes=statusID)) fr_sql = 'SELECT f.internalID, f.cameraID, f.mimeType, ' \ 'f.semanticType, f.fileTime, f.fileSize, f.fileID, f.fileName, s.statusID, f.MD5HEX ' \ 'FROM t_file f, t_cameraStatus s, t_event_to_file ef ' \ 'WHERE f.statusID = s.internalID AND ef.fileID = f.internalID AND ef.eventID = (?)' event.file_records = list(self.file_generator(fr_sql, (internalID,))) with closing(self.con.cursor()) as meta_cur: meta_cur.execute( 'SELECT metaKey, stringValue, floatValue, dateValue ' 'FROM t_eventMeta t ' 'WHERE t.eventID = (?) ' 'ORDER BY metaIndex ASC', (internalID,)) for (metaKey, stringValue, floatValue, dateValue) in meta_cur: event.meta.append( mp.Meta(key=mp.NSString.from_string(metaKey), value=first_non_null( [stringValue, floatValue, mp.milliseconds_to_utc_datetime(dateValue)]))) return event @lru_cache(maxsize=128) def _get_camera_status_with_cache(self, lens, sensor, instURL, instName, locationLatitude, locationLongitude, locationGPS, locationError, orientationAltitude, orientationAzimuth, orientationError, orientationRotation, widthOfField, validFrom, softwareVersion, internalID, statusID, cameraID): # Find if there's a status block for this camera ID after the current one, and use it's validFrom time # as the validTo time on the camera status if so with closing(self.con.cursor()) as valid_to_cursor: valid_to_cursor.execute( 'SELECT validFrom FROM t_cameraStatus t ' 'WHERE t.cameraID = (?) AND t.validFrom > (?) ' 'ORDER BY t.validFrom ASC ' 'ROWS 1', (cameraID, validFrom)) after_row = valid_to_cursor.fetchone() if after_row is None: validTo = None else: validTo = mp.milliseconds_to_utc_datetime(after_row[0]) cs = mp.CameraStatus(lens=lens, sensor=sensor, inst_url=instURL, inst_name=instName, orientation=mp.Orientation( altitude=orientationAltitude, azimuth=orientationAzimuth, rotation=orientationRotation, error=orientationError, width_of_field=widthOfField), location=mp.Location( latitude=locationLatitude, longitude=locationLongitude, gps=locationGPS is True, error=locationError), camera_id=cameraID, status_id=uuid.UUID(bytes=statusID)) cs.valid_from = mp.milliseconds_to_utc_datetime(validFrom) cs.valid_to = validTo cs.software_version = softwareVersion with closing(self.con.cursor()) as region_cursor: region_cursor.execute('SELECT region, x, y FROM t_visibleRegions t ' 'WHERE t.cameraStatusID = (?) ' 'ORDER BY region ASC, pointOrder ASC', (internalID,)) for (region, x, y) in region_cursor: if len(cs.regions) <= region: cs.regions.append([]) cs.regions[region].append( {'x': x, 'y': y}) return cs @lru_cache(maxsize=128) def _get_export_configuration_with_cache(self, internalID, exportConfigID, exportType, searchString, targetURL, targetUser, targetPassword, exportName, description, active): if exportType == "event": search = mp.EventSearch.from_dict(safe_load(searchString)) elif exportType == "file": search = mp.FileRecordSearch.from_dict(safe_load(searchString)) else: raise ValueError("Unknown search type!") return mp.ExportConfiguration(target_url=targetURL, user_id=targetUser, password=targetPassword, search=search, name=exportName, description=description, enabled=active, config_id=uuid.UUID(bytes=exportConfigID))