Skip to content

Commit 75aaa6f

Browse files
authored
Merge pull request #12 from aodn/refactor_and_cleanup
Refactor and cleanup template.py
2 parents a79b7ab + 549c2fd commit 75aaa6f

1 file changed

Lines changed: 39 additions & 125 deletions

File tree

ncwriter/template.py

Lines changed: 39 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33
written by: Hugo Oliveira ocehugo@gmail.com
44
"""
55

6-
# TODO write tests
76
# TODO how we handle groups!?
87
# TODO cleanup the user precedence rules of fill_values
98
# TODO is_dim_consistent too complex
10-
# TODO change_time too complex
119
# TODO check_var too complex
1210
# TODO createVariables too complex
13-
# TODO implement from_file/from_cdl/from_json kwarg!?
1411
# TODO Allow for attribute types to be specified in JSON
1512

1613
import json
@@ -27,69 +24,52 @@ def __init__(self,
2724
dimensions=None,
2825
variables=None,
2926
global_attributes=None,
30-
title='NetCDFGroupDict',
3127
**kwargs):
3228
""" A dictionary to hold netCDF groups
3329
It consist of a generic class holding 3 different dictionaries:
3430
dimensions is a <key:int> dict
3531
variables is <key:[str,class,list,dict,int]> dict
3632
global_attributes is a <key:int> dict
3733
38-
This class has __add__ to growth variables/dims/global attrs
39-
and __sub__ to remove unwanted variables from
40-
other :NetCDFGroupDict: instances.
34+
This class has __add__ to combine variables/dimensions/global attributes
35+
from :NetCDFGroupDict: instances.
4136
4237
Example:
4338
dmn = {'lon':360,'lat':210}
4439
var = {}
4540
var['water'] = {'type':'double','dimensions':['lat','lon']}
4641
w1 = NetCDFGroupDict(dimensions=dmn,variables=var)
42+
4743
dmn2 = {'time':300,'lon':720,'lat':330}
4844
var2 = {}
4945
var2['temp'] = {'type':'double','dimensions':['time','lat','lon']}
5046
w2 = NetCDFGroupDict(dimensions=dmn2,variables=var2)
47+
5148
w3 = w1+w2
5249
#w3.variables.keys() = ['water','temp']
5350
#w3.dimensions = {'time':300,'lon':360,'lat':210}
54-
w4 = w2-w1
55-
#w4.variables.keys() = ['temp']
56-
#w4.dimensions = {'lon':720,'lat':330,'time':300}
5751
"""
5852
self._dimensions = None
5953
self._variables = None
6054
self._global_attributes = None
6155

62-
self.title = title
6356
self.dimensions = dimensions or OrderedDict()
6457
self.variables = variables or OrderedDict()
6558
self.global_attributes = global_attributes or OrderedDict()
6659

67-
if self.is_dim_consistent:
68-
self.rdimensions = dict((x, True) if y is -1 else (x, False)
69-
for x, y in zip(self.dimensions.keys(),
70-
self.dimensions.values()))
71-
else:
72-
raise TypeError("Correct the dimensions.")
73-
74-
notstr = self.title.__class__ is not str
75-
if notstr:
76-
raise TypeError("Title is not a str object")
77-
78-
self.check_dims(self.dimensions)
7960
self.check_var(self.variables)
80-
self.check_global_attributes(self.global_attributes)
8161
self.check_consistency(self.dimensions, self.variables)
8262

8363
def __add__(self, other):
8464
self_copy = deepcopy(self)
8565
self_copy.dimensions.update(other.dimensions)
8666
self_copy.variables.update(other.variables)
8767
self_copy.global_attributes.update(other.global_attributes)
88-
self_copy.title = "{t1} + {t2}".format(t1=self.title, t2=other.title)
8968
return self_copy
9069

9170
@property
9271
def dimensions(self):
72+
"""Property to store the dictionary mapping dimension names to their sizes."""
9373
return self._dimensions
9474

9575
@dimensions.setter
@@ -99,6 +79,9 @@ def dimensions(self, value):
9979

10080
@property
10181
def variables(self):
82+
"""Property to store dictionary of variables. Keys are variable names, values are dictionaries of variable
83+
properties (dimensions, type, attributes, etc...)
84+
"""
10285
return self._variables
10386

10487
@variables.setter
@@ -108,6 +91,7 @@ def variables(self, value):
10891

10992
@property
11093
def global_attributes(self):
94+
"""Property to store dictionary of global attributes"""
11195
return self._global_attributes
11296

11397
@global_attributes.setter
@@ -116,8 +100,7 @@ def global_attributes(self, value):
116100
self._global_attributes = value
117101

118102
def is_dim_consistent(self):
119-
"""Check if the variable dictionary
120-
is consistent with current dimensions"""
103+
"""Check if the variable dictionary is consistent with current dimensions"""
121104
checkdims = set()
122105
for k in self.variables.keys():
123106
try:
@@ -154,75 +137,9 @@ def is_dim_consistent(self):
154137
else:
155138
return True
156139

157-
def search_time_in_vars(self):
158-
"""Check all vars for specific time variables associated with them"""
159-
tvars = set()
160-
for v in self.variables:
161-
try:
162-
tvars.add(self.variables[v]['attributes']['time']['value'])
163-
except KeyError:
164-
None
165-
166-
isnone = tvars == set()
167-
if isnone:
168-
return None
169-
else:
170-
return tvars
171-
172-
def change_time(self, var, timevar):
173-
"""Change the time dimension associated with variable :var:
174-
:var: a list or str
175-
Ex: 'zeta'
176-
['zeta','u']
177-
['u','v']
178-
['Ptracer1','Ptracer2']
179-
:timevar: a list or str
180-
Ex: 'bry_time'
181-
['zeta_time','uv_time']
182-
['uv_time']
183-
['ptime1','ptime2']
184-
"""
185-
186-
if var.__class__ is str:
187-
var = [var]
188-
if timevar.__class__ is str:
189-
timevar = [timevar]
190-
191-
if len(var) == 1 and len(timevar) > 1:
192-
raise ValueError('Invalid input')
193-
elif len(var) > 1 and len(timevar) == 1:
194-
timevar = [timevar for x in range(len(var))]
195-
196-
for v, t in zip(var, timevar):
197-
vargroup = set(self.variables.keys())
198-
dimgroup = set(self.dimensions.keys())
199-
v_included = v in vargroup
200-
t_included = t in vargroup and t in dimgroup
201-
202-
# varname should match dimname for time info
203-
if not t_included:
204-
raise ValueError('Time variable:', t, 'not present!')
205-
if not v_included:
206-
for k in self.variables.keys():
207-
if v in k:
208-
self.variables[k]['dimensions'][0] = t
209-
self.variables[k]['attributes']['time']['value'] = t
210-
else:
211-
self.variables[v]['dimensions'][0] = t
212-
self.variables[v]['attributes']['time']['value'] = t
213-
214-
@classmethod
215-
def check_dims(self, dimdict):
216-
""" Check the dictionary """
217-
for d in dimdict:
218-
notint = dimdict[d].__class__ is not int
219-
if notint:
220-
ValueError("Dimension %s is not an integer object" % d)
221-
222140
@classmethod
223-
def check_var(self, vardict, name=None):
224-
""" Check if the dictionary have all the reuqired fields
225-
to be defined as variable"""
141+
def check_var(cls, vardict, name=None):
142+
"""Check if the dictionary have all the required fields to be defined as variable """
226143
if name is None:
227144
name = 'input'
228145

@@ -235,7 +152,7 @@ def check_var(self, vardict, name=None):
235152

236153
if have_none:
237154
for k in vkeys:
238-
self.check_var(vardict[k], name=k)
155+
cls.check_var(vardict[k], name=k)
239156

240157
if have_dims:
241158
notnone = vardict['dimensions'] is not None
@@ -257,17 +174,9 @@ def check_var(self, vardict, name=None):
257174
ValueError(
258175
"Type for %s should be a string or type object" % name)
259176

260-
@classmethod
261-
def check_global_attributes(self, gadict):
262-
""" Check the dictionary """
263-
for g in gadict:
264-
notstr = gadict[g].__class__ is not str
265-
if notstr:
266-
ValueError("Global Attr %s is not an integer object" % g)
267-
268-
@classmethod
269-
def check_consistency(self, dimdict, vdict):
270-
""" Check the dictionary """
177+
@staticmethod
178+
def check_consistency(dimdict, vdict):
179+
"""Check that all dimensions referenced by variables in :vdict: are defined in the :dimdict:"""
271180
alldims = dimdict.keys()
272181
allvars = vdict.keys()
273182
for k in allvars:
@@ -282,24 +191,25 @@ def check_consistency(self, dimdict, vdict):
282191

283192

284193
class DatasetTemplate(NetCDFGroupDict):
194+
"""Template object used for creating netCDF files"""
195+
285196
def __init__(self, *args, **kwargs):
286197
super(DatasetTemplate, self).__init__(*args, **kwargs)
287-
self.cattrs = set([
288-
'zlib', 'complevel', 'shuffle', 'fletcher32', 'contiguous',
289-
'chunksizes', 'endian', 'least_significant_digit'
290-
])
291-
self.fill_aliases = set(
292-
['fill_value', 'missing_value', 'FillValue', '_FillValue'])
198+
self.cattrs = {'zlib', 'complevel', 'shuffle', 'fletcher32', 'contiguous', 'chunksizes', 'endian',
199+
'least_significant_digit'}
200+
self.fill_aliases = {'fill_value', 'missing_value', 'FillValue', '_FillValue'}
201+
self.outfile = None
202+
self.ncobj = None
293203

294204
@classmethod
295205
def from_json(cls, path):
296-
# load the JSON file into a dict
206+
"""Load template from a JSON file"""
207+
297208
with open(path) as f:
298209
template = json.load(f, object_pairs_hook=OrderedDict)
299210

300-
# e.g. this could call out to JSONschema to make sure the JSON has the expected
301-
# high level structure, and could refuse to create the object right here if it wasn't correct
302211
# TODO: validate_template(template)
212+
# - just check that the only top-level properties are "dimensions", "variables" and "global_attribute"
303213

304214
return cls(dimensions=template.get('dimensions'),
305215
variables=template.get('variables'),
@@ -312,7 +222,8 @@ def _create_var_opts(self, vdict):
312222
`zlib`
313223
`least_significant_digit`
314224
`dimensions`
315-
etc"""
225+
etc
226+
"""
316227
vset = set(list(vdict.keys()))
317228
inside = vset.intersection(self.cattrs)
318229
aliases = vset.intersection(self.fill_aliases)
@@ -357,13 +268,13 @@ def update_dimensions(self):
357268
)
358269
)
359270

360-
def createDimensions(self):
271+
def create_dimensions(self):
361272
"""Create the dimensions on the netcdf file"""
362273
for dname, dval in zip(self.dimensions.keys(),
363274
self.dimensions.values()):
364275
self.ncobj.createDimension(dname, dval)
365276

366-
def createVariables(self, **kwargs):
277+
def create_variables(self, **kwargs):
367278
"""Create all variables for the current class
368279
**kwargs are included here to overload all options for all variables
369280
like `zlib` and friends.
@@ -390,7 +301,7 @@ def createVariables(self, **kwargs):
390301

