Skip to content
/ pyjsg Public

PyJSG -- JSON Schema Grammar bindings for Python

License

Notifications You must be signed in to change notification settings

hsolbrig/pyjsg

Repository files navigation

PyJSG -- JSON Schema Grammar Bindings for Python

Translate JSON Schema Grammar into Python objects.

This tool generates Python 3 objects that represent the JSON objects defined in a JSG schema. It uses the Python Typing library to add type hints to Python IDE's and includes a library to validate the python objects against the library definitions.

Pyversions PyPi Build Publish

Examples

JSON GrammarPython Objects
doc { status:"ready" }
class _Anon1(jsg.JSGString):
    pattern = jsg.JSGPattern(r'ready')
class doc(jsg.JSGObject): def __init__(self, status: _Anon1 = None, **_kwargs: Dict[str, object]): self.status = status super().__init__(self._context, **_kwargs)
doc { street:@string no:@int }
class doc(jsg.JSGObject):
    def __init__(self,
                 street: str = None,
                 no: int = None,
                 **_kwargs: Dict[str, object]):
        self.street = jsg.String(street)
        self.no = jsg.Integer(no)
        super().__init__(self._context, **_kwargs)
doc { street:(NAME|"*"|TEMPLATE) }
@terminals NAME : [A-Za-z].*; TEMPLATE : '{' .* '}';
class _Anon1(jsg.JSGString):
    pattern = jsg.JSGPattern(r'\*')
class NAME(jsg.JSGString): pattern = jsg.JSGPattern(r'[A-Za-z].*')
class TEMPLATE(jsg.JSGString): pattern = jsg.JSGPattern(r'\{.*\}')
class doc(jsg.JSGObject): def __init__(self, street: Union[_Anon1, NAME, TEMPLATE] = None, **_kwargs: Dict[str, object]): self.street = street super().__init__(self._context, **_kwargs)
doc { street:nameOrTemplate }
nameOrTemplate = (NAME | TEMPLATE) ;
@terminals NAME : .*; TEMPLATE : '{' .* '}';
class NAME(jsg.JSGString):
    pattern = jsg.JSGPattern(r'.*')
class TEMPLATE(jsg.JSGString): pattern = jsg.JSGPattern(r'\{.*\}')
nameOrTemplate = Union[NAME, TEMPLATE]
class doc(jsg.JSGObject): def __init__(self, street: nameOrTemplate = None, **_kwargs: Dict[str, object]): self.street = street super().__init__(self._context, **_kwargs)
doc { street:[(NAME | "*" | TEMPLATE){2,}] }
@terminals
NAME : .*;
TEMPLATE : '{' .* '}';
class _Anon1(jsg.JSGString):
    pattern = jsg.JSGPattern(r'\*')
class NAME(jsg.JSGString): pattern = jsg.JSGPattern(r'.*')
class TEMPLATE(jsg.JSGString): pattern = jsg.JSGPattern(r'\{.*\}')
class doc(jsg.JSGObject): def __init__(self, street: List[Union[_Anon1, NAME, TEMPLATE]] = None, **_kwargs: Dict[str, object]): self.street = street super().__init__(self._context, **_kwargs)

Usage

  • Requires Python 3.x -- has been tested through Python 3.6.1. (This module depends on some of the internal features of the Python typing library, which is still under active development -- be careful upgrading to newer versions without first running the unit tests.)
> pip install pyjsg
> generate_parser -h
usage: generate_parser [-h] [-o OUTFILE] [-e] infile

positional arguments:
  infile                Input JSG specification

optional arguments:
  -h, --help            show this help message and exit
  -o OUTFILE, --outfile OUTFILE
                        Output python file (Default: {infile}.jsg)
  -e, --evaluate        Evaluate resulting python file as a test

Setup

> curl https://raw.githubusercontent.com/hsolbrig/shexTest/master/doc/ShExJ.jsg -o ShExJ.jsg
> generate_parser ShExJ.jsg
Output written to ShExJ.py

Python

from tests.py import ShExJ
    from pyjsg.jsglib.jsg import loads
    from io import StringIO

    # Load an exsting schema
    shexj = """{
      "@context": "http://www.w3.org/ns/shex.jsonld",
      "type": "Schema",
      "shapes": [
        {
          "id": "http://a.example/S1",
          "type": "Shape",
          "expression": {
            "type": "TripleConstraint",
            "predicate": "http://a.example/p1",
            "valueExpr": {
              "type": "NodeConstraint",
              "datatype": "http://a.example/dt1"
            }
          }
        }
      ]
    }
    """
    s: ShExJ.Schema = loads(shexj, ShExJ)
    print(f"type(Schema) = {s.type}")
    print(f"PREDICATE: {s.shapes[0].expression.predicate}")

    # Add a new element
    s.shapes[0].closed = Boolean("true")

    # Emit modified JSON
    print(s._as_json_dumps())

    # Validate the JSON
    print(f"Valid: {s._is_valid()")

    # Add an invalid element that isn't caught
    s.shapes[0].expression.valueExpr = "Just text"
    log = StringIO()
    if not s._is_valid(log):
        print(log.getvalue())

    # Attempt to add in invalid string
    try:
        s.shapes[0].closed = Boolean("0", True)
    except ValueError:
        print("String mismatch")

    # Attempt to add in invalid property
    try:
        s.shapes[0].closd = Boolean("true")
    except ValueError:
        print("No attribute named 'closd'")

Output

type(Shema): Schema
PREDICATE: http://a.example/p1
{
  "type": "Schema",
  "@context": "http://www.w3.org/ns/shex.jsonld",
  "shapes": [
     {
        "type": "Shape",
        "id": "http://a.example/S1",
        "closed": "true",
        "expression": {
           "type": "TripleConstraint",
           "predicate": "http://a.example/p1",
           "valueExpr": {
              "type": "NodeConstraint",
              "datatype": "http://a.example/dt1"
           }
        }
     }
  ]
}
Valid: True
String mismatch
No attribute named 'closd'