From 441e4cc5814ed0060d8b3d0e51989783ccffde43 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Fri, 8 Nov 2019 16:55:12 +0000 Subject: [PATCH] Use backed by go-jsonnet for python extension --- .gitignore | 5 +++ c-bindings/BUILD.bazel | 1 + python/__init__.py | 0 python/_jsonnet_test.py | 97 +++++++++++++++++++++++++++++++++++++++++ python/test.jsonnet | 5 +++ setup.py | 60 +++++++++++++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 python/__init__.py create mode 100644 python/_jsonnet_test.py create mode 100644 python/test.jsonnet create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index cf2d3a0..53608e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,13 @@ *~ *.prof *.so +*.a .*.swp +*.pyc coverage.out +build/ +dist/ +jsonnet.egg-info/ /bazel-bin /bazel-genfiles /bazel-go-jsonnet diff --git a/c-bindings/BUILD.bazel b/c-bindings/BUILD.bazel index f6569b9..2176f0d 100644 --- a/c-bindings/BUILD.bazel +++ b/c-bindings/BUILD.bazel @@ -12,6 +12,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") go_library( name = "go_default_library", srcs = [ + "handles.go", "c-bindings.go", "internal.h", "json.cpp", diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/_jsonnet_test.py b/python/_jsonnet_test.py new file mode 100644 index 0000000..838fe83 --- /dev/null +++ b/python/_jsonnet_test.py @@ -0,0 +1,97 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +import _jsonnet + + +# Returns content if worked, None if file not found, or throws an exception +def try_path(dir, rel): + if not rel: + raise RuntimeError('Got invalid filename (empty string).') + if rel[0] == '/': + full_path = rel + else: + full_path = dir + rel + if full_path[-1] == '/': + raise RuntimeError('Attempted to import a directory') + + if not os.path.isfile(full_path): + return full_path, None + with open(full_path) as f: + return full_path, f.read() + + +def import_callback(dir, rel): + full_path, content = try_path(dir, rel) + if content: + return full_path, content + raise RuntimeError('File not found') + + +# Test native extensions +def concat(a, b): + return a + b + + +def return_types(): + return { + 'a': [1, 2, 3, None, []], + 'b': 1.0, + 'c': True, + 'd': None, + 'e': { + 'x': 1, + 'y': 2, + 'z': ['foo'] + }, + } + +native_callbacks = { + 'concat': (('a', 'b'), concat), + 'return_types': ((), return_types), +} + + +class JsonnetTests(unittest.TestCase): + def setUp(self): + self.input_filename = os.path.join( + os.path.dirname(__file__), + "test.jsonnet", + ) + self.expected_str = "true\n" + with open(self.input_filename, "r") as infile: + self.input_snippet = infile.read() + + def test_evaluate_file(self): + json_str = _jsonnet.evaluate_file( + self.input_filename, + import_callback=import_callback, + native_callbacks=native_callbacks, + ) + self.assertEqual(json_str, self.expected_str) + + def test_evaluate_snippet(self): + json_str = _jsonnet.evaluate_snippet( + "snippet", + self.input_snippet, + import_callback=import_callback, + native_callbacks=native_callbacks, + ) + self.assertEqual(json_str, self.expected_str) + +if __name__ == '__main__': + unittest.main() diff --git a/python/test.jsonnet b/python/test.jsonnet new file mode 100644 index 0000000..095af71 --- /dev/null +++ b/python/test.jsonnet @@ -0,0 +1,5 @@ +std.assertEqual(({ x: 1, y: self.x } { x: 2 }).y, 2) && +std.assertEqual(std.native("concat")("foo", "bar"), "foobar") && +std.assertEqual(std.native("return_types")(), {a: [1, 2, 3, null, []], b: 1, c: true, d: null, e: {x: 1, y: 2, z: ["foo"]}}) && +true + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..be7421c --- /dev/null +++ b/setup.py @@ -0,0 +1,60 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from setuptools import setup +from setuptools import Extension +from setuptools.command.build_ext import build_ext as BuildExt +from subprocess import Popen, PIPE + +DIR = os.path.abspath(os.path.dirname(__file__)) +LIB_DIR = DIR + '/c-bindings' +MODULE_SOURCES = ['cpp-jsonnet/python/_jsonnet.c'] + +def get_version(): + # TODO think about it (how to get the version) + return '0.14.0' + +class BuildJsonnetExt(BuildExt): + def run(self): + p = Popen(['go', 'build', '-o', 'libgojsonnet.a', '-buildmode=c-archive'], cwd=LIB_DIR, stdout=PIPE) + p.wait() + + if p.returncode != 0: + raise Exception('Could not build libgojsonnet.so') + + BuildExt.run(self) + +jsonnet_ext = Extension( + '_jsonnet', + sources=MODULE_SOURCES, + extra_objects=[ + LIB_DIR + '/libgojsonnet.a', + ], + include_dirs = ['cpp-jsonnet/include'], + language='c++', +) + +setup(name='jsonnet', + url='https://jsonnet.org', + description='Python bindings for Jsonnet - The data templating language ', + author='David Cunningham', + author_email='dcunnin@google.com', + version=get_version(), + cmdclass={ + 'build_ext': BuildJsonnetExt, + }, + ext_modules=[jsonnet_ext], + test_suite="python._jsonnet_test", +)