Skip to content

Commit ef10984

Browse files
author
bfolie
authored
Merge pull request #69 from CitrineInformatics/release/0.4.0
Release/0.4.0
2 parents 4febff2 + 9d2d1f6 commit ef10984

23 files changed

+659
-335
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ coverage.xml
4949
*.cover
5050
.hypothesis/
5151
.pytest_cache/
52+
flex_measurements.json
5253

5354
# Translations
5455
*.mo

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ install:
77
- pip install -U -r test_requirements.txt
88
- pip install --no-deps -e .
99
script:
10-
- pytest --flake8 --cov=taurus --cov-report term-missing --cov-report term:skip-covered --cov-config=tox.ini
10+
- pytest --cov=taurus --cov-report term-missing --cov-report term:skip-covered --cov-config=tox.ini
1111
--cov-fail-under=100 -s -r ./taurus
12+
- flake8 taurus
1213
- cd docs; make html; cd ..;
1314
- touch ./docs/_build/html/.nojekyll
1415
deploy:

taurus/client/json_encoder.py

+7-11
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from taurus.entity.value.uniform_integer import UniformInteger
3838
from taurus.entity.value.uniform_real import UniformReal
3939
from taurus.enumeration.base_enumeration import BaseEnumeration
40-
from taurus.util import flatten, substitute_links, deepcopy, set_uuids, substitute_objects
40+
from taurus.util import flatten, substitute_links, set_uuids, substitute_objects
4141

4242

4343
def dumps(obj, **kwargs):
@@ -60,7 +60,7 @@ def dumps(obj, **kwargs):
6060
# create a top level list of [flattened_objects, link-i-fied return value]
6161
res = [obj]
6262
additional = flatten(res)
63-
substitute_links(res)
63+
res = substitute_links(res)
6464
res.insert(0, additional)
6565
return json.dumps(res, cls=TaurusEncoder, sort_keys=True, **kwargs)
6666

@@ -88,8 +88,8 @@ def loads(json_str, **kwargs):
8888
index = {}
8989
raw = json.loads(json_str, object_hook=lambda x: _loado(x, index), **kwargs)
9090
# the return value is in the 2nd position.
91-
substitute_objects(raw, index)
92-
return raw[1]
91+
subbed = substitute_objects(raw, index)
92+
return subbed[1]
9393

9494

9595
def load(fp, **kwargs):
@@ -140,8 +140,8 @@ def thin_dumps(obj, **kwargs):
140140
141141
Parameters
142142
----------
143-
obj: BaseEntity
144-
Object to dump (must be a BaseEntity because it must have uids)
143+
obj:
144+
Object to dump
145145
**kwargs: keyword args, optional
146146
Optional keyword arguments to pass to `json.dumps()`.
147147
@@ -151,12 +151,8 @@ def thin_dumps(obj, **kwargs):
151151
A serialized string of `obj`, with link_by_uid in place of pointers to other objects.
152152
153153
"""
154-
if not isinstance(obj, BaseEntity):
155-
raise TypeError("Can only dump BaseEntities, but got {}".format(type(obj)))
156-
157154
set_uuids(obj)
158-
res = deepcopy(obj)
159-
substitute_links(res)
155+
res = substitute_links(obj)
160156
return json.dumps(res, cls=TaurusEncoder, sort_keys=True, **kwargs)
161157

162158

taurus/client/tests/test_json.py

+74-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""Test serialization and deserialization of taurus objects."""
22
import json
3+
from copy import deepcopy
4+
35
import pytest
46

5-
from taurus.client.json_encoder import dumps, loads, copy, thin_dumps
7+
from taurus.client.json_encoder import dumps, loads, copy, thin_dumps, _loado
8+
from taurus.entity.attribute.property import Property
9+
from taurus.entity.bounds.real_bounds import RealBounds
610
from taurus.entity.dict_serializable import DictSerializable
711
from taurus.entity.case_insensitive_dict import CaseInsensitiveDict
812
from taurus.entity.attribute.condition import Condition
@@ -11,10 +15,12 @@
1115
from taurus.entity.object import MeasurementRun, MaterialRun, ProcessRun, MeasurementSpec
1216
from taurus.entity.object.ingredient_run import IngredientRun
1317
from taurus.entity.object.ingredient_spec import IngredientSpec
18+
from taurus.entity.template.property_template import PropertyTemplate
1419
from taurus.entity.value.nominal_integer import NominalInteger
1520
from taurus.entity.value.nominal_real import NominalReal
1621
from taurus.entity.value.normal_real import NormalReal
1722
from taurus.enumeration.origin import Origin
23+
from taurus.util import substitute_objects, substitute_links
1824

