33written 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
1613import 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
284193class 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