summaryrefslogtreecommitdiffstats
path: root/contrib/libucl/python
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/libucl/python')
-rw-r--r--contrib/libucl/python/setup.py54
-rw-r--r--contrib/libucl/python/src/uclmodule.c97
-rwxr-xr-xcontrib/libucl/python/test.sh6
-rwxr-xr-xcontrib/libucl/python/test_uclmodule.py148
-rw-r--r--contrib/libucl/python/tests/__init__.py0
-rw-r--r--contrib/libucl/python/tests/compat.py8
-rw-r--r--contrib/libucl/python/tests/test_dump.py66
-rw-r--r--contrib/libucl/python/tests/test_load.py107
-rw-r--r--contrib/libucl/python/tests/test_validation.py50
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()
OpenPOWER on IntegriCloud