Skip to content

Commit

Permalink
Merge pull request #106 from CitrineInformatics/feature/scope-control
Browse files Browse the repository at this point in the history
Added control for default scope in serialization
  • Loading branch information
kroenlein authored May 28, 2020
2 parents 0db9be5 + 74b0b3a commit 32be9a7
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 19 deletions.
15 changes: 12 additions & 3 deletions gemd/json/gemd_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class GEMDJson(object):
The serialization and deserialization strategy implemented by this class is described in
:ref:`Serialization In Depth`
scope: defines the scope to use for autogenerated UUIDs for objects without uids
"""

_clazzes = [
Expand All @@ -65,12 +67,18 @@ class GEMDJson(object):

_link_type = LinkByUID

def __init__(self):
def __init__(self, scope='auto'):
self._scope = scope
self._clazz_index = {}
# build index from the class's typ member to the class itself
for clazz in self._clazzes:
self._clazz_index[clazz.typ] = clazz

@property
def scope(self):
"""Return the default scope value."""
return self._scope

def dumps(self, obj, **kwargs):
"""
Serialize a gemd object, or container of them, into a json-formatting string.
Expand All @@ -90,7 +98,8 @@ def dumps(self, obj, **kwargs):
"""
# create a top level list of [flattened_objects, link-i-fied return value]
res = {"object": obj}
additional = flatten(res)

additional = flatten(res, self.scope)
res = substitute_links(res)
res["context"] = additional
return json_builtin.dumps(res, cls=GEMDEncoder, sort_keys=True, **kwargs)
Expand Down Expand Up @@ -214,7 +223,7 @@ def thin_dumps(self, obj, **kwargs):
A serialized string of `obj`, with link_by_uid in place of pointers to other objects.
"""
set_uuids(obj)
set_uuids(obj, self.scope)
res = substitute_links(obj)
return json_builtin.dumps(res, cls=GEMDEncoder, sort_keys=True, **kwargs)

Expand Down
27 changes: 27 additions & 0 deletions gemd/json/tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,33 @@ def test_deserialize():
assert(copy_meas.uids["auto"] == measurement.uids["auto"])


def test_scope_control():
"""Serializing a nested object should be identical to individually serializing each piece."""
input_material = MaterialSpec()
process = ProcessSpec()
IngredientSpec(material=input_material, process=process)
material = MaterialSpec(process=process)

# Verify the default scope is there
default_json = GEMDJson()
default_text = default_json.dumps(material)
assert "auto" in default_text
assert "custom" not in default_text

# Clear out ids
input_material.uids = {}
process.uids = {}
process.ingredients[0].uids = {}
input_material.uids = {}
material.uids = {}

# Verify the default scope is there
custom_json = GEMDJson(scope='custom')
custom_text = custom_json.dumps(material)
assert "auto" not in custom_text
assert "custom" in custom_text


def test_deserialize_extra_fields():
"""Extra JSON fields should be ignored in deserialization."""
json_data = '{"context": [],' \
Expand Down
13 changes: 7 additions & 6 deletions gemd/util/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
from toolz import concatv


def set_uuids(obj, name="auto"):
def set_uuids(obj, scope):
"""
Recursively assign a uuid to every BaseEntity that doesn't already contain a uuid.
This ensures that all of the pointers in the object can be replaced with LinkByUID objects
:param obj: to recursively assign uuids to
:param name: of the uuid to assign (default: "auto")
:param scope: of the uuid to assign
:return: None
"""
def func(base_obj):
if len(base_obj.uids) == 0:
base_obj.add_uid(name, str(uuid.uuid4()))
base_obj.add_uid(scope, str(uuid.uuid4()))
return
recursive_foreach(obj, func)
return
Expand Down Expand Up @@ -105,7 +105,7 @@ def substitute_objects(obj, index):
applies=lambda o: isinstance(o, LinkByUID))


def flatten(obj):
def flatten(obj, scope):
"""
Flatten a BaseEntity into a list of objects connected by LinkByUID objects.
Expand All @@ -119,11 +119,12 @@ def flatten(obj):
ingredients of the process in the result, even though process.ingredients is skipped.
This supports the flattening of entire material histories.
:param obj: defining the scope of the flatten
:param obj: the object where the graph traversal starts
:param scope: the scope of the autogenerated ids
:return: a list of BaseEntity with LinkByUIDs to any BaseEntity members
"""
# The ids should be set in the actual object so they are consistent
set_uuids(obj)
set_uuids(obj, scope)

# list of uids that we've seen, to avoid returning duplicates
known_uids = set()
Expand Down
19 changes: 10 additions & 9 deletions gemd/util/tests/test_flatten.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_flatten_bounds():
)
spec = ProcessSpec(name="spec", template=template)

flat = flatten(spec)
flat = flatten(spec, 'test-scope')
assert len(flat) == 2, "Expected 2 flattened objects"


Expand All @@ -31,15 +31,16 @@ def test_flatten_empty_history():
transform_run = ProcessRun(name="transformed", spec=transform)
ingredient_run = IngredientRun(material=input_run, process=transform_run, spec=ingredient)

assert len(flatten(procured)) == 1
assert len(flatten(input)) == 1
assert len(flatten(ingredient)) == 3
assert len(flatten(transform)) == 3
assert len(flatten(procured, 'test-scope')) == 1
assert 'test-scope' in procured.uids
assert len(flatten(input, 'test-scope')) == 1
assert len(flatten(ingredient, 'test-scope')) == 3
assert len(flatten(transform, 'test-scope')) == 3

assert len(flatten(procured_run)) == 3
assert len(flatten(input_run)) == 3
assert len(flatten(ingredient_run)) == 7
assert len(flatten(transform_run)) == 7
assert len(flatten(procured_run, 'test-scope')) == 3
assert len(flatten(input_run, 'test-scope')) == 3
assert len(flatten(ingredient_run, 'test-scope')) == 7
assert len(flatten(transform_run, 'test-scope')) == 7


def test_flatmap_unidirectional_ordering():
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def run(self):


setup(name='gemd',
version='0.7.4',
version='0.8.0',
url='http://github.com/CitrineInformatics/gemd-python',
description="Python binding for Citrine's GEMD data model",
author='Max Hutchinson',
Expand Down

0 comments on commit 32be9a7

Please sign in to comment.