1925

2026
def test_serialize():
@@ -66,8 +72,24 @@ def test_enumeration_serde():
6672
assert copy_condition.notes == Origin.get_value(condition.notes)
6773

6874

75+
def test_attribute_serde():
76+
"""An attribute with a link to an attribute template should be copy-able."""
77+
prop_tmpl = PropertyTemplate(name='prop_tmpl',
78+
bounds=RealBounds(0, 2, 'm')
79+
)
80+
prop = Property(name='prop',
81+
template=prop_tmpl,
82+
value=NominalReal(1, 'm')
83+
)
84+
meas_spec = MeasurementSpec("a spec")
85+
meas = MeasurementRun("a measurement", spec=meas_spec, properties=[prop])
86+
assert loads(dumps(prop)) == prop
87+
assert loads(dumps(meas)) == meas
88+
assert isinstance(prop.template, PropertyTemplate)
89+
90+
6991
def test_thin_dumps():
70-
"""Test that thin_dumps turns pointers into links and doesn't work on non-BaseEntity."""
92+
"""Test that thin_dumps turns pointers into links."""
7193
mat = MaterialRun("The actual material")
7294
meas_spec = MeasurementSpec("measurement", uids={'my_scope': '324324'})
7395
meas = MeasurementRun("The measurement", spec=meas_spec, material=mat)
@@ -78,8 +100,13 @@ def test_thin_dumps():
78100
assert isinstance(thin_copy.spec, LinkByUID)
79101
assert thin_copy.spec.id == meas_spec.uids['my_scope']
80102

103+
# Check that LinkByUID objects are correctly converted their JSON equivalent
104+
expected_json = '{"id": "my_id", "scope": "scope", "type": "link_by_uid"}'
105+
assert thin_dumps(LinkByUID('scope', 'my_id')) == expected_json
106+
107+
# Check that objects lacking .uid attributes will raise an exception when dumped
81108
with pytest.raises(TypeError):
82-
thin_dumps(LinkByUID('scope', 'id'))
109+
thin_dumps({{'key': 'value'}})
83110

84111

85112
def test_uid_deser():
@@ -126,6 +153,50 @@ def __init__(self, foo):
126153
loads(serialized)
127154

128155

156+
def test_pure_subsitutions():
157+
"""Make sure substitute methods don't mutate inputs."""
158+
json_str = '''
159+
[
160+
[
161+
{
162+
"uids": {
163+
"id": "9118c2d3-1c38-47fe-a650-c2b92fdb6777"
164+
},
165+
"type": "material_run",
166+
"name": "flour"
167+
}
168+
],
169+
{
170+
"type": "ingredient_run",
171+
"uids": {
172+
"id": "8858805f-ec02-49e4-ba3b-d784e2aea3f8"
173+
},
174+
"material": {
175+
"type": "link_by_uid",
176+
"scope": "ID",
177+
"id": "9118c2d3-1c38-47fe-a650-c2b92fdb6777"
178+
},
179+
"process": {
180+
"type": "link_by_uid",
181+
"scope": "ID",
182+
"id": "9148c2d3-2c38-47fe-b650-c2b92fdb6777"
183+
}
184+
}
185+
]
186+
'''
187+
index = {}
188+
original = json.loads(json_str, object_hook=lambda x: _loado(x, index))
189+
frozen = deepcopy(original)
190+
loaded = substitute_objects(original, index)
191+
assert original == frozen
192+
frozen_loaded = deepcopy(loaded)
193+
substitute_links(loaded)
194+
assert loaded == frozen_loaded
195+
for o in loaded:
196+
substitute_links(o)
197+
assert loaded == frozen_loaded
198+
199+
129200
def test_case_insensitive_rehydration():
130201
"""
131202

0 commit comments

Comments
 (0)