Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[1.3] Version handling, parser fixes, setup update #197

Merged
merged 23 commits into from
Dec 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d0fa7ee
[section] Add missing attributes to init
mpsonntag Nov 14, 2017
3e83205
[tools/odmlparser] Fix open JSON file bug
mpsonntag Nov 13, 2017
cf60c47
[tools/odmlparser] Fix open YAML file bug
mpsonntag Nov 13, 2017
52b094f
[tools/odmlparser] Fix YAML/JSON Reader.from_str
mpsonntag Nov 13, 2017
ea3f263
[test/parser] Fix open YAML/JSON file bug
mpsonntag Nov 14, 2017
e2be96f
[tools/odmlparser] Pep8 cleanup
mpsonntag Nov 13, 2017
d2550f6
[init] Update version number
mpsonntag Dec 6, 2017
00c995b
[info] Add info file to provide version and format
mpsonntag Dec 6, 2017
88fc8c9
[init] User version number from info
mpsonntag Dec 6, 2017
1820178
[odmlparser] Use format version number from info
mpsonntag Dec 6, 2017
7d61eac
[xmlparser] Use format version from info
mpsonntag Dec 6, 2017
9e0c50a
[xmlparser] Add odml format version check
mpsonntag Dec 6, 2017
713e720
[odmlparser] Add odml format version check
mpsonntag Dec 6, 2017
c9c4dd6
[init/setup] Update packaging information
mpsonntag Dec 7, 2017
577b8ff
[odmlparser] Save only valid odML documents
mpsonntag Dec 7, 2017
9b7baac
[odmlparser] Use xmlparser with ignore_errors
mpsonntag Dec 7, 2017
5f29fcd
[xmlparser] Add parser warning list
mpsonntag Dec 7, 2017
5d2701b
[odmlparser] Add parser warning list
mpsonntag Dec 7, 2017
64a91d5
[travis] Use master branch travis file
mpsonntag Dec 7, 2017
2415055
[parser] Update invalid odML format version text
mpsonntag Dec 11, 2017
f45e9bc
[fileio] Define parsers only in odmlparser
mpsonntag Dec 12, 2017
c8e6157
[fileio] Add docstrings
mpsonntag Dec 12, 2017
c2ae4b5
[xmlparser] Fixing typos
mpsonntag Dec 12, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ matrix:
- python: "3.4"
- python: "3.5"

before_install:
- sudo apt-get -qq update
- sudo apt-get install -y python-enum python-lxml

install:
- export PYVER=${TRAVIS_PYTHON_VERSION:0:1}
- pip install --upgrade coveralls
- pip install lxml enum34 pyyaml

script:
- python setup.py build
Expand Down
4 changes: 2 additions & 2 deletions odml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from . import section
from .dtypes import DType
from .fileio import load, save, display
from .info import VERSION
from .tools.odmlparser import allowed_parsers as parsers


__version__ = '1.3.2'
__version__ = VERSION

# the original property-function is overwritten
# so get it back!
Expand Down
27 changes: 25 additions & 2 deletions odml/fileio.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
from .tools.odmlparser import ODMLReader, ODMLWriter
from .tools.odmlparser import ODMLReader, ODMLWriter, allowed_parsers

parsers = ["xml", "json", "yaml"]
PARSERS = allowed_parsers


def load(filename, backend="xml"):
"""
Load an odML document from file.
:param filename: Path and filename from where the odML document
is to be loaded and parsed.
:param backend: File format of the file containing the odML document.
The default format is XML.
:return: The parsed odML document.
"""
reader = ODMLReader(backend)
return reader.from_file(filename)


def save(obj, filename, backend="xml"):
"""
Save an open odML document to file of a specified format.
:param obj: odML document do be saved.
:param filename: Filename and path where the odML document
should be saved.
:param backend: Format in which the odML document is to be saved.
The default format is XML.
"""
writer = ODMLWriter(backend)
return writer.write_file(obj, filename)