391302
var_c_opts.update(cwargs)
392303

393-
# user precendence
304+
# user precedence
394305
if ureq_fillvalue and vreq_fillvalue:
395306
[var_c_opts.pop(x) for x in vreq_fillvalue]
396307
fv_val = [var_c_opts.pop(x) for x in ureq_fillvalue]
@@ -419,7 +330,7 @@ def createVariables(self, **kwargs):
419330
attrs.pop(not_attr)
420331
ncvar.setncatts(attrs)
421332

422-
def createGlobalAttrs(self):
333+
def create_global_attributes(self):
423334
"""Add the global attributes for the current class"""
424335
for att in self.global_attributes.keys():
425336
self.ncobj.setncattr(att, self.global_attributes[att])
@@ -435,12 +346,15 @@ def to_netcdf(self, outfile, var_args={}, **kwargs):
435346
:return: None
436347
"""
437348
self.outfile = outfile
438-
self.ncobj = netCDF4.Dataset(self.outfile, mode='w', **kwargs)
439349

440350
self.update_dimensions()
441-
self.createDimensions()
442-
self.createVariables(**var_args)
443-
self.createGlobalAttrs()
351+
if not self.is_dim_consistent():
352+
raise ValueError("Dimensions.")
353+
354+
self.ncobj = netCDF4.Dataset(self.outfile, mode='w', **kwargs)
355+
self.create_dimensions()
356+
self.create_variables(**var_args)
357+
self.create_global_attributes()
444358
self.ncobj.sync()
445359
self.ncobj.close()
446360
self.ncobj = netCDF4.Dataset(self.outfile, 'a')

0 commit comments

Comments
 (0)