Skip to content

Commit bf88a3c

Browse files
authored
Merge pull request #197 from mpsonntag/picks
[1.3] Version handling, parser fixes, setup update LGTM
2 parents fea65ed + c2ae4b5 commit bf88a3c

File tree

10 files changed

+157
-64
lines changed

10 files changed

+157
-64
lines changed

.travis.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ matrix:
1414
- python: "3.4"
1515
- python: "3.5"
1616

17-
before_install:
18-
- sudo apt-get -qq update
19-
- sudo apt-get install -y python-enum python-lxml
20-
2117
install:
2218
- export PYVER=${TRAVIS_PYTHON_VERSION:0:1}
2319
- pip install --upgrade coveralls
20+
- pip install lxml enum34 pyyaml
2421

2522
script:
2623
- python setup.py build

odml/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
from . import section
66
from .dtypes import DType
77
from .fileio import load, save, display
8+
from .info import VERSION
89
from .tools.odmlparser import allowed_parsers as parsers
910

10-
11-
__version__ = '1.3.2'
11+
__version__ = VERSION
1212

1313
# the original property-function is overwritten
1414
# so get it back!

odml/fileio.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,41 @@
1-
from .tools.odmlparser import ODMLReader, ODMLWriter
1+
from .tools.odmlparser import ODMLReader, ODMLWriter, allowed_parsers
22

3-
parsers = ["xml", "json", "yaml"]
3+
PARSERS = allowed_parsers
44

55

66
def load(filename, backend="xml"):
7+
"""
8+
Load an odML document from file.
9+
:param filename: Path and filename from where the odML document
10+
is to be loaded and parsed.
11+
:param backend: File format of the file containing the odML document.
12+
The default format is XML.
13+
:return: The parsed odML document.
14+
"""
715
reader = ODMLReader(backend)
816
return reader.from_file(filename)
917

1018

1119
def save(obj, filename, backend="xml"):
20+
"""
21+
Save an open odML document to file of a specified format.
22+
:param obj: odML document do be saved.
23+
:param filename: Filename and path where the odML document
24+
should be saved.
25+
:param backend: Format in which the odML document is to be saved.
26+
The default format is XML.
27+
"""
1228
writer = ODMLWriter(backend)
1329
return writer.write_file(obj, filename)
1430

1531

1632
def display(obj, backend="xml"):
33+
"""
34+
Print an open odML document to the command line, formatted in the
35+
specified format.
36+
:param obj: odML document to be displayed.
37+
:param backend: Format in which the odML document is to be displayed.
38+
The default format is XML.
39+
"""
1740
writer = ODMLWriter(backend)
1841
print(writer.to_string(obj))

odml/info.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
VERSION = '1.3.3'
2+
FORMAT_VERSION = '1'
3+
AUTHOR = 'Hagen Fritsch, Christian Kellner, Jan Grewe, ' \
4+
'Achilleas Koutsou, Michael Sonntag, Lyuba Zehl'
5+
COPYRIGHT = '(c) 2011-2017, German Neuroinformatics Node'
6+
CONTACT = '[email protected]'
7+
HOMEPAGE = 'https://github.com/G-Node/python-odml'
8+
CLASSIFIERS = [
9+
'Programming Language :: Python :: 2',
10+
'Programming Language :: Python :: 3',
11+
'License :: OSI Approved :: BSD License',
12+
'Development Status :: 5 - Production/Stable',
13+
'Topic :: Scientific/Engineering',
14+
'Intended Audience :: Science/Research'
15+
]

odml/section.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@ class BaseSection(base.sectionable, mapping.mapableSection, Section):
2525

2626
_format = format.Section
2727

28-
def __init__(self, name, type="undefined", parent=None, definition=None, mapping=None):
28+
def __init__(self, name, type="undefined", parent=None, definition=None,
29+
mapping=None, reference=None, repository=None, link=None, include=None):
2930
self._parent = parent
3031
self._name = name
3132
self._props = base.SmartList()
3233
self._definition = definition
3334
self._mapping = mapping
35+
self._reference = reference
36+
self._repository = repository
37+
self._link = link
38+
self._include = include
3439
super(BaseSection, self).__init__()
3540
# this may fire a change event, so have the section setup then
3641
self.type = type