def display(obj, backend="xml"):
"""
Print an open odML document to the command line, formatted in the
specified format.
:param obj: odML document to be displayed.
:param backend: Format in which the odML document is to be displayed.
The default format is XML.
"""
writer = ODMLWriter(backend)
print(writer.to_string(obj))
15 changes: 15 additions & 0 deletions odml/info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
VERSION = '1.3.3'
FORMAT_VERSION = '1'
AUTHOR = 'Hagen Fritsch, Christian Kellner, Jan Grewe, ' \
'Achilleas Koutsou, Michael Sonntag, Lyuba Zehl'
COPYRIGHT = '(c) 2011-2017, German Neuroinformatics Node'
CONTACT = 'dev@g-node.org'
HOMEPAGE = 'https://github.com/G-Node/python-odml'
CLASSIFIERS = [
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'License :: OSI Approved :: BSD License',
'Development Status :: 5 - Production/Stable',
'Topic :: Scientific/Engineering',
'Intended Audience :: Science/Research'
]
7 changes: 6 additions & 1 deletion odml/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ class BaseSection(base.sectionable, mapping.mapableSection, Section):

_format = format.Section

def __init__(self, name, type="undefined", parent=None, definition=None, mapping=None):
def __init__(self, name, type="undefined", parent=None, definition=None,
mapping=None, reference=None, repository=None, link=None, include=None):
self._parent = parent
self._name = name
self._props = base.SmartList()
self._definition = definition
self._mapping = mapping
self._reference = reference
self._repository = repository
self._link = link
self._include = include
super(BaseSection, self).__init__()
# this may fire a change event, so have the section setup then
self.type = type
Expand Down
100 changes: 61 additions & 39 deletions odml/tools/odmlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,28 @@

"""

import yaml
import json
import yaml

from .. import format
from ..info import FORMAT_VERSION
from . import xmlparser

# FIX ME: Version should not be hardcoded here. Import from odML module after
# fixing the circular imports issue.
odml_version = '1'

allowed_parsers = ['ODML', 'XML', 'YAML', 'JSON']


class ParserException(Exception):
pass


class ODMLWriter:
'''
"""
A generic odML document writer, for XML, YAML and JSON.

