Skip to content

Commit

Permalink
Add context support
Browse files Browse the repository at this point in the history
Add support for documents to be read and have a context applied and also
to write them out with an applied context.
  • Loading branch information
JPEWdev committed Feb 21, 2024
1 parent 8c8ce52 commit 7ddfd4b
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/shacl2code/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .model import Model
from .model import Model, ContextMap
from .main import main
from .version import VERSION
1 change: 1 addition & 0 deletions src/shacl2code/lang/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def abort_helper(msg):
disclaimer=f"This file was automatically generated by {os.path.basename(sys.argv[0])}. DO NOT MANUALLY MODIFY IT",
enums=model.enums,
classes=model.classes,
context=model.context,
)

with self.__output.open() as f:
Expand Down
98 changes: 80 additions & 18 deletions src/shacl2code/lang/templates/python.j2
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Property(object):
def serializer(self, value):
return value

def deserialize(self, data, object_ids=None):
def deserialize(self, data, object_ids=None, context=None):
if isinstance(data, dict) and "@value" in data:
if "@type" in data and self.TYPE and data["@type"] != self.TYPE:
raise TypeError(
Expand Down Expand Up @@ -203,7 +203,7 @@ class ObjectProp(Property):

return value.serializer()

def deserialize(self, data, object_ids=None):
def deserialize(self, data, *, object_ids=None, context=None):
if data is None:
return data

Expand All @@ -213,10 +213,12 @@ class ObjectProp(Property):

return data

if isinstance(data, dict) and "@id" in data and not "@type" in data:
return data["@id"]
if isinstance(data, dict) and not "@type" in data:
for n in ("@id", compact("@id", context)):
if n in data:
return data[n]

return SHACLObject.deserialize(data, object_ids)
return SHACLObject.deserialize(data, object_ids=object_ids, context=context)

def link_prop(self, value, link_cache, missing, visited):
if value is None:
Expand Down Expand Up @@ -336,11 +338,11 @@ class ListProp(Property):
for idx, v in enumerate(value):
self.prop.walk(v, callback, path + [f"[{idx}]"])

def deserialize(self, data, object_ids=None):
def deserialize(self, data, **kwargs):
if isinstance(data, (list, tuple, set)):
data = [self.prop.deserialize(d, object_ids) for d in data]
data = [self.prop.deserialize(d, **kwargs) for d in data]
else:
data = [self.prop.deserialize(data, object_ids)]
data = [self.prop.deserialize(data, **kwargs)]

return ListProxy(self.prop, data=data)

Expand Down Expand Up @@ -368,6 +370,13 @@ class EnumProp(Property):
f"'{value}' is not a valid value for '{self.__class__.__name__}'"
)

def deserialize(self, data, *, context=None, **kwargs):
value = super().deserialize(data, context=context, **kwargs)
return expand(value, context)

def serializer(self, value, context=None):
return compact(value, context=context)


@functools.total_ordering
class SHACLObject(object):
Expand All @@ -386,15 +395,21 @@ class SHACLObject(object):
setattr(self, k, v)

def _add_property(
self, pyname, prop, json_name=None, min_count=None, max_count=None
self,
pyname,
prop,
json_name=None,
min_count=None,
max_count=None,
context=None,
):
if json_name is None:
json_name = pyname
if pyname in self._obj_properties:
raise KeyError(
f"'{pyname}' is already defined for '{self.__class__.__name__}'"
)
self._obj_properties[pyname] = (json_name, prop, min_count, max_count)
self._obj_properties[pyname] = (json_name, prop, min_count, max_count, context)
self._obj_data[json_name] = prop.init()

def __setattr__(self, name, value):
Expand Down Expand Up @@ -472,7 +487,7 @@ class SHACLObject(object):
for obj in seen:
yield obj

def serializer(self):
def serializer(self, context=None):
if self._id and self._obj_written:
return self._id

Expand All @@ -483,7 +498,7 @@ class SHACLObject(object):
}

for pyname, v in self._obj_properties.items():
json_name, prop, min_count, max_count = v
json_name, prop, min_count, max_count, prop_context = v
value = self._obj_data[json_name]
if prop.elide(value):
if min_count:
Expand All @@ -504,7 +519,9 @@ class SHACLObject(object):
f"Property '{pyname}' in {self.__class__.__name__} ({id(self)}) requires a maximum of {max_count} elements"
)

