Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly call jsonnet with imports from Python

I'm using jsonnet to build json objects that will be used by Python code, calling jsonnet from Python using the bindings. I want to set up my directory structure so that the jsonnet files are in a subdirectory or subdirectories relative to where the Python code is run, something like:

foo.py jsonnet/ jsonnet/bar.jsonnet jsonnet/baz.libsonnet

Running foo.py should then be able to use _jsonnet.evaluate_snippet() on strings read from files in jsonnet/ that import other files from jsonnet/. What's the best way to do this?

like image 796
ceridwen Avatar asked Apr 20 '18 20:04

ceridwen


2 Answers

The default importer uses paths relative to the file from which they are imported. In case of evaluate_snippet you need to pass the path manually. This way jsonnet knows where to look for imported files.

If your intention is to process the files you can use a custom importer. (Digression: jsonnet tries to avoid the need to preprocess the source files, so there is probably a better way or a missing feature in jsonnet.)

Below is the complete, working example on how to use custom importers in Python (adjusted to the directory structure provided):

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')


class JsonnetTests(unittest.TestCase):
    def setUp(self):
        self.input_filename = os.path.join(
            "jsonnet",
            "bar.jsonnet",
        )
        self.expected_str = '{\n   "num": 42,\n   "str": "The answer to life ..."\n}\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,
        )
        self.assertEqual(json_str, self.expected_str)

    def test_evaluate_snippet(self):
        json_str = _jsonnet.evaluate_snippet(
            "jsonnet/bar.jsonnet",
            self.input_snippet,
            import_callback=import_callback,
        )
        self.assertEqual(json_str, self.expected_str)

if __name__ == '__main__':
    unittest.main()

Note: it's a modified version of an example from jsonnet repo.

like image 98
sbarzowski Avatar answered Nov 15 '22 06:11

sbarzowski


I don't fully get why you would use evaluate_snippet() (maybe mask the actual filenames via loading them from python into strings + evaluate_snippet("blah", str) ? ), instead of evaluate_file() - in any case that structure should just work ok.

Example:

jsonnet_test.py:

import json:
import _jsonnet

jsonnet_file = "jsonnet/bar.jsonnet"
data = json.loads(_jsonnet.evaluate_file(jsonnet_file))
print("{str} => {num}".format(**data))

jsonnet/bar.jsonnet:

local baz = import "baz.libsonnet";
{
  str: "The answer to life ...",
  num: baz.mult(6, 7),
}

jsonnet/baz.libsonnet:

{
  mult(a, b):: (
    a * b
  ),
}

Output:

$ python jsonnet_test.py
The answer to life ... => 42
like image 39
jjo Avatar answered Nov 15 '22 06:11

jjo