odml/tools/odmlparser.py

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,28 @@
77
88
"""
99

10-
import yaml
1110
import json
11+
import yaml
12+
1213
from .. import format
14+
from ..info import FORMAT_VERSION
1315
from . import xmlparser
1416

15-
# FIX ME: Version should not be hardcoded here. Import from odML module after
16-
# fixing the circular imports issue.
17-
odml_version = '1'
18-
1917
allowed_parsers = ['ODML', 'XML', 'YAML', 'JSON']
2018

2119

20+
class ParserException(Exception):
21+
pass
22+
23+
2224
class ODMLWriter:
23-
'''
25+
"""
2426
A generic odML document writer, for XML, YAML and JSON.
2527
2628
Usage:
2729
xml_writer = ODMLWriter(parser='XML')
2830
xml_writer.write_file(odml_document, filepath)
29-
'''
31+
"""
3032

3133
def __init__(self, parser='XML'):
3234
self.doc = None # odML document
@@ -55,7 +57,6 @@ def to_dict(self, odml_document):
5557
self.parsed_doc = parsed_doc
5658

5759
def get_sections(self, section_list):
58-
5960
section_seq = []
6061

6162
for section in section_list:
@@ -81,7 +82,6 @@ def get_sections(self, section_list):
8182
return section_seq
8283

8384
def get_properties(self, props_list):
84-
8585
props_seq = []
8686

8787
for prop in props_list:
@@ -100,7 +100,7 @@ def get_properties(self, props_list):
100100
prop_dict[attr] = t
101101

102102
props_seq.append(prop_dict)
103-
103+
104104
return props_seq
105105

106106
def get_values(self, value_list):
@@ -122,6 +122,17 @@ def get_values(self, value_list):
122122
return value_seq
123123

124124
def write_file(self, odml_document, filename):
125+
# Write document only if it does not contain validation errors.
126+
from ..validation import Validation # disgusting import problems
127+
validation = Validation(odml_document)
128+
msg = ""
129+
for e in validation.errors:
130+
if e.is_error:
131+
msg += "\n\t- %s %s: %s" % (e.obj, e.type, e.msg)
132+
if msg != "":
133+
msg = "Resolve document validation errors before saving %s" % msg
134+
raise ParserException(msg)
135+
125136
file = open(filename, 'w')
126137
file.write(self.to_string(odml_document))
127138
file.close()
@@ -135,7 +146,7 @@ def to_string(self, odml_document):
135146
self.to_dict(odml_document)
136147
odml_output = {}
137148
odml_output['Document'] = self.parsed_doc
138-
odml_output['odml-version'] = odml_version
149+
odml_output['odml-version'] = FORMAT_VERSION
139150

140151
if self.parser == 'YAML':
141152
string_doc = yaml.dump(odml_output, default_flow_style=False)
@@ -150,8 +161,8 @@ class ODMLReader:
150161
based on the given data exchange format, like XML, YAML or JSON.
151162
152163
Usage:
153-
yaml_odml_doc = ODMLReader(parser='YAML').fromFile(open("odml_doc.yaml"))
154-
json_odml_doc = ODMLReader(parser='JSON').fromFile(open("odml_doc.json"))
164+
yaml_odml_doc = ODMLReader(parser='YAML').from_file("odml_doc.yaml")
165+
json_odml_doc = ODMLReader(parser='JSON').from_file("odml_doc.json")
155166
"""
156167

157168
def __init__(self, parser='XML'):
@@ -162,18 +173,29 @@ def __init__(self, parser='XML'):
162173
if parser not in allowed_parsers:
163174
raise NotImplementedError("'%s' odML parser does not exist!" % parser)
164175
self.parser = parser
176+
self.warnings = []
165177

166178
def is_valid_attribute(self, attr, fmt):
167179
if attr in fmt._args:
168180
return attr
169181
if fmt.revmap(attr):
170182
return attr
171-
print("Invalid element <%s> inside <%s> tag" % (attr, fmt.__class__.__name__))
183+
msg = "Invalid element <%s> inside <%s> tag" % (attr, fmt.__class__.__name__)
184+
print(msg)
185+
self.warnings.append(msg)
172186
return None
173187

174188
def to_odml(self):
175-
176-
self.odml_version = self.parsed_doc.get('odml-version', odml_version)
189+
# Parse only odML documents of supported format versions.
190+
if 'odml-version' not in self.parsed_doc:
191+
raise ParserException("Invalid odML document: Could not find odml-version.")
192+
elif self.parsed_doc.get('odml-version') != FORMAT_VERSION:
193+
msg = ("Cannot read file: invalid odML document format version '%s'. \n"
194+
"This package supports odML format versions: '%s'."
195+
% (self.parsed_doc.get('odml-version'), FORMAT_VERSION))
196+
raise ParserException(msg)
197+
198+
self.odml_version = self.parsed_doc.get('odml-version')
177199
self.parsed_doc = self.parsed_doc['Document']
178200

179201
doc_attrs = {}
@@ -193,7 +215,6 @@ def to_odml(self):
193215
return self.doc
194216

195217
def parse_sections(self, section_list):
196-
197218
odml_sections = []
198219

199220
for section in section_list:
@@ -218,7 +239,6 @@ def parse_sections(self, section_list):
218239

219240
return odml_sections
220241

221-
222242
def parse_properties(self, props_list):
223243
odml_props = []
224244

@@ -257,52 +277,54 @@ def parse_values(self, value_list):
257277

258278
return odml_values
259279

260-
261280
def from_file(self, file):
262281

263282
if self.parser == 'XML' or self.parser == 'ODML':
264-
odml_doc = xmlparser.XMLReader().fromFile(file)
283+
par = xmlparser.XMLReader(ignore_errors=True)
284+
self.warnings = par.warnings
285+
odml_doc = par.fromFile(file)
265286
self.doc = odml_doc
266287
return odml_doc
267288

268289
elif self.parser == 'YAML':
269-
try:
270-
self.parsed_doc = yaml.load(file)
271-
except yaml.parser.ParserError as e:
272-
print(e)
273-
return
274-
finally:
275-
file.close()
290+
with open(file) as yaml_data:
291+
try:
292+
self.parsed_doc = yaml.load(yaml_data)
293+
except yaml.parser.ParserError as e:
294+
print(e)
295+
return
296+
276297
return self.to_odml()
277298

278299
elif self.parser == 'JSON':
279-
try:
280-
self.parsed_doc = json.load(file)
281-
except json.decoder.JSONDecodeError as e:
282-
print(e)
283-
return
284-
finally:
285-
file.close()
286-
return self.to_odml()
300+
with open(file) as json_data:
301+
try:
302+
self.parsed_doc = json.load(json_data)
303+
except ValueError as e: # Python 2 does not support JSONDecodeError
304+
print("JSON Decoder Error: %s" % e)
305+
return
287306

307+
return self.to_odml()
288308

289309
def from_string(self, string):
290310

291311
if self.parser == 'XML' or self.parser == 'ODML':
292312
odml_doc = xmlparser.XMLReader().fromString(string)
293313
self.doc = odml_doc
294314
return self.doc
315+
295316
elif self.parser == 'YAML':
296317
try:
297-
odml_doc = yaml.load(string)
318+
self.parsed_doc = yaml.load(string)
298319
except yaml.parser.ParserError as e:
299320
print(e)
300321
return
301322
return self.to_odml()
323+
302324
elif self.parser == 'JSON':
303325
try:
304-
odml_doc = json.loads(string)
305-
except json.decoder.JSONDecodeError as e:
306-
print(e)
326+
self.parsed_doc = json.loads(string)
327+
except ValueError as e: # Python 2 does not support JSONDecodeError
328+
print("JSON Decoder Error: %s" % e)
307329
return
308330
return self.to_odml()

0 commit comments

Comments
 (0)