diff --git a/MANIFEST.in b/MANIFEST.in index 01e330b6..00ee1263 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include LICENSE include README.rst -include odml.desktop +include odml/info.json diff --git a/README.rst b/README.rst index 86d5dd10..3ea6b4b4 100644 --- a/README.rst +++ b/README.rst @@ -21,13 +21,20 @@ Dependencies * enum (version 0.4.4) * lxml (version 3.7.2) + * yaml (version 3.12) + * rdflib (version >=4.2.2) -* These packages will be downloaded and installed automatically if the :code:`pip` method is used to install odML. Alternatively, they can be installed from the OS package manager. On Ubuntu, they are available as: +* These packages will be downloaded and installed automatically if the :code:`pip` + method is used to install odML. Alternatively, they can be installed from the OS + package manager. On Ubuntu, they are available as: * python-enum * python-lxml + * python-yaml + * python-rdflib -* If you prefer installing using the Python package manager, the following packages are required to build the lxml Python package on Ubuntu 14.04: +* If you prefer installing using the Python package manager, the following packages are + required to build the lxml Python package on Ubuntu 14.04: * libxml2-dev * libxslt1-dev @@ -43,7 +50,8 @@ The simplest way to install Python-odML is from PyPI using the pip tool:: On Ubuntu, the pip package manager is available in the repositories as :code:`python-pip`. -If this method is used, the appropriate Python dependencies (enum and lxml) are downloaded and installed automatically. +If this method is used, the appropriate Python dependencies are downloaded and installed +automatically. Building from source @@ -63,7 +71,8 @@ To install the Python-odML library, enter the corresponding directory and run:: $ cd python-odml $ python setup.py install -**Note** The master branch is our current development branch, not all features might be working as expected. Use the release tags instead. +**Note** The master branch is our current development branch, not all features might be +working as expected. Use the release tags instead. Documentation ------------- @@ -76,4 +85,6 @@ Bugs & Questions Should you find a behaviour that is likely a bug, please file a bug report at `the github bug tracker `_. -If you have questions regarding the use of the library or the editor, feel free to join the `#gnode `_ IRC channel on freenode. +If you have questions regarding the use of the library or the editor, feel free to +join the `#gnode `_ IRC channel +on freenode. diff --git a/odml/dtypes.py b/odml/dtypes.py index 36af0205..39d1e8df 100644 --- a/odml/dtypes.py +++ b/odml/dtypes.py @@ -204,8 +204,8 @@ def boolean_get(string): false = ["false", "0", False, "f"] if string in false: return False - return bool(string) - + # disallow any values that cannot be interpreted as boolean. + raise ValueError # Alias boolean_set to boolean_get. Both perform same function. diff --git a/odml/info.json b/odml/info.json new file mode 100644 index 00000000..8194bd0a --- /dev/null +++ b/odml/info.json @@ -0,0 +1,16 @@ +{ + "VERSION": "1.4.0", + "FORMAT_VERSION": "1.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" + ] +} diff --git a/odml/info.py b/odml/info.py index 709e19fb..210d06b1 100644 --- a/odml/info.py +++ b/odml/info.py @@ -1,15 +1,15 @@ -VERSION = '1.4.0' -FORMAT_VERSION = '1.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' -] +import os +import json + +here = os.path.dirname(__file__) + +with open(os.path.join(here, "info.json")) as infofile: + infodict = json.load(infofile) + +VERSION = infodict["VERSION"] +FORMAT_VERSION = infodict["FORMAT_VERSION"] +AUTHOR = infodict["AUTHOR"] +COPYRIGHT = infodict["COPYRIGHT"] +CONTACT = infodict["CONTACT"] +HOMEPAGE = infodict["HOMEPAGE"] +CLASSIFIERS = infodict["CLASSIFIERS"] diff --git a/odml/tools/dict_parser.py b/odml/tools/dict_parser.py index eeaf4df8..47ea641c 100644 --- a/odml/tools/dict_parser.py +++ b/odml/tools/dict_parser.py @@ -114,7 +114,10 @@ def to_odml(self, parsed_doc): self.parsed_doc = parsed_doc # Parse only odML documents of supported format versions. - if 'odml-version' not in self.parsed_doc: + if 'Document' not in self.parsed_doc: + msg = "Missing root element 'Document'" + raise ParserException(msg) + elif '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: diff --git a/setup.py b/setup.py index a5e1ff11..6419bf28 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +import json +import os import sys try: @@ -5,24 +7,17 @@ except ImportError as ex: from distutils.core import setup -try: - from odml.info import AUTHOR, CONTACT, CLASSIFIERS, HOMEPAGE, VERSION -except ImportError as ex: - # Read the information from odml.info.py if package dependencies - # are not yet available during a local install. - CLASSIFIERS = "" - with open('odml/info.py') as f: - for line in f: - curr_args = line.split(" = ") - if len(curr_args) == 2: - if curr_args[0] == "AUTHOR": - AUTHOR = curr_args[1].replace('\'', '').replace('\\', '').strip() - elif curr_args[0] == "CONTACT": - CONTACT = curr_args[1].replace('\'', '').strip() - elif curr_args[0] == "HOMEPAGE": - HOMEPAGE = curr_args[1].replace('\'', '').strip() - elif curr_args[0] == "VERSION": - VERSION = curr_args[1].replace('\'', '').strip() +with open(os.path.join("odml", "info.json")) as infofile: + infodict = json.load(infofile) + +VERSION = infodict["VERSION"] +FORMAT_VERSION = infodict["FORMAT_VERSION"] +AUTHOR = infodict["AUTHOR"] +COPYRIGHT = infodict["COPYRIGHT"] +CONTACT = infodict["CONTACT"] +HOMEPAGE = infodict["HOMEPAGE"] +CLASSIFIERS = infodict["CLASSIFIERS"] + packages = [ 'odml', @@ -32,7 +27,7 @@ with open('README.rst') as f: description_text = f.read() -install_req = ["lxml", "pyyaml", "rdflib", "rdflib-jsonld"] +install_req = ["lxml", "pyyaml", "rdflib"] if sys.version_info < (3, 4): install_req += ["enum34"] @@ -47,6 +42,7 @@ packages=packages, test_suite='test', install_requires=install_req, + include_package_data=True, long_description=description_text, classifiers=CLASSIFIERS, license="BSD" diff --git a/test/resources/invalid_root.xml b/test/resources/invalid_root.xml new file mode 100644 index 00000000..4e53f342 --- /dev/null +++ b/test/resources/invalid_root.xml @@ -0,0 +1,5 @@ + + 2018-02-27 + 1.0 + Test author + \ No newline at end of file diff --git a/test/resources/invalid_version.json b/test/resources/invalid_version.json new file mode 100644 index 00000000..b037933e --- /dev/null +++ b/test/resources/invalid_version.json @@ -0,0 +1,9 @@ +{ + "odml-version": "invalid", + "Document": { + "date": "2018-02-27", + "version": "1.0", + "sections": [], + "author": "Test author" + } +} \ No newline at end of file diff --git a/test/resources/invalid_version.xml b/test/resources/invalid_version.xml new file mode 100644 index 00000000..d9720c80 --- /dev/null +++ b/test/resources/invalid_version.xml @@ -0,0 +1,5 @@ + + 2018-02-27 + 1.0 + Test author + \ No newline at end of file diff --git a/test/resources/invalid_version.yaml b/test/resources/invalid_version.yaml new file mode 100644 index 00000000..362838be --- /dev/null +++ b/test/resources/invalid_version.yaml @@ -0,0 +1,6 @@ +Document: + author: Test author + date: '2018-02-27' + sections: [] + version: '1.0' +odml-version: 'totallyUnsupported' \ No newline at end of file diff --git a/test/resources/missing_root.json b/test/resources/missing_root.json new file mode 100644 index 00000000..ef8a1af6 --- /dev/null +++ b/test/resources/missing_root.json @@ -0,0 +1,6 @@ +{ + "odml-version": "1.1", + "sometag": { + "Document": "somecontent" + } +} \ No newline at end of file diff --git a/test/resources/missing_root.yaml b/test/resources/missing_root.yaml new file mode 100644 index 00000000..5b069050 --- /dev/null +++ b/test/resources/missing_root.yaml @@ -0,0 +1,2 @@ +Root: + some: value diff --git a/test/resources/missing_version.json b/test/resources/missing_version.json new file mode 100644 index 00000000..ddeaf022 --- /dev/null +++ b/test/resources/missing_version.json @@ -0,0 +1,8 @@ +{ + "Document": { + "date": "2018-02-27", + "version": "1.0", + "sections": [], + "author": "Test author" + } +} \ No newline at end of file diff --git a/test/resources/missing_version.xml b/test/resources/missing_version.xml new file mode 100644 index 00000000..181d1e89 --- /dev/null +++ b/test/resources/missing_version.xml @@ -0,0 +1,5 @@ + + 2018-02-27 + 1.0 + Test author + \ No newline at end of file diff --git a/test/resources/missing_version.yaml b/test/resources/missing_version.yaml new file mode 100644 index 00000000..6fc66ef2 --- /dev/null +++ b/test/resources/missing_version.yaml @@ -0,0 +1,5 @@ +Document: + author: Test author + date: '2018-02-27' + sections: [] + version: '1.0' diff --git a/test/test_dtypes.py b/test/test_dtypes.py index 56d6bd30..6e90e5e4 100644 --- a/test/test_dtypes.py +++ b/test/test_dtypes.py @@ -45,6 +45,24 @@ def test_str(self): self.assertEqual(s.value[0], 'Jerin') self.assertEqual(s.dtype, 'string') + def test_bool(self): + self.assertEqual(None, typ.boolean_get(None)) + + true_values = [True, "TRUE", "true", "T", "t", "1", 1] + for val in true_values: + self.assertTrue(typ.boolean_get(val)) + + false_values = [False, "FALSE", "false", "F", "f", "0", 0] + for val in false_values: + self.assertFalse(typ.boolean_get(val)) + + with self.assertRaises(ValueError): + typ.boolean_get("text") + with self.assertRaises(ValueError): + typ.boolean_get(12) + with self.assertRaises(ValueError): + typ.boolean_get(2.1) + def test_tuple(self): # Success test t = odml.Property(name="Location", value='(39.12; 67.19)', dtype='2-tuple') diff --git a/test/test_json_parser.py b/test/test_json_parser.py new file mode 100644 index 00000000..999cd3fa --- /dev/null +++ b/test/test_json_parser.py @@ -0,0 +1,50 @@ +import json +import os +import unittest + +from odml.tools import dict_parser +from odml.tools.parser_utils import ParserException + + +class TestJSONParser(unittest.TestCase): + + def setUp(self): + self.basepath = 'test/resources/' + + self.json_reader = dict_parser.DictReader() + + def test_missing_root(self): + filename = "missing_root.json" + message = "Missing root element" + + with open(os.path.join(self.basepath, filename)) as json_data: + parsed_doc = json.load(json_data) + + with self.assertRaises(ParserException) as exc: + _ = self.json_reader.to_odml(parsed_doc) + + self.assertIn(message, str(exc.exception)) + + def test_missing_version(self): + filename = "missing_version.json" + message = "Could not find odml-version" + + with open(os.path.join(self.basepath, filename)) as json_data: + parsed_doc = json.load(json_data) + + with self.assertRaises(ParserException) as exc: + _ = self.json_reader.to_odml(parsed_doc) + + self.assertIn(message, str(exc.exception)) + + def test_invalid_version(self): + filename = "invalid_version.json" + message = "invalid odML document format version" + + with open(os.path.join(self.basepath, filename)) as json_data: + parsed_doc = json.load(json_data) + + with self.assertRaises(ParserException) as exc: + _ = self.json_reader.to_odml(parsed_doc) + + self.assertIn(message, str(exc.exception)) diff --git a/test/test_property.py b/test/test_property.py index ba62e45e..debe542c 100644 --- a/test/test_property.py +++ b/test/test_property.py @@ -16,19 +16,36 @@ def test_value(self): def test_bool_conversion(self): - p = Property(name='received', value=[3, 0, 1, 0, 8]) + # Success tests + p = Property(name='received', value=[1, 0, 1, 0, 1]) assert(p.dtype == 'int') p.dtype = DType.boolean assert(p.dtype == 'boolean') assert(p.value == [True, False, True, False, True]) - q = Property(name='sent', - value=['False', True, 'TRUE', '0', 't', 'F', 'Ft']) + q = Property(name='sent', value=['False', True, 'TRUE', '0', 't', 'F', '1']) assert(q.dtype == 'string') q.dtype = DType.boolean assert(q.dtype == 'boolean') assert(q.value == [False, True, True, False, True, False, True]) + # Failure tests + curr_val = [3, 0, 1, 0, 8] + curr_type = 'int' + p = Property(name='received', value=curr_val) + assert(p.dtype == curr_type) + with self.assertRaises(ValueError): + p.dtype = DType.boolean + assert(p.dtype == curr_type) + assert(p.value == curr_val) + + curr_type = 'string' + q = Property(name='sent', value=['False', True, 'TRUE', '0', 't', '12', 'Ft']) + assert(q.dtype == curr_type) + with self.assertRaises(ValueError): + q.dtype = DType.boolean + assert(q.dtype == curr_type) + def test_str_to_int_convert(self): # Success Test diff --git a/test/test_xml_parser.py b/test/test_xml_parser.py new file mode 100644 index 00000000..fb557e16 --- /dev/null +++ b/test/test_xml_parser.py @@ -0,0 +1,40 @@ +import os +import unittest + +from odml.tools import xmlparser +from odml.tools.parser_utils import ParserException + + +class TestXMLParser(unittest.TestCase): + + def setUp(self): + self.basepath = 'test/resources/' + + self.xml_reader = xmlparser.XMLReader() + + def test_invalid_root(self): + filename = "invalid_root.xml" + message = "Expecting " + + with self.assertRaises(ParserException) as exc: + _ = self.xml_reader.from_file(os.path.join(self.basepath, filename)) + + self.assertIn(message, str(exc.exception)) + + def test_missing_version(self): + filename = "missing_version.xml" + message = "Could not find format version attribute" + + with self.assertRaises(ParserException) as exc: + _ = self.xml_reader.from_file(os.path.join(self.basepath, filename)) + + self.assertIn(message, str(exc.exception)) + + def test_invalid_version(self): + filename = "invalid_version.xml" + message = "invalid odML document format version" + + with self.assertRaises(ParserException) as exc: + _ = self.xml_reader.from_file(os.path.join(self.basepath, filename)) + + self.assertIn(message, str(exc.exception)) diff --git a/test/test_yaml_parser.py b/test/test_yaml_parser.py new file mode 100644 index 00000000..20dfc0b3 --- /dev/null +++ b/test/test_yaml_parser.py @@ -0,0 +1,50 @@ +import os +import unittest +import yaml + +from odml.tools import dict_parser +from odml.tools.parser_utils import ParserException + + +class TestYAMLParser(unittest.TestCase): + + def setUp(self): + self.basepath = 'test/resources/' + + self.yaml_reader = dict_parser.DictReader() + + def test_missing_root(self): + filename = "missing_root.yaml" + message = "Missing root element" + + with open(os.path.join(self.basepath, filename)) as raw_data: + parsed_doc = yaml.load(raw_data) + + with self.assertRaises(ParserException) as exc: + _ = self.yaml_reader.to_odml(parsed_doc) + + self.assertIn(message, str(exc.exception)) + + def test_missing_version(self): + filename = "missing_version.yaml" + message = "Could not find odml-version" + + with open(os.path.join(self.basepath, filename)) as raw_data: + parsed_doc = yaml.load(raw_data) + + with self.assertRaises(ParserException) as exc: + _ = self.yaml_reader.to_odml(parsed_doc) + + self.assertIn(message, str(exc.exception)) + + def test_invalid_version(self): + filename = "invalid_version.yaml" + message = "invalid odML document format version" + + with open(os.path.join(self.basepath, filename)) as raw_data: + parsed_doc = yaml.load(raw_data) + + with self.assertRaises(ParserException) as exc: + _ = self.yaml_reader.to_odml(parsed_doc) + + self.assertIn(message, str(exc.exception))