From 5533b1ccbf90514981e426fa8ddc92c617611039 Mon Sep 17 00:00:00 2001 From: Billy Charlton Date: Thu, 31 Oct 2013 15:06:38 -0700 Subject: [PATCH 01/17] Create structure for multiple language APIs --- README.md | 0 omx/Exceptions.py | 3 + omx/File.py | 228 +++++++++++++++++++++++++++++++++++++++++ omx/__init__.py | 42 ++++++++ omx/tests/test_file.py | 78 ++++++++++++++ 5 files changed, 351 insertions(+) create mode 100644 README.md create mode 100644 omx/Exceptions.py create mode 100644 omx/File.py create mode 100644 omx/__init__.py create mode 100644 omx/tests/test_file.py diff --git a/README.md b/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/omx/Exceptions.py b/omx/Exceptions.py new file mode 100644 index 0000000000..4688e2ebdd --- /dev/null +++ b/omx/Exceptions.py @@ -0,0 +1,3 @@ +class ShapeError(Exception): + pass + diff --git a/omx/File.py b/omx/File.py new file mode 100644 index 0000000000..1ee80e47c4 --- /dev/null +++ b/omx/File.py @@ -0,0 +1,228 @@ +# OMX package +# release 1 + +import tables # requires pytables >= 2.4 + +from Exceptions import * + + +class File(tables.File): + + def __init__(self, f,m,t,r,f1, **kwargs): + tables.File.__init__(self,f,m,t,r,f1,**kwargs) + self._shape = None + + def version(self): + if 'OMX_VERSION' in self.root._v_attrs: + return self.root._v_attrs['OMX_VERSION'] + else: + return None + + + def createMatrix(self, name, atom=None, shape=None, title='', filters=None, + chunkshape=None, byteorder=None, createparents=False, obj=None, + attrs=None): + """Create OMX Matrix (CArray) at root level. User must pass in either + an existing numpy matrix, or a shape and an atom type.""" + + # If object was passed in, make sure its shape is correct + if self.shape() != None and obj != None and obj.shape != self.shape(): + raise ShapeError('%s has shape %s but this file requires shape %s' % + (name, obj.shape, self.shape())) + + if tables.__version__.startswith('3'): + matrix = self.createCArray(self.root.data, name, atom, shape, title, filters, + chunkshape, byteorder, createparents, obj) + else: + # this version is tables 2.4-compatible: + matrix = self.createCArray(self.root.data, name, atom, shape, title, filters, + chunkshape, byteorder, createparents) + if (obj != None): + matrix[:] = obj + + # attributes + if attrs: + for key in attrs: + matrix.attrs[key] = attrs[key] + + return matrix + + def shape(self): + """Return the one and only shape of all matrices in this File""" + + # If we already have the shape, just return it + if self._shape: + return self._shape + + # If shape is already set in root node attributes, grab it + if 'SHAPE' in self.root._v_attrs: + self._shape = self.root._v_attrs['SHAPE'] + return self._shape + + # Inspect the first CArray object to determine its shape + if len(self) > 0: + self._shape = self.iterNodes(self.root.data,'CArray').next().shape + + # Store it if we can + if self._isWritable(): + self.root._v_attrs['SHAPE'] = self._shape + + return self._shape + + else: + return None + + + def listMatrices(self): + """Return list of Matrix names in this File""" + return [node.name for node in self.listNodes(self.root.data,'CArray')] + + + def listAllAttributes(self): + """Return combined list of all attributes used for any Matrix in this File""" + all_tags = set() + for m in self.listNodes(self.root,'CArray'): + if m.attrs != None: + all_tags.update(m.attrs._v_attrnames) + return sorted(list(all_tags)) + + + # MAPPINGS ----------------------------------------------- + def listMappings(self): + try: + return [m.name for m in self.listNodes(self.root.lookup)] + except: + return [] + + + def deleteMapping(self, title): + try: + self.removeNode(self.root.lookup, title) + except: + raise LookupError('No such mapping: '+title) + + + def mapping(self, title): + """Return dict containing key:value pairs for specified mapping. Keys + represent the map item and value represents the array offset.""" + try: + # fetch entries + entries = [] + entries.extend(self.getNode(self.root.lookup, title)[:]) + + # build reverse key-lookup + keymap = {} + for i in range(len(entries)): + keymap[entries[i]] = i + + return keymap + + except: + raise LookupError('No such mapping: '+title) + + def mapentries(self, title): + """Return entries[] with key for each array offset.""" + try: + # fetch entries + entries = [] + entries.extend(self.getNode(self.root.lookup, title)[:]) + + return (keymap,entries) + + except: + raise LookupError('No such mapping: '+title) + + + def createMapping(self, title, entries, overwrite=False): + """Create an equivalency index, which maps a raw data dimension to + another integer value. Once created, mappings can be referenced by + offset or by key.""" + + # Enforce shape-checking + if self.shape(): + if not len(entries) in self._shape: + raise ShapeError('Mapping must match one data dimension') + + # Handle case where mapping already exists: + if title in self.listMappings(): + if overwrite: + self.deleteMapping(title) + else: + raise LookupError(title+' mapping already exists.') + + # Create lookup group under root if it doesn't already exist. + if 'lookup' not in self.root: + self.createGroup(self.root, 'lookup') + + # Write the mapping! + mymap = self.createArray(self.root.lookup, title, atom=tables.UInt16Atom(), + shape=(len(entries)) ) + mymap[:] = entries + + return mymap + + + # The following functions implement Python list/dictionary lookups. ---- + def __getitem__(self,key): + """Return a matrix by name, or a list of matrices by attributes""" + + if isinstance(key, str): + return self.getNode(self.root.data, key) + + if 'keys' not in dir(key): + raise LookupError('Key %s not found' % key) + + # Loop through key/value pairs + mats = self.listNodes(self.root.data, 'CArray') + for a in key.keys(): + mats = self._getMatricesByAttribute(a, key[a], mats) + + return mats + + + def _getMatricesByAttribute(self, key, value, matrices=None): + + answer = [] + + if matrices==None: + matrices = self.listNodes(self.root.data,'CArray') + + for m in matrices: + if m.attrs == None: continue + + # Only test if key is present in matrix attributes + if key in m.attrs._v_attrnames and m.attrs[key] == value: + answer.append(m) + + return answer + + + def __len__(self): + return len(self.listNodes(self.root.data, 'CArray')) + + + def __setitem__(self, key, dataset): + # We need to determine atom and shape from the object that's been passed in. + # This assumes 'dataset' is a numpy object. + atom = tables.Atom.from_dtype(dataset.dtype) + shape = dataset.shape + + #checks to see if it is already a tables instance, and if so, just copies it + if dataset.__class__.__name__ == 'CArray': + return dataset.copy(self.root.data, key) + else: + return self.createMatrix(key, atom, shape, obj=dataset) + + + def __delitem__(self, key): + self.removeNode(self.root.data, key) + + + def __iter__(self): + """Iterate over the keys in this container""" + return self.iterNodes(self.root.data, 'CArray') + + + def __contains__(self, item): + return item in self.root.data._v_children + diff --git a/omx/__init__.py b/omx/__init__.py new file mode 100644 index 0000000000..ed3eb59568 --- /dev/null +++ b/omx/__init__.py @@ -0,0 +1,42 @@ +# OMX package +# release 1 + +import tables +from File import * +from Exceptions import * + +# GLOBAL VARIABLES ----------- +__version__ = '0.2' + +# GLOBAL FUNCTIONS ----------- +def openFile(filename, mode='r', title='', root_uep='/', + filters=tables.Filters(complevel=1,shuffle=True,fletcher32=False,complib='zlib'), + shape=None, **kwargs): + """Open or create a new OMX file. New files will be created with default + zlib compression enabled.""" + + f = File(filename, mode, title, root_uep, filters, **kwargs); + + # add omx structure if file is writable + if mode != 'r': + # version number + if 'OMX_VERSION' not in f.root._v_attrs: + f.root._v_attrs['OMX_VERSION'] = __version__ + + # shape + if shape: + f.root._v_attrs['SHAPE'] = shape + + # /data and /lookup folders + if 'data' not in f.root: + f.createGroup(f.root,"data") + if 'lookup' not in f.root: + f.createGroup(f.root,"lookup") + + return f + + +if __name__ == "__main__": + print 'OMX!' + + diff --git a/omx/tests/test_file.py b/omx/tests/test_file.py new file mode 100644 index 0000000000..d0101a1e09 --- /dev/null +++ b/omx/tests/test_file.py @@ -0,0 +1,78 @@ +import tables,omx,numpy,os +from nose.tools import * + +def setup_clean(): + try: + os.remove('test1.omx') + except: + pass + + +def setup_empty_file(): + try: + os.remove('test1.omx') + f = omx.openFile('test.omx','w') + f.close() + except: + pass + + +def teardown_clean(): + try: + os.remove('test2.omx') + except: + pass + + +@with_setup(setup_clean,teardown_clean) +def test_create_file(): + f = omx.openFile('test1.omx','w') + f.close() + assert(os.path.exists('test1.omx')) + +def test_open_readonly_hdf5_file(): + f = tables.openFile('test2.omx','w') + f.close() + f = omx.openFile('test2.omx','r') + f.close() + +def test_add_numpy_matrix_using_brackets(): + f = omx.openFile('test3.omx','w') + f['m1'] = numpy.ones((5,5)) + f.close() + +def test_add_numpy_matrix_using_create_matrix(): + f = omx.openFile('test4.omx','w') + f.createMatrix('m1', obj=numpy.ones((5,5))) + f.close() + +def test_add_matrix_to_readonly_file(): + f = omx.openFile('test6.omx','w') + f['m2'] = numpy.ones((5,5)) + f.close() + f = omx.openFile('test6.omx','r') + assert_raises(tables.FileModeError, add_m1_node, f) + f.close() + +def test_add_matrix_with_same_name(): + f = omx.openFile('test5.omx','w') + add_m1_node(f) + # now add m1 again: + assert_raises(tables.NodeError, add_m1_node, f) + f.close() + +@with_setup(setup_clean,teardown_clean) +def test_get_length_of_file(): + f = omx.openFile('test7.omx','w') + f['m1'] = numpy.ones((5,5)) + f['m2'] = numpy.ones((5,5)) + f['m3'] = numpy.ones((5,5)) + f['m4'] = numpy.ones((5,5)) + f['m5'] = numpy.ones((5,5)) + assert(len(f)==5) + assert(len(f.listMatrices())==5) + f.close() + +def add_m1_node(f): + f.createMatrix('m1', obj=numpy.ones((7,7))) + From 30142ba7991d4224525d68829b42e9e35252193c Mon Sep 17 00:00:00 2001 From: Billy Charlton Date: Wed, 7 May 2014 20:42:31 -0700 Subject: [PATCH 02/17] Fix SHAPE attribute to be compatible with R and C++ OMX formats --- omx/File.py | 21 ++++++++++++++++++--- omx/__init__.py | 5 ++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/omx/File.py b/omx/File.py index 1ee80e47c4..50e27632ec 100644 --- a/omx/File.py +++ b/omx/File.py @@ -1,6 +1,7 @@ # OMX package # release 1 +import numpy as np import tables # requires pytables >= 2.4 from Exceptions import * @@ -29,7 +30,8 @@ def createMatrix(self, name, atom=None, shape=None, title='', filters=None, if self.shape() != None and obj != None and obj.shape != self.shape(): raise ShapeError('%s has shape %s but this file requires shape %s' % (name, obj.shape, self.shape())) - + + # Create the HDF5 array if tables.__version__.startswith('3'): matrix = self.createCArray(self.root.data, name, atom, shape, title, filters, chunkshape, byteorder, createparents, obj) @@ -40,6 +42,12 @@ def createMatrix(self, name, atom=None, shape=None, title='', filters=None, if (obj != None): matrix[:] = obj + # Store shape if we don't have one yet + if self._shape == None: + storeshape = np.array([matrix.shape[0],matrix.shape[1]], dtype='int32') + self.root._v_attrs['SHAPE'] = storeshape + self._shape = matrix.shape + # attributes if attrs: for key in attrs: @@ -56,7 +64,11 @@ def shape(self): # If shape is already set in root node attributes, grab it if 'SHAPE' in self.root._v_attrs: - self._shape = self.root._v_attrs['SHAPE'] + # Shape is stored as a numpy.array: + arrayshape = self.root._v_attrs['SHAPE'] + # which must be converted to a tuple: + realshape = (array[0],array[1]) + self._shape = realshape return self._shape # Inspect the first CArray object to determine its shape @@ -65,7 +77,10 @@ def shape(self): # Store it if we can if self._isWritable(): - self.root._v_attrs['SHAPE'] = self._shape + storeshape = np.array( + [self._shape[0],self._shape[1]], + dtype='int32') + self.root._v_attrs['SHAPE'] = storeshape return self._shape diff --git a/omx/__init__.py b/omx/__init__.py index ed3eb59568..7dd7504c33 100644 --- a/omx/__init__.py +++ b/omx/__init__.py @@ -2,6 +2,8 @@ # release 1 import tables +import numpy as np + from File import * from Exceptions import * @@ -25,7 +27,8 @@ def openFile(filename, mode='r', title='', root_uep='/', # shape if shape: - f.root._v_attrs['SHAPE'] = shape + storeshape = numpy.array([shape[0],shape[1]], dtype='int32') + f.root._v_attrs['SHAPE'] = storeshape # /data and /lookup folders if 'data' not in f.root: From a1171a6750d439ad6a14adf0a5b4c8834b6e83ae Mon Sep 17 00:00:00 2001 From: Billy Charlton Date: Wed, 7 May 2014 20:47:36 -0700 Subject: [PATCH 03/17] Ensure shape of mapping is a one-tuple, not a scalar --- omx/File.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/omx/File.py b/omx/File.py index 50e27632ec..ef351aab43 100644 --- a/omx/File.py +++ b/omx/File.py @@ -171,7 +171,7 @@ def createMapping(self, title, entries, overwrite=False): # Write the mapping! mymap = self.createArray(self.root.lookup, title, atom=tables.UInt16Atom(), - shape=(len(entries)) ) + shape=(len(entries),) ) mymap[:] = entries return mymap From 70aabb2c0cb91b9478531e9b2b3462981e7410c4 Mon Sep 17 00:00:00 2001 From: Billy Charlton Date: Wed, 7 May 2014 22:07:01 -0700 Subject: [PATCH 04/17] fix typo in shape() --- omx/File.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/omx/File.py b/omx/File.py index ef351aab43..82e3e91478 100644 --- a/omx/File.py +++ b/omx/File.py @@ -67,7 +67,7 @@ def shape(self): # Shape is stored as a numpy.array: arrayshape = self.root._v_attrs['SHAPE'] # which must be converted to a tuple: - realshape = (array[0],array[1]) + realshape = (arrayshape[0],arrayshape[1]) self._shape = realshape return self._shape From 4d077c8685b58949dc5ce142ac953a7c9cd884c6 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 10 Dec 2014 15:09:01 -0800 Subject: [PATCH 05/17] move files to match activitysim structure --- README.md | 0 {omx => activitysim/omx}/Exceptions.py | 0 {omx => activitysim/omx}/File.py | 0 {omx => activitysim/omx}/__init__.py | 0 {omx => activitysim/omx}/tests/test_file.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 README.md rename {omx => activitysim/omx}/Exceptions.py (100%) rename {omx => activitysim/omx}/File.py (100%) rename {omx => activitysim/omx}/__init__.py (100%) rename {omx => activitysim/omx}/tests/test_file.py (100%) diff --git a/README.md b/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/omx/Exceptions.py b/activitysim/omx/Exceptions.py similarity index 100% rename from omx/Exceptions.py rename to activitysim/omx/Exceptions.py diff --git a/omx/File.py b/activitysim/omx/File.py similarity index 100% rename from omx/File.py rename to activitysim/omx/File.py diff --git a/omx/__init__.py b/activitysim/omx/__init__.py similarity index 100% rename from omx/__init__.py rename to activitysim/omx/__init__.py diff --git a/omx/tests/test_file.py b/activitysim/omx/tests/test_file.py similarity index 100% rename from omx/tests/test_file.py rename to activitysim/omx/tests/test_file.py From 9e4b7d0fd2526cce29deae6145d538bff7737889 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 11:32:19 -0800 Subject: [PATCH 06/17] omx module renames --- activitysim/omx/{Exceptions.py => exceptions.py} | 0 activitysim/omx/{File.py => omxfile.py} | 0 activitysim/omx/tests/{test_file.py => test_omxfile.py} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename activitysim/omx/{Exceptions.py => exceptions.py} (100%) rename activitysim/omx/{File.py => omxfile.py} (100%) rename activitysim/omx/tests/{test_file.py => test_omxfile.py} (100%) diff --git a/activitysim/omx/Exceptions.py b/activitysim/omx/exceptions.py similarity index 100% rename from activitysim/omx/Exceptions.py rename to activitysim/omx/exceptions.py diff --git a/activitysim/omx/File.py b/activitysim/omx/omxfile.py similarity index 100% rename from activitysim/omx/File.py rename to activitysim/omx/omxfile.py diff --git a/activitysim/omx/tests/test_file.py b/activitysim/omx/tests/test_omxfile.py similarity index 100% rename from activitysim/omx/tests/test_file.py rename to activitysim/omx/tests/test_omxfile.py From 43fdcc53b073f4027b32d08cc28595ad11aa3b18 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 12:09:00 -0800 Subject: [PATCH 07/17] pep8 fixes for omx --- activitysim/omx/__init__.py | 33 +++++---- activitysim/omx/exceptions.py | 1 - activitysim/omx/omxfile.py | 97 +++++++++++++-------------- activitysim/omx/tests/test_omxfile.py | 74 +++++++++++--------- 4 files changed, 106 insertions(+), 99 deletions(-) diff --git a/activitysim/omx/__init__.py b/activitysim/omx/__init__.py index 7dd7504c33..f2902e3e92 100644 --- a/activitysim/omx/__init__.py +++ b/activitysim/omx/__init__.py @@ -1,45 +1,42 @@ # OMX package # release 1 +import numpy import tables -import numpy as np -from File import * -from Exceptions import * +from .omxile import File +from .exceptions import ShapeError # GLOBAL VARIABLES ----------- -__version__ = '0.2' +OMX_VERSION = '0.2' + # GLOBAL FUNCTIONS ----------- -def openFile(filename, mode='r', title='', root_uep='/', - filters=tables.Filters(complevel=1,shuffle=True,fletcher32=False,complib='zlib'), - shape=None, **kwargs): +def open_omxfile( + filename, mode='r', title='', root_uep='/', + filters=tables.Filters( + complevel=1, shuffle=True, fletcher32=False, complib='zlib'), + shape=None, **kwargs): """Open or create a new OMX file. New files will be created with default zlib compression enabled.""" - f = File(filename, mode, title, root_uep, filters, **kwargs); + f = File(filename, mode, title, root_uep, filters, **kwargs) # add omx structure if file is writable if mode != 'r': # version number if 'OMX_VERSION' not in f.root._v_attrs: - f.root._v_attrs['OMX_VERSION'] = __version__ + f.root._v_attrs['OMX_VERSION'] = OMX_VERSION # shape if shape: - storeshape = numpy.array([shape[0],shape[1]], dtype='int32') + storeshape = numpy.array([shape[0], shape[1]], dtype='int32') f.root._v_attrs['SHAPE'] = storeshape # /data and /lookup folders if 'data' not in f.root: - f.createGroup(f.root,"data") + f.createGroup(f.root, "data") if 'lookup' not in f.root: - f.createGroup(f.root,"lookup") + f.createGroup(f.root, "lookup") return f - - -if __name__ == "__main__": - print 'OMX!' - - diff --git a/activitysim/omx/exceptions.py b/activitysim/omx/exceptions.py index 4688e2ebdd..6f82cdb605 100644 --- a/activitysim/omx/exceptions.py +++ b/activitysim/omx/exceptions.py @@ -1,3 +1,2 @@ class ShapeError(Exception): pass - diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index 82e3e91478..569cc4fe46 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -4,13 +4,12 @@ import numpy as np import tables # requires pytables >= 2.4 -from Exceptions import * +from .exceptions import ShapeError class File(tables.File): - - def __init__(self, f,m,t,r,f1, **kwargs): - tables.File.__init__(self,f,m,t,r,f1,**kwargs) + def __init__(self, f, m, t, r, f1, **kwargs): + tables.File.__init__(self, f, m, t, r, f1, **kwargs) self._shape = None def version(self): @@ -19,32 +18,38 @@ def version(self): else: return None - - def createMatrix(self, name, atom=None, shape=None, title='', filters=None, - chunkshape=None, byteorder=None, createparents=False, obj=None, - attrs=None): + def createMatrix( + self, name, atom=None, shape=None, title='', filters=None, + chunkshape=None, byteorder=None, createparents=False, obj=None, + attrs=None): """Create OMX Matrix (CArray) at root level. User must pass in either an existing numpy matrix, or a shape and an atom type.""" # If object was passed in, make sure its shape is correct - if self.shape() != None and obj != None and obj.shape != self.shape(): - raise ShapeError('%s has shape %s but this file requires shape %s' % + if (self.shape() is not None and + obj is not None and + obj.shape != self.shape()): + raise ShapeError( + '%s has shape %s but this file requires shape %s' % (name, obj.shape, self.shape())) - + # Create the HDF5 array if tables.__version__.startswith('3'): - matrix = self.createCArray(self.root.data, name, atom, shape, title, filters, - chunkshape, byteorder, createparents, obj) + matrix = self.createCArray( + self.root.data, name, atom, shape, title, filters, + chunkshape, byteorder, createparents, obj) else: # this version is tables 2.4-compatible: - matrix = self.createCArray(self.root.data, name, atom, shape, title, filters, - chunkshape, byteorder, createparents) - if (obj != None): + matrix = self.createCArray( + self.root.data, name, atom, shape, title, filters, + chunkshape, byteorder, createparents) + if (obj is not None): matrix[:] = obj # Store shape if we don't have one yet - if self._shape == None: - storeshape = np.array([matrix.shape[0],matrix.shape[1]], dtype='int32') + if self._shape is None: + storeshape = np.array( + [matrix.shape[0], matrix.shape[1]], dtype='int32') self.root._v_attrs['SHAPE'] = storeshape self._shape = matrix.shape @@ -67,18 +72,18 @@ def shape(self): # Shape is stored as a numpy.array: arrayshape = self.root._v_attrs['SHAPE'] # which must be converted to a tuple: - realshape = (arrayshape[0],arrayshape[1]) + realshape = (arrayshape[0], arrayshape[1]) self._shape = realshape return self._shape # Inspect the first CArray object to determine its shape if len(self) > 0: - self._shape = self.iterNodes(self.root.data,'CArray').next().shape + self._shape = self.iterNodes(self.root.data, 'CArray').next().shape # Store it if we can if self._isWritable(): storeshape = np.array( - [self._shape[0],self._shape[1]], + [self._shape[0], self._shape[1]], dtype='int32') self.root._v_attrs['SHAPE'] = storeshape @@ -87,21 +92,22 @@ def shape(self): else: return None - def listMatrices(self): """Return list of Matrix names in this File""" - return [node.name for node in self.listNodes(self.root.data,'CArray')] - + return [node.name for node in self.listNodes(self.root.data, 'CArray')] def listAllAttributes(self): - """Return combined list of all attributes used for any Matrix in this File""" + """ + Return combined list of all attributes used for + any Matrix in this File + + """ all_tags = set() - for m in self.listNodes(self.root,'CArray'): - if m.attrs != None: + for m in self.listNodes(self.root, 'CArray'): + if m.attrs is not None: all_tags.update(m.attrs._v_attrnames) return sorted(list(all_tags)) - # MAPPINGS ----------------------------------------------- def listMappings(self): try: @@ -109,13 +115,11 @@ def listMappings(self): except: return [] - def deleteMapping(self, title): try: self.removeNode(self.root.lookup, title) except: - raise LookupError('No such mapping: '+title) - + raise LookupError('No such mapping: ' + title) def mapping(self, title): """Return dict containing key:value pairs for specified mapping. Keys @@ -142,12 +146,11 @@ def mapentries(self, title): entries = [] entries.extend(self.getNode(self.root.lookup, title)[:]) - return (keymap,entries) + return (keymap, entries) except: raise LookupError('No such mapping: '+title) - def createMapping(self, title, entries, overwrite=False): """Create an equivalency index, which maps a raw data dimension to another integer value. Once created, mappings can be referenced by @@ -170,15 +173,15 @@ def createMapping(self, title, entries, overwrite=False): self.createGroup(self.root, 'lookup') # Write the mapping! - mymap = self.createArray(self.root.lookup, title, atom=tables.UInt16Atom(), - shape=(len(entries),) ) + mymap = self.createArray( + self.root.lookup, title, atom=tables.UInt16Atom(), + shape=(len(entries),)) mymap[:] = entries return mymap - # The following functions implement Python list/dictionary lookups. ---- - def __getitem__(self,key): + def __getitem__(self, key): """Return a matrix by name, or a list of matrices by attributes""" if isinstance(key, str): @@ -194,16 +197,16 @@ def __getitem__(self,key): return mats - def _getMatricesByAttribute(self, key, value, matrices=None): answer = [] - if matrices==None: - matrices = self.listNodes(self.root.data,'CArray') + if matrices is None: + matrices = self.listNodes(self.root.data, 'CArray') for m in matrices: - if m.attrs == None: continue + if m.attrs is None: + continue # Only test if key is present in matrix attributes if key in m.attrs._v_attrnames and m.attrs[key] == value: @@ -211,33 +214,29 @@ def _getMatricesByAttribute(self, key, value, matrices=None): return answer - def __len__(self): return len(self.listNodes(self.root.data, 'CArray')) - def __setitem__(self, key, dataset): - # We need to determine atom and shape from the object that's been passed in. + # We need to determine atom and shape from the object that's + # been passed in. # This assumes 'dataset' is a numpy object. atom = tables.Atom.from_dtype(dataset.dtype) shape = dataset.shape - #checks to see if it is already a tables instance, and if so, just copies it + # checks to see if it is already a tables instance, and if so, + # copies it if dataset.__class__.__name__ == 'CArray': return dataset.copy(self.root.data, key) else: return self.createMatrix(key, atom, shape, obj=dataset) - def __delitem__(self, key): self.removeNode(self.root.data, key) - def __iter__(self): """Iterate over the keys in this container""" return self.iterNodes(self.root.data, 'CArray') - def __contains__(self, item): return item in self.root.data._v_children - diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index d0101a1e09..c1e713902f 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -1,5 +1,11 @@ -import tables,omx,numpy,os -from nose.tools import * +import os + +import nose.tools as nt +import numpy +import tables + +from .. import open_omxfile + def setup_clean(): try: @@ -11,7 +17,7 @@ def setup_clean(): def setup_empty_file(): try: os.remove('test1.omx') - f = omx.openFile('test.omx','w') + f = open_omxfile('test.omx', 'w') f.close() except: pass @@ -24,55 +30,61 @@ def teardown_clean(): pass -@with_setup(setup_clean,teardown_clean) +@nt.with_setup(setup_clean, teardown_clean) def test_create_file(): - f = omx.openFile('test1.omx','w') + f = open_omxfile('test1.omx', 'w') f.close() - assert(os.path.exists('test1.omx')) + nt.assert(os.path.exists('test1.omx')) + def test_open_readonly_hdf5_file(): - f = tables.openFile('test2.omx','w') + f = tables.openFile('test2.omx', 'w') f.close() - f = omx.openFile('test2.omx','r') - f.close() + f = open_omxfile('test2.omx', 'r') + f.close() + def test_add_numpy_matrix_using_brackets(): - f = omx.openFile('test3.omx','w') - f['m1'] = numpy.ones((5,5)) + f = open_omxfile('test3.omx', 'w') + f['m1'] = numpy.ones((5, 5)) f.close() + def test_add_numpy_matrix_using_create_matrix(): - f = omx.openFile('test4.omx','w') - f.createMatrix('m1', obj=numpy.ones((5,5))) + f = open_omxfile('test4.omx', 'w') + f.createMatrix('m1', obj=numpy.ones((5, 5))) f.close() + def test_add_matrix_to_readonly_file(): - f = omx.openFile('test6.omx','w') - f['m2'] = numpy.ones((5,5)) + f = open_omxfile('test6.omx', 'w') + f['m2'] = numpy.ones((5, 5)) + f.close() + f = open_omxfile('test6.omx', 'r') + nt.assert_raises(tables.FileModeError, add_m1_node, f) f.close() - f = omx.openFile('test6.omx','r') - assert_raises(tables.FileModeError, add_m1_node, f) - f.close() + def test_add_matrix_with_same_name(): - f = omx.openFile('test5.omx','w') + f = open_omxfile('test5.omx', 'w') add_m1_node(f) # now add m1 again: - assert_raises(tables.NodeError, add_m1_node, f) + nt.assert_raises(tables.NodeError, add_m1_node, f) f.close() -@with_setup(setup_clean,teardown_clean) + +@nt.with_setup(setup_clean, teardown_clean) def test_get_length_of_file(): - f = omx.openFile('test7.omx','w') - f['m1'] = numpy.ones((5,5)) - f['m2'] = numpy.ones((5,5)) - f['m3'] = numpy.ones((5,5)) - f['m4'] = numpy.ones((5,5)) - f['m5'] = numpy.ones((5,5)) - assert(len(f)==5) - assert(len(f.listMatrices())==5) + f = open_omxfile('test7.omx', 'w') + f['m1'] = numpy.ones((5, 5)) + f['m2'] = numpy.ones((5, 5)) + f['m3'] = numpy.ones((5, 5)) + f['m4'] = numpy.ones((5, 5)) + f['m5'] = numpy.ones((5, 5)) + nt.assert(len(f) == 5) + nt.assert(len(f.listMatrices()) == 5) f.close() -def add_m1_node(f): - f.createMatrix('m1', obj=numpy.ones((7,7))) +def add_m1_node(f): + f.createMatrix('m1', obj=numpy.ones((7, 7))) From 25606a7046ea233640d2a00f84e8473f442fa69c Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 12:42:29 -0800 Subject: [PATCH 08/17] get omx tests running under pytest --- activitysim/omx/__init__.py | 2 +- activitysim/omx/tests/__init__.py | 0 activitysim/omx/tests/test_omxfile.py | 107 +++++++++++++------------- 3 files changed, 55 insertions(+), 54 deletions(-) create mode 100644 activitysim/omx/tests/__init__.py diff --git a/activitysim/omx/__init__.py b/activitysim/omx/__init__.py index f2902e3e92..19df77cf8c 100644 --- a/activitysim/omx/__init__.py +++ b/activitysim/omx/__init__.py @@ -4,7 +4,7 @@ import numpy import tables -from .omxile import File +from .omxfile import File from .exceptions import ShapeError # GLOBAL VARIABLES ----------- diff --git a/activitysim/omx/tests/__init__.py b/activitysim/omx/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index c1e713902f..5f8f5052ab 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -1,90 +1,91 @@ import os +import tempfile -import nose.tools as nt -import numpy +import numpy as np +import pytest import tables from .. import open_omxfile +from ..exceptions import ShapeError -def setup_clean(): - try: - os.remove('test1.omx') - except: - pass +@pytest.fixture +def tmpomx(request): + with tempfile.NamedTemporaryFile() as f: + fname = f.name + def cleanup(): + if os.path.exists(fname): + os.remove(fname) + request.addfinalizer(cleanup) -def setup_empty_file(): - try: - os.remove('test1.omx') - f = open_omxfile('test.omx', 'w') - f.close() - except: - pass + return fname -def teardown_clean(): - try: - os.remove('test2.omx') - except: - pass - - -@nt.with_setup(setup_clean, teardown_clean) -def test_create_file(): - f = open_omxfile('test1.omx', 'w') +def test_create_file(tmpomx): + f = open_omxfile(tmpomx, 'w') f.close() - nt.assert(os.path.exists('test1.omx')) + assert os.path.exists(tmpomx) -def test_open_readonly_hdf5_file(): - f = tables.openFile('test2.omx', 'w') +def test_open_readonly_hdf5_file(tmpomx): + f = tables.openFile(tmpomx, 'w') f.close() - f = open_omxfile('test2.omx', 'r') + f = open_omxfile(tmpomx, 'r') f.close() -def test_add_numpy_matrix_using_brackets(): - f = open_omxfile('test3.omx', 'w') - f['m1'] = numpy.ones((5, 5)) +def test_add_numpy_matrix_using_brackets(tmpomx): + f = open_omxfile(tmpomx, 'w') + f['m1'] = np.ones((5, 5)) f.close() -def test_add_numpy_matrix_using_create_matrix(): - f = open_omxfile('test4.omx', 'w') - f.createMatrix('m1', obj=numpy.ones((5, 5))) +def test_add_np_matrix_using_create_matrix(tmpomx): + f = open_omxfile(tmpomx, 'w') + f.createMatrix('m1', obj=np.ones((5, 5))) + + # test check for shape matching + with pytest.raises(ShapeError): + f.createMatrix('m2', obj=np.ones((8, 8))) + f.close() -def test_add_matrix_to_readonly_file(): - f = open_omxfile('test6.omx', 'w') - f['m2'] = numpy.ones((5, 5)) +def test_add_matrix_to_readonly_file(tmpomx): + f = open_omxfile(tmpomx, 'w') + f['m2'] = np.ones((7, 7)) f.close() - f = open_omxfile('test6.omx', 'r') - nt.assert_raises(tables.FileModeError, add_m1_node, f) + f = open_omxfile(tmpomx, 'r') + + with pytest.raises(tables.FileModeError): + add_m1_node(f) + f.close() -def test_add_matrix_with_same_name(): - f = open_omxfile('test5.omx', 'w') +def test_add_matrix_with_same_name(tmpomx): + f = open_omxfile(tmpomx, 'w') add_m1_node(f) # now add m1 again: - nt.assert_raises(tables.NodeError, add_m1_node, f) + + with pytest.raises(tables.NodeError): + add_m1_node(f) + f.close() -@nt.with_setup(setup_clean, teardown_clean) -def test_get_length_of_file(): - f = open_omxfile('test7.omx', 'w') - f['m1'] = numpy.ones((5, 5)) - f['m2'] = numpy.ones((5, 5)) - f['m3'] = numpy.ones((5, 5)) - f['m4'] = numpy.ones((5, 5)) - f['m5'] = numpy.ones((5, 5)) - nt.assert(len(f) == 5) - nt.assert(len(f.listMatrices()) == 5) +def test_get_length_of_file(tmpomx): + f = open_omxfile(tmpomx, 'w') + f['m1'] = np.ones((5, 5)) + f['m2'] = np.ones((5, 5)) + f['m3'] = np.ones((5, 5)) + f['m4'] = np.ones((5, 5)) + f['m5'] = np.ones((5, 5)) + assert len(f) == 5 + assert len(f.listMatrices()) == 5 f.close() def add_m1_node(f): - f.createMatrix('m1', obj=numpy.ones((7, 7))) + f.createMatrix('m1', obj=np.ones((7, 7))) From 72e1a4eab1f07768a7b0128115bd4f09fe72fef0 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 13:39:28 -0800 Subject: [PATCH 09/17] more pep8 fixes and general cleanup --- activitysim/omx/__init__.py | 42 +---------- activitysim/omx/omxfile.py | 102 +++++++++++++++++--------- activitysim/omx/tests/test_omxfile.py | 13 ++-- 3 files changed, 74 insertions(+), 83 deletions(-) diff --git a/activitysim/omx/__init__.py b/activitysim/omx/__init__.py index 19df77cf8c..4608c44aa8 100644 --- a/activitysim/omx/__init__.py +++ b/activitysim/omx/__init__.py @@ -1,42 +1,2 @@ -# OMX package -# release 1 - -import numpy -import tables - -from .omxfile import File +from .omxfile import OMXFile, open_omxfile, OMX_VERSION from .exceptions import ShapeError - -# GLOBAL VARIABLES ----------- -OMX_VERSION = '0.2' - - -# GLOBAL FUNCTIONS ----------- -def open_omxfile( - filename, mode='r', title='', root_uep='/', - filters=tables.Filters( - complevel=1, shuffle=True, fletcher32=False, complib='zlib'), - shape=None, **kwargs): - """Open or create a new OMX file. New files will be created with default - zlib compression enabled.""" - - f = File(filename, mode, title, root_uep, filters, **kwargs) - - # add omx structure if file is writable - if mode != 'r': - # version number - if 'OMX_VERSION' not in f.root._v_attrs: - f.root._v_attrs['OMX_VERSION'] = OMX_VERSION - - # shape - if shape: - storeshape = numpy.array([shape[0], shape[1]], dtype='int32') - f.root._v_attrs['SHAPE'] = storeshape - - # /data and /lookup folders - if 'data' not in f.root: - f.createGroup(f.root, "data") - if 'lookup' not in f.root: - f.createGroup(f.root, "lookup") - - return f diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index 569cc4fe46..b88a8942eb 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -6,10 +6,12 @@ from .exceptions import ShapeError +OMX_VERSION = '0.2' -class File(tables.File): - def __init__(self, f, m, t, r, f1, **kwargs): - tables.File.__init__(self, f, m, t, r, f1, **kwargs) + +class OMXFile(tables.File): + def __init__(self, *args, **kwargs): + super(OMXFile, self).__init__(*args, **kwargs) self._shape = None def version(self): @@ -18,7 +20,7 @@ def version(self): else: return None - def createMatrix( + def create_matrix( self, name, atom=None, shape=None, title='', filters=None, chunkshape=None, byteorder=None, createparents=False, obj=None, attrs=None): @@ -35,12 +37,12 @@ def createMatrix( # Create the HDF5 array if tables.__version__.startswith('3'): - matrix = self.createCArray( + matrix = self.create_carray( self.root.data, name, atom, shape, title, filters, chunkshape, byteorder, createparents, obj) else: # this version is tables 2.4-compatible: - matrix = self.createCArray( + matrix = self.create_carray( self.root.data, name, atom, shape, title, filters, chunkshape, byteorder, createparents) if (obj is not None): @@ -78,13 +80,12 @@ def shape(self): # Inspect the first CArray object to determine its shape if len(self) > 0: - self._shape = self.iterNodes(self.root.data, 'CArray').next().shape + self._shape = self.iter_nodes( + self.root.data, 'CArray').next().shape # Store it if we can - if self._isWritable(): - storeshape = np.array( - [self._shape[0], self._shape[1]], - dtype='int32') + if self._iswritable(): + storeshape = np.array(self._shape, dtype='int32') self.root._v_attrs['SHAPE'] = storeshape return self._shape @@ -92,32 +93,33 @@ def shape(self): else: return None - def listMatrices(self): + def list_matrices(self): """Return list of Matrix names in this File""" - return [node.name for node in self.listNodes(self.root.data, 'CArray')] + return [ + node.name for node in self.list_nodes(self.root.data, 'CArray')] - def listAllAttributes(self): + def list_all_attributes(self): """ Return combined list of all attributes used for any Matrix in this File """ all_tags = set() - for m in self.listNodes(self.root, 'CArray'): + for m in self.list_nodes(self.root, 'CArray'): if m.attrs is not None: all_tags.update(m.attrs._v_attrnames) return sorted(list(all_tags)) # MAPPINGS ----------------------------------------------- - def listMappings(self): + def list_mappings(self): try: - return [m.name for m in self.listNodes(self.root.lookup)] + return [m.name for m in self.list_nodes(self.root.lookup)] except: return [] - def deleteMapping(self, title): + def delete_mapping(self, title): try: - self.removeNode(self.root.lookup, title) + self.remove_node(self.root.lookup, title) except: raise LookupError('No such mapping: ' + title) @@ -127,7 +129,7 @@ def mapping(self, title): try: # fetch entries entries = [] - entries.extend(self.getNode(self.root.lookup, title)[:]) + entries.extend(self.get_node(self.root.lookup, title)[:]) # build reverse key-lookup keymap = {} @@ -144,14 +146,14 @@ def mapentries(self, title): try: # fetch entries entries = [] - entries.extend(self.getNode(self.root.lookup, title)[:]) + entries.extend(self.get_node(self.root.lookup, title)[:]) return (keymap, entries) except: raise LookupError('No such mapping: '+title) - def createMapping(self, title, entries, overwrite=False): + def create_mapping(self, title, entries, overwrite=False): """Create an equivalency index, which maps a raw data dimension to another integer value. Once created, mappings can be referenced by offset or by key.""" @@ -162,18 +164,18 @@ def createMapping(self, title, entries, overwrite=False): raise ShapeError('Mapping must match one data dimension') # Handle case where mapping already exists: - if title in self.listMappings(): + if title in self.list_mappings(): if overwrite: - self.deleteMapping(title) + self.delete_mapping(title) else: raise LookupError(title+' mapping already exists.') # Create lookup group under root if it doesn't already exist. if 'lookup' not in self.root: - self.createGroup(self.root, 'lookup') + self.create_group(self.root, 'lookup') # Write the mapping! - mymap = self.createArray( + mymap = self.create_array( self.root.lookup, title, atom=tables.UInt16Atom(), shape=(len(entries),)) mymap[:] = entries @@ -185,24 +187,24 @@ def __getitem__(self, key): """Return a matrix by name, or a list of matrices by attributes""" if isinstance(key, str): - return self.getNode(self.root.data, key) + return self.get_node(self.root.data, key) if 'keys' not in dir(key): raise LookupError('Key %s not found' % key) # Loop through key/value pairs - mats = self.listNodes(self.root.data, 'CArray') + mats = self.list_nodes(self.root.data, 'CArray') for a in key.keys(): - mats = self._getMatricesByAttribute(a, key[a], mats) + mats = self._get_matrices_by_attribute(a, key[a], mats) return mats - def _getMatricesByAttribute(self, key, value, matrices=None): + def _get_matrices_by_attribute(self, key, value, matrices=None): answer = [] if matrices is None: - matrices = self.listNodes(self.root.data, 'CArray') + matrices = self.list_nodes(self.root.data, 'CArray') for m in matrices: if m.attrs is None: @@ -215,7 +217,7 @@ def _getMatricesByAttribute(self, key, value, matrices=None): return answer def __len__(self): - return len(self.listNodes(self.root.data, 'CArray')) + return len(self.list_nodes(self.root.data, 'CArray')) def __setitem__(self, key, dataset): # We need to determine atom and shape from the object that's @@ -229,14 +231,44 @@ def __setitem__(self, key, dataset): if dataset.__class__.__name__ == 'CArray': return dataset.copy(self.root.data, key) else: - return self.createMatrix(key, atom, shape, obj=dataset) + return self.create_matrix(key, atom, shape, obj=dataset) def __delitem__(self, key): - self.removeNode(self.root.data, key) + self.remove_node(self.root.data, key) def __iter__(self): """Iterate over the keys in this container""" - return self.iterNodes(self.root.data, 'CArray') + return self.iter_nodes(self.root.data, 'CArray') def __contains__(self, item): return item in self.root.data._v_children + + +def open_omxfile( + filename, mode='r', title='', root_uep='/', + filters=tables.Filters( + complevel=1, shuffle=True, fletcher32=False, complib='zlib'), + shape=None, **kwargs): + """Open or create a new OMX file. New files will be created with default + zlib compression enabled.""" + + f = OMXFile(filename, mode, title, root_uep, filters, **kwargs) + + # add omx structure if file is writable + if mode != 'r': + # version number + if 'OMX_VERSION' not in f.root._v_attrs: + f.root._v_attrs['OMX_VERSION'] = OMX_VERSION + + # shape + if shape: + storeshape = np.array([shape[0], shape[1]], dtype='int32') + f.root._v_attrs['SHAPE'] = storeshape + + # /data and /lookup folders + if 'data' not in f.root: + f.create_group(f.root, "data") + if 'lookup' not in f.root: + f.create_group(f.root, "lookup") + + return f diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index 5f8f5052ab..758e132b34 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -5,8 +5,7 @@ import pytest import tables -from .. import open_omxfile -from ..exceptions import ShapeError +from .. import open_omxfile, ShapeError @pytest.fixture @@ -29,7 +28,7 @@ def test_create_file(tmpomx): def test_open_readonly_hdf5_file(tmpomx): - f = tables.openFile(tmpomx, 'w') + f = tables.open_file(tmpomx, 'w') f.close() f = open_omxfile(tmpomx, 'r') f.close() @@ -43,11 +42,11 @@ def test_add_numpy_matrix_using_brackets(tmpomx): def test_add_np_matrix_using_create_matrix(tmpomx): f = open_omxfile(tmpomx, 'w') - f.createMatrix('m1', obj=np.ones((5, 5))) + f.create_matrix('m1', obj=np.ones((5, 5))) # test check for shape matching with pytest.raises(ShapeError): - f.createMatrix('m2', obj=np.ones((8, 8))) + f.create_matrix('m2', obj=np.ones((8, 8))) f.close() @@ -83,9 +82,9 @@ def test_get_length_of_file(tmpomx): f['m4'] = np.ones((5, 5)) f['m5'] = np.ones((5, 5)) assert len(f) == 5 - assert len(f.listMatrices()) == 5 + assert len(f.list_matrices()) == 5 f.close() def add_m1_node(f): - f.createMatrix('m1', obj=np.ones((7, 7))) + f.create_matrix('m1', obj=np.ones((7, 7))) From 097f0ac628d4d22686d51e37011af42c44e8bde9 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 13:42:10 -0800 Subject: [PATCH 10/17] remove tables 2 compatibility --- activitysim/omx/omxfile.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index b88a8942eb..6c70d9a3de 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -2,7 +2,7 @@ # release 1 import numpy as np -import tables # requires pytables >= 2.4 +import tables from .exceptions import ShapeError @@ -40,13 +40,6 @@ def create_matrix( matrix = self.create_carray( self.root.data, name, atom, shape, title, filters, chunkshape, byteorder, createparents, obj) - else: - # this version is tables 2.4-compatible: - matrix = self.create_carray( - self.root.data, name, atom, shape, title, filters, - chunkshape, byteorder, createparents) - if (obj is not None): - matrix[:] = obj # Store shape if we don't have one yet if self._shape is None: From 67227841b80f2fec8471a3caf34ae13497f57052 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 13:48:31 -0800 Subject: [PATCH 11/17] remove unused mapentries method --- activitysim/omx/omxfile.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index 6c70d9a3de..ba5be68474 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -132,19 +132,7 @@ def mapping(self, title): return keymap except: - raise LookupError('No such mapping: '+title) - - def mapentries(self, title): - """Return entries[] with key for each array offset.""" - try: - # fetch entries - entries = [] - entries.extend(self.get_node(self.root.lookup, title)[:]) - - return (keymap, entries) - - except: - raise LookupError('No such mapping: '+title) + raise LookupError('No such mapping: ' + title) def create_mapping(self, title, entries, overwrite=False): """Create an equivalency index, which maps a raw data dimension to @@ -255,7 +243,7 @@ def open_omxfile( # shape if shape: - storeshape = np.array([shape[0], shape[1]], dtype='int32') + storeshape = np.array(shape, dtype='int32') f.root._v_attrs['SHAPE'] = storeshape # /data and /lookup folders From 7c0bff899fe4254f0be93c91c80283585770f932 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 16:18:21 -0800 Subject: [PATCH 12/17] use context managers in tests --- activitysim/omx/omxfile.py | 2 +- activitysim/omx/tests/test_omxfile.py | 65 +++++++++++++-------------- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index ba5be68474..d75e5a8358 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -67,7 +67,7 @@ def shape(self): # Shape is stored as a numpy.array: arrayshape = self.root._v_attrs['SHAPE'] # which must be converted to a tuple: - realshape = (arrayshape[0], arrayshape[1]) + realshape = tuple(arrayshape) self._shape = realshape return self._shape diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index 758e132b34..6c9f9613e5 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -8,6 +8,10 @@ from .. import open_omxfile, ShapeError +def add_m1_node(f): + f.create_matrix('m1', obj=np.ones((7, 7))) + + @pytest.fixture def tmpomx(request): with tempfile.NamedTemporaryFile() as f: @@ -35,56 +39,47 @@ def test_open_readonly_hdf5_file(tmpomx): def test_add_numpy_matrix_using_brackets(tmpomx): - f = open_omxfile(tmpomx, 'w') - f['m1'] = np.ones((5, 5)) - f.close() + with open_omxfile(tmpomx, 'w') as f: + f['m1'] = np.ones((5, 5)) def test_add_np_matrix_using_create_matrix(tmpomx): - f = open_omxfile(tmpomx, 'w') - f.create_matrix('m1', obj=np.ones((5, 5))) - - # test check for shape matching - with pytest.raises(ShapeError): - f.create_matrix('m2', obj=np.ones((8, 8))) + with open_omxfile(tmpomx, 'w') as f: + f.create_matrix('m1', obj=np.ones((5, 5))) - f.close() + # test check for shape matching + with pytest.raises(ShapeError): + f.create_matrix('m2', obj=np.ones((8, 8))) def test_add_matrix_to_readonly_file(tmpomx): - f = open_omxfile(tmpomx, 'w') - f['m2'] = np.ones((7, 7)) - f.close() - f = open_omxfile(tmpomx, 'r') + with open_omxfile(tmpomx, 'w') as f: + f['m2'] = np.ones((7, 7)) - with pytest.raises(tables.FileModeError): - add_m1_node(f) - - f.close() + with open_omxfile(tmpomx, 'r') as f: + with pytest.raises(tables.FileModeError): + add_m1_node(f) def test_add_matrix_with_same_name(tmpomx): - f = open_omxfile(tmpomx, 'w') - add_m1_node(f) - # now add m1 again: - - with pytest.raises(tables.NodeError): + with open_omxfile(tmpomx, 'w') as f: add_m1_node(f) - f.close() + # now add m1 again: + with pytest.raises(tables.NodeError): + add_m1_node(f) def test_get_length_of_file(tmpomx): - f = open_omxfile(tmpomx, 'w') - f['m1'] = np.ones((5, 5)) - f['m2'] = np.ones((5, 5)) - f['m3'] = np.ones((5, 5)) - f['m4'] = np.ones((5, 5)) - f['m5'] = np.ones((5, 5)) - assert len(f) == 5 - assert len(f.list_matrices()) == 5 - f.close() + mats = ['m{}'.format(x) for x in range(5)] + with open_omxfile(tmpomx, 'w') as f: + for m in mats: + f[m] = np.ones((5, 5)) + assert f.list_matrices() == mats -def add_m1_node(f): - f.create_matrix('m1', obj=np.ones((7, 7))) + +def test_shapeerror(tmpomx): + with open_omxfile(tmpomx, mode='w', shape=(5, 5)) as f: + with pytest.raises(ShapeError): + f['test'] = np.ones(10) From 219303805f21b64cad6f961466e45b96a8f81393 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 16:38:30 -0800 Subject: [PATCH 13/17] add a test array fixture --- activitysim/omx/tests/test_omxfile.py | 45 ++++++++++++++------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index 6c9f9613e5..83f3f25ecb 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -2,16 +2,13 @@ import tempfile import numpy as np +import numpy.testing as npt import pytest import tables from .. import open_omxfile, ShapeError -def add_m1_node(f): - f.create_matrix('m1', obj=np.ones((7, 7))) - - @pytest.fixture def tmpomx(request): with tempfile.NamedTemporaryFile() as f: @@ -25,61 +22,67 @@ def cleanup(): return fname -def test_create_file(tmpomx): - f = open_omxfile(tmpomx, 'w') - f.close() - assert os.path.exists(tmpomx) +@pytest.fixture +def ones5x5(): + return np.ones((5, 5)) def test_open_readonly_hdf5_file(tmpomx): f = tables.open_file(tmpomx, 'w') f.close() + assert os.path.exists(tmpomx) f = open_omxfile(tmpomx, 'r') f.close() -def test_add_numpy_matrix_using_brackets(tmpomx): +def test_setitem_getitem(tmpomx, ones5x5): with open_omxfile(tmpomx, 'w') as f: - f['m1'] = np.ones((5, 5)) + f['m1'] = ones5x5 + npt.assert_array_equal(f['m1'], ones5x5) + assert f.shape() == (5, 5) -def test_add_np_matrix_using_create_matrix(tmpomx): +def test_create_matrix(tmpomx, ones5x5): with open_omxfile(tmpomx, 'w') as f: - f.create_matrix('m1', obj=np.ones((5, 5))) + f.create_matrix('m1', obj=ones5x5) + npt.assert_array_equal(f['m1'], ones5x5) + assert f.shape() == (5, 5) # test check for shape matching with pytest.raises(ShapeError): f.create_matrix('m2', obj=np.ones((8, 8))) -def test_add_matrix_to_readonly_file(tmpomx): +def test_add_matrix_to_readonly_file(tmpomx, ones5x5): with open_omxfile(tmpomx, 'w') as f: - f['m2'] = np.ones((7, 7)) + f['m2'] = ones5x5 with open_omxfile(tmpomx, 'r') as f: with pytest.raises(tables.FileModeError): - add_m1_node(f) + f['m1'] = ones5x5 -def test_add_matrix_with_same_name(tmpomx): +def test_add_matrix_with_same_name(tmpomx, ones5x5): with open_omxfile(tmpomx, 'w') as f: - add_m1_node(f) + f['m1'] = ones5x5 # now add m1 again: with pytest.raises(tables.NodeError): - add_m1_node(f) + f['m1'] = ones5x5 -def test_get_length_of_file(tmpomx): +def test_get_length_of_file(tmpomx, ones5x5): mats = ['m{}'.format(x) for x in range(5)] with open_omxfile(tmpomx, 'w') as f: for m in mats: - f[m] = np.ones((5, 5)) + f[m] = ones5x5 assert f.list_matrices() == mats -def test_shapeerror(tmpomx): +def test_shape(tmpomx): with open_omxfile(tmpomx, mode='w', shape=(5, 5)) as f: + assert f.shape() == (5, 5) + with pytest.raises(ShapeError): f['test'] = np.ones(10) From a1a0760a22fa332bbf4441a09d9a91e91563c493 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 21:37:02 -0800 Subject: [PATCH 14/17] test coverage, cleanup, and bug fixes --- activitysim/omx/omxfile.py | 34 ++++++++---------- activitysim/omx/tests/test_omxfile.py | 50 ++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index d75e5a8358..86e9078832 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -1,6 +1,3 @@ -# OMX package -# release 1 - import numpy as np import tables @@ -43,8 +40,7 @@ def create_matrix( # Store shape if we don't have one yet if self._shape is None: - storeshape = np.array( - [matrix.shape[0], matrix.shape[1]], dtype='int32') + storeshape = np.array(matrix.shape, dtype='int32') self.root._v_attrs['SHAPE'] = storeshape self._shape = matrix.shape @@ -98,10 +94,9 @@ def list_all_attributes(self): """ all_tags = set() - for m in self.list_nodes(self.root, 'CArray'): - if m.attrs is not None: - all_tags.update(m.attrs._v_attrnames) - return sorted(list(all_tags)) + for m in self.iter_nodes(self.root.data, 'CArray'): + all_tags.update(m.attrs._v_attrnamesuser) + return sorted(all_tags) # MAPPINGS ----------------------------------------------- def list_mappings(self): @@ -149,7 +144,7 @@ def create_mapping(self, title, entries, overwrite=False): if overwrite: self.delete_mapping(title) else: - raise LookupError(title+' mapping already exists.') + raise LookupError(title + ' mapping already exists.') # Create lookup group under root if it doesn't already exist. if 'lookup' not in self.root: @@ -181,7 +176,6 @@ def __getitem__(self, key): return mats def _get_matrices_by_attribute(self, key, value, matrices=None): - answer = [] if matrices is None: @@ -192,7 +186,7 @@ def _get_matrices_by_attribute(self, key, value, matrices=None): continue # Only test if key is present in matrix attributes - if key in m.attrs._v_attrnames and m.attrs[key] == value: + if key in m.attrs and m.attrs[key] == value: answer.append(m) return answer @@ -201,17 +195,17 @@ def __len__(self): return len(self.list_nodes(self.root.data, 'CArray')) def __setitem__(self, key, dataset): - # We need to determine atom and shape from the object that's - # been passed in. - # This assumes 'dataset' is a numpy object. - atom = tables.Atom.from_dtype(dataset.dtype) - shape = dataset.shape - # checks to see if it is already a tables instance, and if so, # copies it - if dataset.__class__.__name__ == 'CArray': + if isinstance(dataset, tables.CArray): return dataset.copy(self.root.data, key) else: + # We need to determine atom and shape from the object that's + # been passed in. + # This assumes 'dataset' is a numpy object. + atom = tables.Atom.from_dtype(dataset.dtype) + shape = dataset.shape + return self.create_matrix(key, atom, shape, obj=dataset) def __delitem__(self, key): @@ -222,7 +216,7 @@ def __iter__(self): return self.iter_nodes(self.root.data, 'CArray') def __contains__(self, item): - return item in self.root.data._v_children + return item in self.root.data def open_omxfile( diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index 83f3f25ecb..f54f247ba1 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -27,6 +27,18 @@ def ones5x5(): return np.ones((5, 5)) +@pytest.fixture +def basic_omx(request, tmpomx, ones5x5): + f = open_omxfile(tmpomx, mode='w') + f['m1'] = ones5x5 + + def fin(): + f.close() + request.addfinalizer(fin) + + return f + + def test_open_readonly_hdf5_file(tmpomx): f = tables.open_file(tmpomx, 'w') f.close() @@ -35,11 +47,13 @@ def test_open_readonly_hdf5_file(tmpomx): f.close() -def test_setitem_getitem(tmpomx, ones5x5): +def test_set_get_del(tmpomx, ones5x5): with open_omxfile(tmpomx, 'w') as f: f['m1'] = ones5x5 npt.assert_array_equal(f['m1'], ones5x5) assert f.shape() == (5, 5) + del f['m1'] + assert 'm1' not in f def test_create_matrix(tmpomx, ones5x5): @@ -71,13 +85,17 @@ def test_add_matrix_with_same_name(tmpomx, ones5x5): f['m1'] = ones5x5 -def test_get_length_of_file(tmpomx, ones5x5): - mats = ['m{}'.format(x) for x in range(5)] +def test_len_list_iter(tmpomx, ones5x5): + names = ['m{}'.format(x) for x in range(5)] with open_omxfile(tmpomx, 'w') as f: - for m in mats: + for m in names: f[m] = ones5x5 - assert f.list_matrices() == mats + for mat in f: + npt.assert_array_equal(mat, ones5x5) + + assert len(f) == len(names) + assert f.list_matrices() == names def test_shape(tmpomx): @@ -86,3 +104,25 @@ def test_shape(tmpomx): with pytest.raises(ShapeError): f['test'] = np.ones(10) + + +def test_contains(basic_omx): + assert 'm1' in basic_omx + + +def test_list_all_attrs(basic_omx, ones5x5): + basic_omx['m2'] = ones5x5 + + assert basic_omx.list_all_attributes() == [] + + basic_omx['m1'].attrs['a1'] = 'a1' + basic_omx['m1'].attrs['a2'] = 'a2' + basic_omx['m2'].attrs['a2'] = 'a2' + basic_omx['m2'].attrs['a3'] = 'a3' + + assert basic_omx.list_all_attributes() == ['a1', 'a2', 'a3'] + + +def test_set_with_carray(basic_omx): + basic_omx['m2'] = basic_omx['m1'] + npt.assert_array_equal(basic_omx['m2'], basic_omx['m1']) From 1febc8458c8271d8ee8989f9489a0e1184b7f6fb Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 21:51:24 -0800 Subject: [PATCH 15/17] Split getitem and get_matrices_by_attr Instead of having a dual-purpose __getitem__, only have it return matrices by name. To get matrices matching a key/value attribute pair use the method get_matrices_by_attr. --- activitysim/omx/omxfile.py | 44 +++++++++++---------------- activitysim/omx/tests/test_omxfile.py | 20 ++++++++++++ 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index 86e9078832..46cbef579b 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -158,38 +158,30 @@ def create_mapping(self, title, entries, overwrite=False): return mymap - # The following functions implement Python list/dictionary lookups. ---- def __getitem__(self, key): """Return a matrix by name, or a list of matrices by attributes""" + return self.get_node(self.root.data, key) - if isinstance(key, str): - return self.get_node(self.root.data, key) - - if 'keys' not in dir(key): - raise LookupError('Key %s not found' % key) - - # Loop through key/value pairs - mats = self.list_nodes(self.root.data, 'CArray') - for a in key.keys(): - mats = self._get_matrices_by_attribute(a, key[a], mats) - - return mats - - def _get_matrices_by_attribute(self, key, value, matrices=None): - answer = [] - - if matrices is None: - matrices = self.list_nodes(self.root.data, 'CArray') + def get_matrices_by_attr(self, key, value): + """ + Returns a list of matrices that have an attribute matching + a certain value. - for m in matrices: - if m.attrs is None: - continue + Parameters + ---------- + key : str + Attribute name to match. + value : object + Attribute value to match. - # Only test if key is present in matrix attributes - if key in m.attrs and m.attrs[key] == value: - answer.append(m) + Returns + ------- + matrices : list - return answer + """ + return [ + m for m in self + if key in m.attrs and m.attrs[key] == value] def __len__(self): return len(self.list_nodes(self.root.data, 'CArray')) diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index f54f247ba1..9b5bb6d239 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -123,6 +123,26 @@ def test_list_all_attrs(basic_omx, ones5x5): assert basic_omx.list_all_attributes() == ['a1', 'a2', 'a3'] +def test_matrices_by_attr(basic_omx, ones5x5): + bo = basic_omx + bo['m2'] = ones5x5 + bo['m3'] = ones5x5 + + for m in bo: + m.attrs['a1'] = 'a1' + m.attrs['a2'] = 'a2' + bo['m3'].attrs['a2'] = 'a22' + bo['m3'].attrs['a3'] = 'a3' + + gmba = bo.get_matrices_by_attr + + assert gmba('zz', 'zz') == [] + assert gmba('a1', 'a1') == [bo['m1'], bo['m2'], bo['m3']] + assert gmba('a2', 'a2') == [bo['m1'], bo['m2']] + assert gmba('a2', 'a22') == [bo['m3']] + assert gmba('a3', 'a3') == [bo['m3']] + + def test_set_with_carray(basic_omx): basic_omx['m2'] = basic_omx['m1'] npt.assert_array_equal(basic_omx['m2'], basic_omx['m1']) From 64db23b57efe25a1354b499d766c0a70dd4701e8 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 11 Dec 2014 22:03:28 -0800 Subject: [PATCH 16/17] Add Apache v2 license and license notices to OMX files --- activitysim/omx/LICENSE.TXT | 202 ++++++++++++++++++++++++++ activitysim/omx/__init__.py | 5 + activitysim/omx/exceptions.py | 6 + activitysim/omx/omxfile.py | 5 + activitysim/omx/tests/test_omxfile.py | 5 + 5 files changed, 223 insertions(+) create mode 100644 activitysim/omx/LICENSE.TXT diff --git a/activitysim/omx/LICENSE.TXT b/activitysim/omx/LICENSE.TXT new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/activitysim/omx/LICENSE.TXT @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/activitysim/omx/__init__.py b/activitysim/omx/__init__.py index 4608c44aa8..ab6663217f 100644 --- a/activitysim/omx/__init__.py +++ b/activitysim/omx/__init__.py @@ -1,2 +1,7 @@ +# Licensed under the Apache License, v2.0 +# See activitysim/omx/LICENSE.txt +# Modified from the original OMX library available at +# https://github.com/osPlanning/omx/tree/dev/api/python/omx + from .omxfile import OMXFile, open_omxfile, OMX_VERSION from .exceptions import ShapeError diff --git a/activitysim/omx/exceptions.py b/activitysim/omx/exceptions.py index 6f82cdb605..f7220e21bb 100644 --- a/activitysim/omx/exceptions.py +++ b/activitysim/omx/exceptions.py @@ -1,2 +1,8 @@ +# Licensed under the Apache License, v2.0 +# See activitysim/omx/LICENSE.txt +# Modified from the original OMX library available at +# https://github.com/osPlanning/omx/tree/dev/api/python/omx + + class ShapeError(Exception): pass diff --git a/activitysim/omx/omxfile.py b/activitysim/omx/omxfile.py index 46cbef579b..182d0d5362 100644 --- a/activitysim/omx/omxfile.py +++ b/activitysim/omx/omxfile.py @@ -1,3 +1,8 @@ +# Licensed under the Apache License, v2.0 +# See activitysim/omx/LICENSE.txt +# Modified from the original OMX library available at +# https://github.com/osPlanning/omx/tree/dev/api/python/omx + import numpy as np import tables diff --git a/activitysim/omx/tests/test_omxfile.py b/activitysim/omx/tests/test_omxfile.py index 9b5bb6d239..7f8b6ac23a 100644 --- a/activitysim/omx/tests/test_omxfile.py +++ b/activitysim/omx/tests/test_omxfile.py @@ -1,3 +1,8 @@ +# Licensed under the Apache License, v2.0 +# See activitysim/omx/LICENSE.txt +# Modified from the original OMX library available at +# https://github.com/osPlanning/omx/tree/dev/api/python/omx + import os import tempfile From e1398aeeaff3375fc0a3b8a7517c6de9a5123711 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Fri, 12 Dec 2014 10:44:14 -0800 Subject: [PATCH 17/17] explicitly add tables and urbansim as dependencies --- .travis.yml | 2 +- setup.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4af264e0bc..94539674f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - > conda create -q -c synthicity -n test-environment python=$TRAVIS_PYTHON_VERSION - numpy pandas pip pytest urbansim + numpy pandas pip pytables pytest urbansim - source activate test-environment - pip install pytest-cov coveralls pep8 - pip install . diff --git a/setup.py b/setup.py index 2bbe07261e..d88ecdb149 100644 --- a/setup.py +++ b/setup.py @@ -20,5 +20,7 @@ install_requires=[ 'numpy>=1.8.0', 'pandas>=0.13.1', + 'tables>=3.1.0', + 'urbansim>=1.3' ] )