diff options
Diffstat (limited to 'contrib/libucl/python')
-rw-r--r-- | contrib/libucl/python/setup.py | 54 | ||||
-rw-r--r-- | contrib/libucl/python/src/uclmodule.c | 97 | ||||
-rwxr-xr-x | contrib/libucl/python/test.sh | 6 | ||||
-rwxr-xr-x | contrib/libucl/python/test_uclmodule.py | 148 | ||||
-rw-r--r-- | contrib/libucl/python/tests/__init__.py | 0 | ||||
-rw-r--r-- | contrib/libucl/python/tests/compat.py | 8 | ||||
-rw-r--r-- | contrib/libucl/python/tests/test_dump.py | 66 | ||||
-rw-r--r-- | contrib/libucl/python/tests/test_load.py | 107 | ||||
-rw-r--r-- | contrib/libucl/python/tests/test_validation.py | 50 |
9 files changed, 339 insertions, 197 deletions
diff --git a/contrib/libucl/python/setup.py b/contrib/libucl/python/setup.py index b2b8981..9e1151a 100644 --- a/contrib/libucl/python/setup.py +++ b/contrib/libucl/python/setup.py @@ -1,37 +1,43 @@ -import distutils.ccompiler -import distutils.sysconfig -from distutils.core import setup, Extension +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension + import os +import sys +tests_require = [] -compiler = distutils.ccompiler.new_compiler() -search_paths=[os.path.expanduser('~/{}'), '/opt/local/{}', '/usr/local/{}', '/usr/{}'] -lib_paths = [ a.format("lib") for a in search_paths] -inc_paths = [ a.format("include") for a in search_paths] +if sys.version < '2.7': + tests_require.append('unittest2') -uclmodule = Extension('ucl', - include_dirs = inc_paths, - library_dirs = lib_paths, - libraries = ['ucl'], - sources = ['src/uclmodule.c'], - runtime_library_dirs = lib_paths, - language='c') +uclmodule = Extension( + 'ucl', + libraries = ['ucl'], + sources = ['src/uclmodule.c'], + language = 'c' +) -setup(name='ucl', - version='1.0', - description='ucl parser and emmitter', +setup( + name = 'ucl', + version = '0.8', + description = 'ucl parser and emmitter', ext_modules = [uclmodule], - author="Eitan Adler", - author_email="lists@eitanadler.com", - url="https://github.com/vstakhov/libucl/", - license="MIT", - classifiers=["Development Status :: 3 - Alpha", + test_suite = 'tests', + tests_require = tests_require, + author = "Eitan Adler, Denis Volpato Martins", + author_email = "lists@eitanadler.com", + url = "https://github.com/vstakhov/libucl/", + license = "MIT", + classifiers = [ + "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: DFSG approved", "License :: OSI Approved :: MIT License", "Programming Language :: C", + "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries", - ] - ) + ] +) diff --git a/contrib/libucl/python/src/uclmodule.c b/contrib/libucl/python/src/uclmodule.c index bc13c70..fce0dab 100644 --- a/contrib/libucl/python/src/uclmodule.c +++ b/contrib/libucl/python/src/uclmodule.c @@ -2,6 +2,8 @@ #include <ucl.h> #include <Python.h> +static PyObject *SchemaError; + static PyObject * _basic_ucl_type (ucl_object_t const *obj) { @@ -13,9 +15,11 @@ _basic_ucl_type (ucl_object_t const *obj) case UCL_STRING: return Py_BuildValue ("s", ucl_object_tostring (obj)); case UCL_BOOLEAN: - return ucl_object_toboolean (obj) ? Py_True : Py_False; + return PyBool_FromLong (ucl_object_toboolean (obj)); case UCL_TIME: return Py_BuildValue ("d", ucl_object_todouble (obj)); + case UCL_NULL: + Py_RETURN_NONE; } return NULL; } @@ -124,26 +128,60 @@ _iterate_python (PyObject *obj) { if (obj == Py_None) { return ucl_object_new(); - } else if (PyBool_Check (obj)) { + } + else if (PyBool_Check (obj)) { return ucl_object_frombool (obj == Py_True); - } else if (PyInt_Check (obj)) { + } +#if PY_MAJOR_VERSION < 3 + else if (PyInt_Check (obj)) { return ucl_object_fromint (PyInt_AsLong (obj)); - } else if (PyFloat_Check (obj)) { + } +#endif + else if (PyLong_Check (obj)) { + return ucl_object_fromint (PyLong_AsLong (obj)); + } + else if (PyFloat_Check (obj)) { return ucl_object_fromdouble (PyFloat_AsDouble (obj)); - } else if (PyString_Check (obj)) { + } + else if (PyUnicode_Check (obj)) { + ucl_object_t *ucl_str; + PyObject *str = PyUnicode_AsASCIIString(obj); + ucl_str = ucl_object_fromstring (PyBytes_AsString (str)); + Py_DECREF(str); + return ucl_str; + } +#if PY_MAJOR_VERSION < 3 + else if (PyString_Check (obj)) { return ucl_object_fromstring (PyString_AsString (obj)); - // } else if (PyDateTime_Check (obj)) { } +#endif else if (PyDict_Check(obj)) { PyObject *key, *value; Py_ssize_t pos = 0; ucl_object_t *top, *elm; + char *keystr = NULL; top = ucl_object_typed_new (UCL_OBJECT); while (PyDict_Next(obj, &pos, &key, &value)) { elm = _iterate_python(value); - ucl_object_insert_key (top, elm, PyString_AsString(key), 0, true); + + if (PyUnicode_Check(key)) { + PyObject *keyascii = PyUnicode_AsASCIIString(key); + keystr = PyBytes_AsString(keyascii); + Py_DECREF(keyascii); + } +#if PY_MAJOR_VERSION < 3 + else if (PyString_Check(key)) { + keystr = PyString_AsString(key); + } +#endif + else { + PyErr_SetString(PyExc_TypeError, "Unknown key type"); + return NULL; + } + + ucl_object_insert_key (top, elm, keystr, 0, true); } return top; @@ -195,11 +233,6 @@ ucl_dump (PyObject *self, PyObject *args) Py_RETURN_NONE; } - if (!PyDict_Check(obj)) { - PyErr_SetString(PyExc_TypeError, "Argument must be dict"); - return NULL; - } - root = _iterate_python(obj); if (root) { PyObject *ret; @@ -207,7 +240,11 @@ ucl_dump (PyObject *self, PyObject *args) buf = (char *) ucl_object_emit (root, emitter); ucl_object_unref (root); +#if PY_MAJOR_VERSION < 3 ret = PyString_FromString (buf); +#else + ret = PyUnicode_FromString (buf); +#endif free(buf); return ret; @@ -219,17 +256,35 @@ ucl_dump (PyObject *self, PyObject *args) static PyObject * ucl_validate (PyObject *self, PyObject *args) { - char *uclstr, *schema; + PyObject *dataobj, *schemaobj; + ucl_object_t *data, *schema; + bool r; + struct ucl_schema_error err; - if (PyArg_ParseTuple(args, "zz", &uclstr, &schema)) { - if (!uclstr || !schema) { - Py_RETURN_NONE; - } + if (!PyArg_ParseTuple (args, "OO", &schemaobj, &dataobj)) { + PyErr_SetString (PyExc_TypeError, "Unhandled object type"); + return NULL; + } - PyErr_SetString(PyExc_NotImplementedError, "schema validation is not yet supported"); + schema = _iterate_python(schemaobj); + if (!schema) + return NULL; + + data = _iterate_python(dataobj); + if (!data) + return NULL; + + // validation + r = ucl_object_validate (schema, data, &err); + ucl_object_unref (schema); + ucl_object_unref (data); + + if (!r) { + PyErr_SetString (SchemaError, err.msg); + return NULL; } - return NULL; + Py_RETURN_TRUE; } static PyMethodDef uclMethods[] = { @@ -247,6 +302,10 @@ init_macros(PyObject *mod) PyModule_AddIntMacro(mod, UCL_EMIT_CONFIG); PyModule_AddIntMacro(mod, UCL_EMIT_YAML); PyModule_AddIntMacro(mod, UCL_EMIT_MSGPACK); + + SchemaError = PyErr_NewException("ucl.SchemaError", NULL, NULL); + Py_INCREF(SchemaError); + PyModule_AddObject(mod, "SchemaError", SchemaError); } #if PY_MAJOR_VERSION >= 3 diff --git a/contrib/libucl/python/test.sh b/contrib/libucl/python/test.sh deleted file mode 100755 index 53af6a3..0000000 --- a/contrib/libucl/python/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -xe -python3.4 setup.py build_ext --inplace -./test_uclmodule.py -v -rm -rfv build -rm ucl.so diff --git a/contrib/libucl/python/test_uclmodule.py b/contrib/libucl/python/test_uclmodule.py deleted file mode 100755 index 0d77469..0000000 --- a/contrib/libucl/python/test_uclmodule.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python -import json -import unittest -import ucl -import sys - -if sys.version_info[:2] == (2, 7): - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - - -class TestUcl(unittest.TestCase): - def test_no_args(self): - with self.assertRaises(TypeError): - ucl.load() - - def test_multi_args(self): - with self.assertRaises(TypeError): - ucl.load(0,0) - - def test_none(self): - r = ucl.load(None) - self.assertEqual(r, None) - - def test_int(self): - r = ucl.load("a : 1") - self.assertEqual(ucl.load("a : 1"), { "a" : 1 } ) - - def test_braced_int(self): - self.assertEqual(ucl.load("{a : 1}"), { "a" : 1 } ) - - def test_nested_int(self): - self.assertEqual(ucl.load("a : { b : 1 }"), { "a" : { "b" : 1 } }) - - def test_str(self): - self.assertEqual(ucl.load("a : b"), {"a" : "b"}) - - def test_float(self): - self.assertEqual(ucl.load("a : 1.1"), {"a" : 1.1}) - - def test_boolean(self): - totest = ( - "a : True;" \ - "b : False" - ) - correct = {"a" : True, "b" : False} - self.assertEqual(ucl.load(totest), correct) - - def test_empty_ucl(self): - r = ucl.load("{}") - self.assertEqual(r, {}) - - def test_single_brace(self): - self.assertEqual(ucl.load("{"), {}) - - def test_single_back_brace(self): - ucl.load("}") - - def test_single_square_forward(self): - self.assertEqual(ucl.load("["), []) - - def test_invalid_ucl(self): - with self.assertRaisesRegex(ValueError, "unfinished key$"): - ucl.load('{ "var"') - - def test_comment_ignored(self): - self.assertEqual(ucl.load("{/*1*/}"), {}) - - def test_1_in(self): - with open("../tests/basic/1.in", "r") as in1: - self.assertEqual(ucl.load(in1.read()), {'key1': 'value'}) - - def test_every_type(self): - totest="""{ - "key1": value; - "key2": value2; - "key3": "value;" - "key4": 1.0, - "key5": -0xdeadbeef - "key6": 0xdeadbeef.1 - "key7": 0xreadbeef - "key8": -1e-10, - "key9": 1 - "key10": true - "key11": no - "key12": yes - }""" - correct = { - 'key1': 'value', - 'key2': 'value2', - 'key3': 'value;', - 'key4': 1.0, - 'key5': -3735928559, - 'key6': '0xdeadbeef.1', - 'key7': '0xreadbeef', - 'key8': -1e-10, - 'key9': 1, - 'key10': True, - 'key11': False, - 'key12': True, - } - self.assertEqual(ucl.load(totest), correct) - - def test_validation_useless(self): - with self.assertRaises(NotImplementedError): - ucl.validate("","") - - -class TestUclDump(unittest.TestCase): - def test_no_args(self): - with self.assertRaises(TypeError): - ucl.dump() - - def test_multi_args(self): - with self.assertRaises(TypeError): - ucl.dump(0, 0) - - def test_none(self): - self.assertEqual(ucl.dump(None), None) - - def test_int(self): - self.assertEqual(ucl.dump({ "a" : 1 }), "a = 1;\n") - - def test_nested_int(self): - self.assertEqual(ucl.dump({ "a" : { "b" : 1 } }), "a {\n b = 1;\n}\n") - - def test_int_array(self): - self.assertEqual(ucl.dump({ "a" : [1,2,3,4]}), "a [\n 1,\n 2,\n 3,\n 4,\n]\n") - - def test_str(self): - self.assertEqual(ucl.dump({"a" : "b"}), "a = \"b\";\n") - - def test_float(self): - self.assertEqual(ucl.dump({"a" : 1.1}), "a = 1.100000;\n") - - def test_boolean(self): - totest = {"a" : True, "b" : False} - correct = "a = true;\nb = false;\n" - self.assertEqual(ucl.dump(totest), correct) - - def test_empty_ucl(self): - self.assertEqual(ucl.dump({}), "") - - def test_json(self): - self.assertEqual(ucl.dump({ "a" : 1, "b": "bleh;" }, ucl.UCL_EMIT_JSON), '{\n "a": 1,\n "b": "bleh;"\n}') - - -if __name__ == '__main__': - unittest.main() diff --git a/contrib/libucl/python/tests/__init__.py b/contrib/libucl/python/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/libucl/python/tests/__init__.py diff --git a/contrib/libucl/python/tests/compat.py b/contrib/libucl/python/tests/compat.py new file mode 100644 index 0000000..5013826 --- /dev/null +++ b/contrib/libucl/python/tests/compat.py @@ -0,0 +1,8 @@ +try: + import unittest2 as unittest +except ImportError: + import unittest + +# Python 2.7 - 3.1 +if not hasattr(unittest.TestCase, 'assertRaisesRegex'): + unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp diff --git a/contrib/libucl/python/tests/test_dump.py b/contrib/libucl/python/tests/test_dump.py new file mode 100644 index 0000000..3692414 --- /dev/null +++ b/contrib/libucl/python/tests/test_dump.py @@ -0,0 +1,66 @@ +from .compat import unittest +import ucl +import sys + +class DumpTest(unittest.TestCase): + def test_no_args(self): + with self.assertRaises(TypeError): + ucl.dump() + + def test_none(self): + self.assertEqual(ucl.dump(None), None) + + def test_null(self): + data = { "a" : None } + valid = "a = null;\n" + self.assertEqual(ucl.dump(data), valid) + + def test_int(self): + data = { "a" : 1 } + valid = "a = 1;\n" + self.assertEqual(ucl.dump(data), valid) + + def test_nested_int(self): + data = { "a" : { "b" : 1 } } + valid = "a {\n b = 1;\n}\n" + self.assertEqual(ucl.dump(data), valid) + + def test_int_array(self): + data = { "a" : [1,2,3,4] } + valid = "a [\n 1,\n 2,\n 3,\n 4,\n]\n" + self.assertEqual(ucl.dump(data), valid) + + def test_str(self): + data = { "a" : "b" } + valid = "a = \"b\";\n" + self.assertEqual(ucl.dump(data), valid) + + @unittest.skipIf(sys.version_info[0] > 2, "Python3 uses unicode only") + def test_unicode(self): + data = { unicode("a") : unicode("b") } + valid = unicode("a = \"b\";\n") + self.assertEqual(ucl.dump(data), valid) + + def test_float(self): + data = { "a" : 1.1 } + valid = "a = 1.100000;\n" + self.assertEqual(ucl.dump(data), valid) + + def test_boolean(self): + data = { "a" : True, "b" : False } + valid = [ + "a = true;\nb = false;\n", + "b = false;\na = true;\n" + ] + self.assertIn(ucl.dump(data), valid) + + def test_empty_ucl(self): + self.assertEqual(ucl.dump({}), "") + + def test_json(self): + data = { "a" : 1, "b": "bleh;" } + valid = [ + '{\n "a": 1,\n "b": "bleh;"\n}', + '{\n "b": "bleh;",\n "a": 1\n}' + ] + self.assertIn(ucl.dump(data, ucl.UCL_EMIT_JSON), valid) diff --git a/contrib/libucl/python/tests/test_load.py b/contrib/libucl/python/tests/test_load.py new file mode 100644 index 0000000..786587a --- /dev/null +++ b/contrib/libucl/python/tests/test_load.py @@ -0,0 +1,107 @@ +from .compat import unittest +import ucl + +class LoadTest(unittest.TestCase): + def test_no_args(self): + with self.assertRaises(TypeError): + ucl.load() + + def test_multi_args(self): + with self.assertRaises(TypeError): + ucl.load(0,0) + + def test_none(self): + self.assertEqual(ucl.load(None), None) + + def test_null(self): + data = "a: null" + valid = { "a" : None } + self.assertEqual(ucl.load(data), valid) + + def test_int(self): + data = "a : 1" + valid = { "a" : 1 } + self.assertEqual(ucl.load(data), valid) + + def test_braced_int(self): + data = "{a : 1}" + valid = { "a" : 1 } + self.assertEqual(ucl.load(data), valid) + + def test_nested_int(self): + data = "a : { b : 1 }" + valid = { "a" : { "b" : 1 } } + self.assertEqual(ucl.load(data), valid) + + def test_str(self): + data = "a : b" + valid = { "a" : "b" } + self.assertEqual(ucl.load(data), valid) + + def test_float(self): + data = "a : 1.1" + valid = {"a" : 1.1} + self.assertEqual(ucl.load(data), valid) + + def test_boolean(self): + data = ( + "a : True;" \ + "b : False" + ) + valid = { "a" : True, "b" : False } + self.assertEqual(ucl.load(data), valid) + + def test_empty_ucl(self): + self.assertEqual(ucl.load("{}"), {}) + + def test_single_brace(self): + self.assertEqual(ucl.load("{"), {}) + + def test_single_back_brace(self): + self.assertEqual(ucl.load("}"), {}) + + def test_single_square_forward(self): + self.assertEqual(ucl.load("["), []) + + def test_invalid_ucl(self): + with self.assertRaisesRegex(ValueError, "unfinished key$"): + ucl.load('{ "var"') + + def test_comment_ignored(self): + self.assertEqual(ucl.load("{/*1*/}"), {}) + + def test_1_in(self): + valid = { 'key1': 'value' } + with open("../tests/basic/1.in", "r") as in1: + self.assertEqual(ucl.load(in1.read()), valid) + + def test_every_type(self): + data = ("""{ + "key1": value; + "key2": value2; + "key3": "value;" + "key4": 1.0, + "key5": -0xdeadbeef + "key6": 0xdeadbeef.1 + "key7": 0xreadbeef + "key8": -1e-10, + "key9": 1 + "key10": true + "key11": no + "key12": yes + }""") + valid = { + 'key1': 'value', + 'key2': 'value2', + 'key3': 'value;', + 'key4': 1.0, + 'key5': -3735928559, + 'key6': '0xdeadbeef.1', + 'key7': '0xreadbeef', + 'key8': -1e-10, + 'key9': 1, + 'key10': True, + 'key11': False, + 'key12': True, + } + self.assertEqual(ucl.load(data), valid) diff --git a/contrib/libucl/python/tests/test_validation.py b/contrib/libucl/python/tests/test_validation.py new file mode 100644 index 0000000..f7c853a --- /dev/null +++ b/contrib/libucl/python/tests/test_validation.py @@ -0,0 +1,50 @@ +from .compat import unittest +import ucl +import json +import os.path +import glob +import re + +TESTS_SCHEMA_FOLDER = '../tests/schema/*.json' + +comment_re = re.compile('\/\*((?!\*\/).)*?\*\/', re.DOTALL | re.MULTILINE) +def json_remove_comments(content): + return comment_re.sub('', content) + +class ValidationTest(unittest.TestCase): + def validate(self, jsonfile): + def perform_test(schema, data, valid, description): + msg = '%s (valid=%r)' % (description, valid) + if valid: + self.assertTrue(ucl.validate(schema, data), msg) + else: + with self.assertRaises(ucl.SchemaError): + ucl.validate(schema, data) + self.fail(msg) # fail() will be called only if SchemaError is not raised + + with open(jsonfile) as f: + try: + # data = json.load(f) + data = json.loads(json_remove_comments(f.read())) + except ValueError as e: + raise self.skipTest('Failed to load JSON: %s' % str(e)) + + for testgroup in data: + for test in testgroup['tests']: + perform_test(testgroup['schema'], test['data'], + test['valid'], test['description']) + + @classmethod + def setupValidationTests(cls): + """Creates each test dynamically from a folder""" + def test_gen(filename): + def run_test(self): + self.validate(filename) + return run_test + + for jsonfile in glob.glob(TESTS_SCHEMA_FOLDER): + testname = os.path.splitext(os.path.basename(jsonfile))[0] + setattr(cls, 'test_%s' % testname, test_gen(jsonfile)) + + +ValidationTest.setupValidationTests() |