NCCOOS Trac Projects: Top | Web | Platforms | Processing | Viz | Sprints | Sandbox | (Wind)

root/spongenet/trunk/spongenet/parse.py

Revision 369 (checked in by cbc, 14 years ago)

Doctests for classes in parse module.

Line 
1 #!/usr/bin/env python
2
3 """
4 Parse combined sponge data XML files.
5
6 Usage:
7
8    > python parse.py path/to/xml/file
9    > python parse.py -t
10    > python parse.py --test
11
12 Test silent import.
13
14 >>> import parse
15 """
16
17 __author__ = "Chris Calloway"
18 __email__ = "cbc@chriscalloway.org"
19 __copyright__ = "Copyright 2010 UNC-CH Department of Marine Science"
20 __license__ = "GPL2"
21
22 import sys
23 import os
24 import re
25 import glob
26 import hashlib
27 import doctest
28 import unittest
29 from StringIO import StringIO
30 import xml.etree.cElementTree as ET
31
32 USAGE = "\n".join(__doc__.splitlines()[3:8])
33 TEST_PATH = os.path.join("tests", "parse")
34 XMLNS_PATTERN = re.compile(r"(\{.*\})(.*)")
35
36
37 def _test():
38     """
39     Run doctests as unittest suite.
40
41     Test silent import
42
43     >>> from parse import _test
44     """
45
46     suite = []
47     suite.append(doctest.DocTestSuite())
48     suite = unittest.TestSuite(suite)
49     unittest.TextTestRunner().run(suite)
50
51     return
52
53
54 def xmldoc_path():
55     """
56     Return the XML document file path from the command line.
57
58     Supply too few arguments on command line.
59
60     >>> save_stdout = sys.stdout
61     >>> temp_stdout = StringIO()
62     >>> sys.stdout = temp_stdout
63     >>> sys.argv = []
64     >>> _xmldoc_path = xmldoc_path()
65     >>> sys.stdout = save_stdout
66     >>> USAGE == temp_stdout.getvalue()[:-1]
67     True
68
69     Supply too many arguments on the command line.
70
71     >>> save_stdout = sys.stdout
72     >>> temp_stdout = StringIO()
73     >>> sys.stdout = temp_stdout
74     >>> sys.argv = ["", "", "",]
75     >>> _xmldoc_path = xmldoc_path()
76     >>> sys.stdout = save_stdout
77     >>> USAGE == temp_stdout.getvalue()[:-1]
78     True
79
80     Supply non-file argument.
81
82     >>> save_stdout = sys.stdout
83     >>> temp_stdout = StringIO()
84     >>> sys.stdout = temp_stdout
85     >>> _xmldoc_path = os.path.join(
86     ...                    os.path.dirname(
87     ...                        os.path.abspath(__file__)),
88     ...                    TEST_PATH)
89     >>> sys.argv = ["", _xmldoc_path]
90     >>> _xmldoc_path = xmldoc_path()
91     >>> sys.stdout = save_stdout
92     >>> USAGE == temp_stdout.getvalue()[:-1]
93     True
94
95     Supply nonexistent file argument.
96
97     >>> save_stdout = sys.stdout
98     >>> temp_stdout = StringIO()
99     >>> sys.stdout = temp_stdout
100     >>> _xmldoc_path = os.path.join(
101     ...                    os.path.dirname(
102     ...                        os.path.abspath(__file__)),
103     ...                    TEST_PATH, "xxxxx")
104     >>> sys.argv = ["", _xmldoc_path]
105     >>> _xmldoc_path = xmldoc_path()
106     >>> sys.stdout = save_stdout
107     >>> USAGE == temp_stdout.getvalue()[:-1]
108     True
109
110     Supply valid XML document path argument.
111
112     >>> _xmldoc_path = os.path.join(
113     ...                    os.path.dirname(
114     ...                        os.path.abspath(__file__)),
115     ...                    TEST_PATH, "xml","*","*.xml")
116     >>> _xmldoc_path = glob.glob(_xmldoc_path)[0]
117     >>> sys.argv = ["", _xmldoc_path]
118     >>> _xmldoc_path == xmldoc_path()
119     True
120     """
121
122     path = None
123     try:
124         if len(sys.argv) == 2:
125             if sys.argv[1] == "-t" or sys.argv[1] == "--test":
126                 _test()
127             else:
128                 path = sys.argv[1]
129                 if not os.path.exists(path):
130                     raise IOError(path + \
131                                   " does not exist.")
132                 elif not os.path.isfile(path):
133                     raise IOError(path + \
134                                   " is not a file.")
135         else:
136             raise IOError("Incorrect number of arguments supplied.")
137     except IOError:
138         print USAGE
139     return path
140
141
142 def xmldoc(path):
143     """
144     Return the XML document as a string from a file at path.
145
146     Get the test reference data.
147
148     >>> xmlref = os.path.join(
149     ...              os.path.dirname(
150     ...                  os.path.abspath(__file__)),
151     ...               TEST_PATH, "xmlref.py")
152     >>> namespace = {}
153     >>> execfile(xmlref, globals(), namespace)
154     >>> xml_doc_lens = namespace["XML_DOC_LENS"]
155     >>> xml_doc_md5s = namespace["XML_DOC_MD5S"]
156
157     Pick a test document.
158
159     >>> xmldoc_glob = os.path.join(
160     ...                   os.path.dirname(
161     ...                       os.path.abspath(__file__)),
162     ...                   TEST_PATH, "xml","*","*.xml")
163     >>> _xmldoc_path = glob.glob(xmldoc_glob)[0]
164     >>> _xmldoc = xmldoc(_xmldoc_path)
165
166     Verify the test document matches the reference data.
167
168     >>> xmldoc_lines = _xmldoc.splitlines()
169     >>> xmldoc_lines[0] == '<?xml version="1.0" encoding="utf-8"?>'
170     True
171     >>> xmldoc_lines[-1] == '</root>'
172     True
173     >>> len(xmldoc_lines) == xml_doc_lens[os.path.basename(_xmldoc_path)]
174     True
175     >>> doc_hash = hashlib.md5()
176     >>> doc_hash.update(_xmldoc)
177     >>> doc_hash.hexdigest() == xml_doc_md5s[os.path.basename(_xmldoc_path)]
178     True
179     """
180
181     _xmldoc = None
182     with open(path) as handle:
183         _xmldoc = handle.readlines()
184
185     return "".join(_xmldoc)
186
187
188 class Point(object):
189     """
190     A data point for a sponge sensor sample."
191
192     Instantiate a valid Point object.
193
194     >>> xmldoc_glob = os.path.join(
195     ...                   os.path.dirname(
196     ...                       os.path.abspath(__file__)),
197     ...                   TEST_PATH, "xml","*","*.xml")
198     >>> _xmldoc_path = glob.glob(xmldoc_glob)[0]
199     >>> _xmldoc = xmldoc(_xmldoc_path)
200     >>> _data = Data(_xmldoc)
201     >>> point = _data.devices[0].sensors[0].points[0]
202     >>> point.id != None
203     True
204     >>> point.descr != None
205     True
206     >>> point.type != None
207     True
208     >>> point.format != None
209     True
210     >>> point.unit != None
211     True
212     >>> point.rangemin != None
213     True
214     >>> point.rangemax != None
215     True
216     >>> point.value != None
217     True
218     """
219
220     def __init__(self, point):
221         for key, value in point.attrib.items():
222             key = key.lower()
223             super(Point, self).__setattr__(key, value)
224         for elem in point.getchildren():
225             tag = XMLNS_PATTERN.search(elem.tag).groups()[1].lower()
226             super(Point, self).__setattr__(tag, elem.text)
227
228
229 class Sensor(object):
230     """
231     A collection of data points for a sponge sensor sample.
232
233     Instantiate a valid Sensor object.
234
235     >>> xmldoc_glob = os.path.join(
236     ...                   os.path.dirname(
237     ...                       os.path.abspath(__file__)),
238     ...                   TEST_PATH, "xml","*","*.xml")
239     >>> _xmldoc_path = glob.glob(xmldoc_glob)[0]
240     >>> _xmldoc = xmldoc(_xmldoc_path)
241     >>> _data = Data(_xmldoc)
242     >>> sensor = _data.devices[0].sensors[0]
243     >>> len(sensor.points)
244     3
245     >>> sensor.id != None
246     True
247     >>> sensor.serialno != None
248     True
249     >>> sensor.prodno != None
250     True
251     >>> sensor.prodname != None
252     True
253     >>> sensor.descr != None
254     True
255     >>> sensor.adr != None
256     True
257     >>> sensor.protocolver != None
258     True
259     >>> sensor.verticalposition != None
260     True
261     >>> sensor.status != None
262     True
263     """
264
265     def __init__(self, sensor, xmlns):
266         for key, value in sensor.attrib.items():
267             key = key.lower()
268             super(Sensor, self).__setattr__(key, value)
269         for elem in sensor.getchildren():
270             tag = XMLNS_PATTERN.search(elem.tag).groups()[1].lower()
271             if tag == "parameters":
272                 self.points = [Point(point) for point
273                                 in elem.findall(xmlns + "Point")]
274             else:
275                 super(Sensor, self).__setattr__(tag, elem.text)
276
277
278 class Device(object):
279     """
280     Data from a collection of sponge sensors for a single time sample.
281
282     Instantiate a valid Device object.
283
284     >>> xmldoc_glob = os.path.join(
285     ...                   os.path.dirname(
286     ...                       os.path.abspath(__file__)),
287     ...                   TEST_PATH, "xml","*","*.xml")
288     >>> _xmldoc_path = glob.glob(xmldoc_glob)[0]
289     >>> _xmldoc = xmldoc(_xmldoc_path)
290     >>> _data = Data(_xmldoc)
291     >>> device = _data.devices[0]
292     >>> len(device.sensors)
293     15
294     >>> device.id != None
295     True
296     >>> device.sessionid != None
297     True
298     >>> device.descr != None
299     True
300     >>> device.serialno != None
301     True
302     >>> device.prodno != None
303     True
304     >>> device.prodname != None
305     True
306     >>> device.devicetype != None
307     True
308     >>> device.protocolver != None
309     True
310     >>> device.time != None
311     True
312     >>> device.status != None
313     True
314     >>> device.location != None
315     True
316     >>> device.verticalposition != None
317     True
318     >>> device.owner != None
319     True
320     >>> device.recordnumber != None
321     True
322     >>> device.data_time != None
323     True
324     >>> device.data_sessionid != None
325     True
326     """
327
328     def __init__(self, device, xmlns):
329         for key, value in device.attrib.items():
330             key = key.lower()
331             super(Device, self).__setattr__(key, value)
332         for elem in device.getchildren():
333             tag = XMLNS_PATTERN.search(elem.tag).groups()[1].lower()
334             if tag == "siteinfo":
335                 for subelem in elem.getchildren():
336                     tag = XMLNS_PATTERN.search(subelem.tag).groups()[1].lower()
337                     super(Device, self).__setattr__(tag, subelem.text)
338             elif tag == "data":
339                 for key, value in elem.attrib.items():
340                     key = key.lower()
341                     if key == "time":
342                         key = "data_time"
343                     elif key == "sessionid":
344                         key = "data_sessionid"
345                     super(Device, self).__setattr__(key, value)
346                 self.sensors = [Sensor(sensor, xmlns) for sensor
347                                 in elem.findall(xmlns + "SensorData")]
348             else:
349                 super(Device, self).__setattr__(tag, elem.text)
350
351
352 class Data(object):
353     """
354     A collection of sponge data samples from a collection of sensors.
355
356     Instantiate a valid Data object.
357
358     >>> xmldoc_glob = os.path.join(
359     ...                   os.path.dirname(
360     ...                       os.path.abspath(__file__)),
361     ...                   TEST_PATH, "xml","*","*.xml")
362     >>> _xmldoc_path = glob.glob(xmldoc_glob)[0]
363     >>> _xmldoc = xmldoc(_xmldoc_path)
364     >>> _data = Data(_xmldoc)
365     >>> _data.xmlns
366     '{http://www.aadi.no/RTOutSchema}'
367     >>> len(_data.devices)
368     50
369     """
370
371     def __init__(self, _xmldoc):
372         """
373         Initialize a new sponge data tree.
374         """
375
376         tree = ET.XML(_xmldoc)
377         self.xmlns = XMLNS_PATTERN.search(
378                          tree.getchildren()[0].tag).groups()[0]
379         self.devices = [Device(device, self.xmlns) for device
380                         in tree.findall(self.xmlns + "Device")]
381
382
383 def _main():
384     """
385     Run module as script.
386
387     Test silent import.
388
389     >>> from parse import _main
390     """
391
392     data = None
393
394     _xmldoc_path = xmldoc_path()
395     if _xmldoc_path:
396         _xmldoc = xmldoc(_xmldoc_path)
397         data = Data(_xmldoc)
398
399     return data
400
401 if __name__ == "__main__":
402     DATA = _main()
403     print "DATA =", DATA
Note: See TracBrowser for help on using the browser.