d[json_name] = prop.serializer(value)
d[compact(json_name, context)] = prop.serializer(
value, context=prop_context
)
return d

def to_jsonld(self, f, *args, **kwargs):
Expand All @@ -514,24 +531,33 @@ class SHACLObject(object):
return write_jsonld([self], f, *args, **kwargs)

@classmethod
def deserialize(cls, data, object_ids=None):
def deserialize(cls, data, *, object_ids=None, context=None):
if not "@type" in data:
return None

typ = data["@type"]

if not typ in cls.DESERIALIZERS:
raise Exception("Unknown type f{typ}")
if expand(typ, context) in data["@type"]:
typ = expand(typ, context)
else:
raise Exception("Unknown type f{typ}")

obj = cls.DESERIALIZERS[typ]()

if obj._id and obj._id in object_ids:
return object_ids[obj._id]

for pyname, v in obj._obj_properties.items():
json_name, prop, _, _ = v
if json_name in data:
obj._obj_data[json_name] = prop.deserialize(data[json_name], object_ids)
json_name, prop, _, _, prop_context = v
for n in (json_name, compact(json_name, context)):
if n in data:
obj._obj_data[json_name] = prop.deserialize(
data[n],
object_ids=object_ids,
context=prop_context or context,
)
break

if obj._id:
object_ids[obj._id] = obj
Expand Down Expand Up @@ -591,6 +617,25 @@ class SHACLObject(object):
return sort_key(self) < sort_key(other)


def compact(_id, context=None):
global CONTEXT
if context is None:
context = CONTEXT[""]
return context[_id]


def expand(_id, context=None):
global CONTEXT
if context is None:
context = CONTEXT[""]

for k, v in context.items():
if v == _id:
return k

return _id


def write_jsonld(objects, f, force_graph=False, **kwargs):
"""
Write a list of objects to a JSON LD file
Expand Down Expand Up @@ -663,6 +708,10 @@ def write_jsonld(objects, f, force_graph=False, **kwargs):
else:
data = objects[0].serializer()

#{% if context.url %}
data["@context"] = "{{ context.url }}"
#{% endif %}

sha1 = hashlib.sha1()
for chunk in json.JSONEncoder(**kwargs).iterencode(data):
chunk = chunk.encode("utf-8")
Expand Down Expand Up @@ -755,6 +804,16 @@ DATATYPE_CLASSES = {
}
%}
CONTEXT = {
{%- for context_name, context_map in context.map.items() %}
"{{ context_name }}": {
{%- for k, v in context_map.items() %}
"{{ k }}": "{{ v }}",
{%- endfor %}
},
{%- endfor %}
}
# ENUMERATIONS
{%- for enum in enums %}
{%- if enum.comment %}
Expand Down Expand Up @@ -819,6 +878,9 @@ class {{ class.clsname }}({% if class.parents -%}{{ class.parents | join(", ") }
{%- if not prop.min_count is none %}
min_count={{ prop.min_count }},
{%- endif %}
{%- if prop.path in context.map %}
context=CONTEXT["{{ prop.path }}"],
{%- endif %}
)
{%- endfor %}
self._set_init_props(**kwargs)
Expand Down
15 changes: 13 additions & 2 deletions src/shacl2code/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import urllib.request
from pathlib import Path

from . import Model
from . import Model, ContextMap
from .version import VERSION
from .lang import LANGUAGES

Expand All @@ -26,7 +26,13 @@ def handle_generate(args):
with Path(args.input).open("r") as f:
model_data = json.load(f)

m = Model(model_data)
if args.context:
with urllib.request.urlopen(args.context) as url:
context = ContextMap(json.load(url), args.context)
else:
context = ContextMap(None, {})

m = Model(model_data, context)

render = args.lang(args)
render.output(m)
Expand Down Expand Up @@ -64,6 +70,11 @@ def handle_version(args):
help="Input JSON-LD model (path, URL, or '-')",
required=True,
)
generate_parser.add_argument(
"--context",
"-x",
help="Require context for output (URL)",
)
generate_parser.set_defaults(func=handle_generate)

lang_subparser = generate_parser.add_subparsers(
Expand Down
Loading

0 comments on commit 7ddfd4b

Please sign in to comment.