Search code examples
pythonpython-importjsonnet

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?


Solution

  • 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.