Usage:
xml_writer = ODMLWriter(parser='XML')
xml_writer.write_file(odml_document, filepath)
'''
"""

def __init__(self, parser='XML'):
self.doc = None # odML document
Expand Down Expand Up @@ -55,7 +57,6 @@ def to_dict(self, odml_document):
self.parsed_doc = parsed_doc

def get_sections(self, section_list):

section_seq = []

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

def get_properties(self, props_list):

props_seq = []

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

props_seq.append(prop_dict)

return props_seq

def get_values(self, value_list):
Expand All @@ -122,6 +122,17 @@ def get_values(self, value_list):
return value_seq

def write_file(self, odml_document, filename):
# Write document only if it does not contain validation errors.
from ..validation import Validation # disgusting import problems
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to shuffle things around a bit to make this a bit more sane. It's possible to avoid this import cycle if we move the validation file to the tools submodule, but I think we have bigger import issues. One issue is we don't use explicit relative imports everywhere. This might lead to issues when testing local code while a global version of the package is installed. It might also lead to subtle bugs where local file location are changed, but the absolute import hides this by importing from an installed version.

This leads me to a different issue I came across. We have a lot of functions in the top-level __init__.py. This isn't a problem in and of itself, but we do import one of these functions into the validation file, which makes internal package imports impossible. So we end up with a situation where the validation file imports odml, which relies on importing everything else, which obviously would result in an import cycle.

validation = Validation(odml_document)
msg = ""
for e in validation.errors:
if e.is_error:
msg += "\n\t- %s %s: %s" % (e.obj, e.type, e.msg)
if msg != "":
msg = "Resolve document validation errors before saving %s" % msg
raise ParserException(msg)

file = open(filename, 'w')
file.write(self.to_string(odml_document))
file.close()
Expand All @@ -135,7 +146,7 @@ def to_string(self, odml_document):
self.to_dict(odml_document)
odml_output = {}
odml_output['Document'] = self.parsed_doc
odml_output['odml-version'] = odml_version
odml_output['odml-version'] = FORMAT_VERSION

if self.parser == 'YAML':
string_doc = yaml.dump(odml_output, default_flow_style=False)
Expand All @@ -150,8 +161,8 @@ class ODMLReader:
based on the given data exchange format, like XML, YAML or JSON.

Usage:
yaml_odml_doc = ODMLReader(parser='YAML').fromFile(open("odml_doc.yaml"))
json_odml_doc = ODMLReader(parser='JSON').fromFile(open("odml_doc.json"))
yaml_odml_doc = ODMLReader(parser='YAML').from_file("odml_doc.yaml")
json_odml_doc = ODMLReader(parser='JSON').from_file("odml_doc.json")
"""

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

def is_valid_attribute(self, attr, fmt):
if attr in fmt._args:
return attr
if fmt.revmap(attr):
return attr
print("Invalid element <%s> inside <%s> tag" % (attr, fmt.__class__.__name__))
msg = "Invalid element <%s> inside <%s> tag" % (attr, fmt.__class__.__name__)
print(msg)
self.warnings.append(msg)
return None

def to_odml(self):

self.odml_version = self.parsed_doc.get('odml-version', odml_version)
# Parse only odML documents of supported format versions.
if 'odml-version' not in self.parsed_doc:
raise ParserException("Invalid odML document: Could not find odml-version.")
elif self.parsed_doc.get('odml-version') != FORMAT_VERSION:
msg = ("Cannot read file: invalid odML document format version '%s'. \n"
"This package supports odML format versions: '%s'."
% (self.parsed_doc.get('odml-version'), FORMAT_VERSION))
raise ParserException(msg)

self.odml_version = self.parsed_doc.get('odml-version')
self.parsed_doc = self.parsed_doc['Document']

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

def parse_sections(self, section_list):

odml_sections = []

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

return odml_sections


def parse_properties(self, props_list):
odml_props = []

Expand Down Expand Up @@ -257,52 +277,54 @@ def parse_values(self, value_list):

return odml_values


def from_file(self, file):

if self.parser == 'XML' or self.parser == 'ODML':
odml_doc = xmlparser.XMLReader().fromFile(file)
par = xmlparser.XMLReader(ignore_errors=True)
self.warnings = par.warnings
odml_doc = par.fromFile(file)
self.doc = odml_doc
return odml_doc

elif self.parser == 'YAML':
try:
self.parsed_doc = yaml.load(file)
except yaml.parser.ParserError as e:
print(e)
return
finally:
file.close()
with open(file) as yaml_data:
try:
self.parsed_doc = yaml.load(yaml_data)
except yaml.parser.ParserError as e:
print(e)
return

return self.to_odml()

elif self.parser == 'JSON':
try:
self.parsed_doc = json.load(file)
except json.decoder.JSONDecodeError as e:
print(e)
return
finally:
file.close()
return self.to_odml()
with open(file) as json_data:
try:
self.parsed_doc = json.load(json_data)
except ValueError as e: # Python 2 does not support JSONDecodeError
print("JSON Decoder Error: %s" % e)
return

return self.to_odml()

def from_string(self, string):

if self.parser == 'XML' or self.parser == 'ODML':
odml_doc = xmlparser.XMLReader().fromString(string)
self.doc = odml_doc
return self.doc

elif self.parser == 'YAML':
try:
odml_doc = yaml.load(string)
self.parsed_doc = yaml.load(string)
except yaml.parser.ParserError as e:
print(e)
return
return self.to_odml()

elif self.parser == 'JSON':
try:
odml_doc = json.loads(string)
except json.decoder.JSONDecodeError as e:
print(e)
self.parsed_doc = json.loads(string)
except ValueError as e: # Python 2 does not support JSONDecodeError
print("JSON Decoder Error: %s" % e)
return
return self.to_odml()
Loading