bigraph-schema 0.0.71__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bigraph-schema might be problematic. Click here for more details.
- bigraph_schema/__init__.py +8 -0
- bigraph_schema/edge.py +129 -0
- bigraph_schema/parse.py +165 -0
- bigraph_schema/protocols.py +35 -0
- bigraph_schema/registry.py +287 -0
- bigraph_schema/tests.py +2631 -0
- bigraph_schema/type_functions.py +3379 -0
- bigraph_schema/type_system.py +1798 -0
- bigraph_schema/type_system_adjunct.py +502 -0
- bigraph_schema/units.py +133 -0
- bigraph_schema/utilities.py +243 -0
- bigraph_schema-0.0.71.dist-info/METADATA +52 -0
- bigraph_schema-0.0.71.dist-info/RECORD +17 -0
- bigraph_schema-0.0.71.dist-info/WHEEL +5 -0
- bigraph_schema-0.0.71.dist-info/licenses/AUTHORS.md +6 -0
- bigraph_schema-0.0.71.dist-info/licenses/LICENSE +201 -0
- bigraph_schema-0.0.71.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1798 @@
|
|
|
1
|
+
"""
|
|
2
|
+
===========
|
|
3
|
+
Type System
|
|
4
|
+
===========
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import functools
|
|
9
|
+
import inspect
|
|
10
|
+
import random
|
|
11
|
+
import traceback
|
|
12
|
+
from pprint import pformat as pf
|
|
13
|
+
|
|
14
|
+
from bigraph_schema import (
|
|
15
|
+
deep_merge, is_schema_key, non_schema_keys, Registry,
|
|
16
|
+
type_parameter_key)
|
|
17
|
+
from bigraph_schema.parse import parse_expression
|
|
18
|
+
from bigraph_schema.utilities import union_keys, state_instance
|
|
19
|
+
from bigraph_schema.registry import (
|
|
20
|
+
remove_omitted, set_star_path, transform_path)
|
|
21
|
+
|
|
22
|
+
from bigraph_schema.type_functions import (
|
|
23
|
+
base_types, is_empty, is_method_key, register_base_reactions,
|
|
24
|
+
registry_types, resolve_path, set_apply, SYMBOL_TYPES, TYPE_FUNCTION_KEYS,
|
|
25
|
+
type_schema_keys, unit_types)
|
|
26
|
+
from bigraph_schema.type_system_adjunct import TypeSystemAdjunct
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TypeSystem(Registry):
|
|
30
|
+
"""Handles type schemas and their operation"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__()
|
|
34
|
+
|
|
35
|
+
self.inherits = {}
|
|
36
|
+
|
|
37
|
+
self.default_registry = Registry(function_keys=[
|
|
38
|
+
'schema',
|
|
39
|
+
'core'])
|
|
40
|
+
|
|
41
|
+
self.check_registry = Registry(function_keys=[
|
|
42
|
+
'state',
|
|
43
|
+
'schema',
|
|
44
|
+
'core'])
|
|
45
|
+
|
|
46
|
+
self.apply_registry = Registry(function_keys=[
|
|
47
|
+
'current',
|
|
48
|
+
'update',
|
|
49
|
+
'schema',
|
|
50
|
+
'core'])
|
|
51
|
+
|
|
52
|
+
self.serialize_registry = Registry(function_keys=[
|
|
53
|
+
'value',
|
|
54
|
+
'schema',
|
|
55
|
+
'core'])
|
|
56
|
+
|
|
57
|
+
self.deserialize_registry = Registry(function_keys=[
|
|
58
|
+
'encoded',
|
|
59
|
+
'schema',
|
|
60
|
+
'core'])
|
|
61
|
+
|
|
62
|
+
self.fold_registry = Registry(function_keys=[
|
|
63
|
+
'method',
|
|
64
|
+
'state',
|
|
65
|
+
'schema',
|
|
66
|
+
'core'])
|
|
67
|
+
|
|
68
|
+
self.react_registry = Registry()
|
|
69
|
+
self.method_registry = Registry()
|
|
70
|
+
|
|
71
|
+
# register all the base methods and types
|
|
72
|
+
self.apply_registry.register(
|
|
73
|
+
'set',
|
|
74
|
+
set_apply)
|
|
75
|
+
|
|
76
|
+
self._register_types(registry_types)
|
|
77
|
+
self._register_types(base_types)
|
|
78
|
+
self._register_types(unit_types)
|
|
79
|
+
|
|
80
|
+
# # TODO -- add a proper registration into registry
|
|
81
|
+
register_base_reactions(self)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _register_types(self, type_library):
|
|
85
|
+
"""
|
|
86
|
+
private method to implement initialization
|
|
87
|
+
|
|
88
|
+
basically the same as update_types, except it never updates
|
|
89
|
+
"""
|
|
90
|
+
for type_key, type_data in type_library.items():
|
|
91
|
+
if not self.exists(type_key):
|
|
92
|
+
self.register(
|
|
93
|
+
type_key,
|
|
94
|
+
type_data)
|
|
95
|
+
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# is it useful just because it's simpler?
|
|
100
|
+
def update_types(self, type_updates):
|
|
101
|
+
"""
|
|
102
|
+
initialize or update multiple types
|
|
103
|
+
"""
|
|
104
|
+
for type_key, type_data in type_updates.items():
|
|
105
|
+
is_update = self.exists(type_key)
|
|
106
|
+
|
|
107
|
+
self.register(
|
|
108
|
+
type_key,
|
|
109
|
+
type_data,
|
|
110
|
+
update=is_update)
|
|
111
|
+
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _lookup_registry(self, underscore_key):
|
|
116
|
+
"""
|
|
117
|
+
private helper method
|
|
118
|
+
|
|
119
|
+
access the registry for the given key
|
|
120
|
+
|
|
121
|
+
>>> t = TypeSystem()
|
|
122
|
+
>>> t.foo_registry = "OK"
|
|
123
|
+
>>> t._lookup_registry('_foo')
|
|
124
|
+
'OK'
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
if underscore_key == '_type':
|
|
129
|
+
return self
|
|
130
|
+
root = underscore_key.strip('_')
|
|
131
|
+
registry_key = f'{root}_registry'
|
|
132
|
+
if hasattr(self, registry_key):
|
|
133
|
+
return getattr(self, registry_key)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _find_registry(self, underscore_key):
|
|
137
|
+
"""
|
|
138
|
+
access the registry for the given key
|
|
139
|
+
and create if it doesn't exist
|
|
140
|
+
|
|
141
|
+
>>> t = TypeSystem()
|
|
142
|
+
>>> hasattr(t, 'foo_registry')
|
|
143
|
+
False
|
|
144
|
+
>>> t._find_registry('_foo').__class__ == Registry
|
|
145
|
+
True
|
|
146
|
+
>>> hasattr(t, 'foo_registry')
|
|
147
|
+
True
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
registry = self._lookup_registry(underscore_key)
|
|
151
|
+
if registry is None:
|
|
152
|
+
registry = Registry()
|
|
153
|
+
# setattr is needed here because the key is dynamically
|
|
154
|
+
# constructed
|
|
155
|
+
setattr(
|
|
156
|
+
self,
|
|
157
|
+
f'{underscore_key[1:]}_registry',
|
|
158
|
+
registry)
|
|
159
|
+
|
|
160
|
+
return registry
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# TODO: explain this method
|
|
164
|
+
def register(self, key, schema, alternate_keys=tuple(), strict=True, update=False):
|
|
165
|
+
"""
|
|
166
|
+
register the schema under the given key in the registry
|
|
167
|
+
|
|
168
|
+
if schema is a string, we expand and duplicate it under a new name
|
|
169
|
+
>>> t = TypeSystem()
|
|
170
|
+
>>> t.register('regetni', 'integer')
|
|
171
|
+
>>> t.find('regetni') == t.find('integer')
|
|
172
|
+
True
|
|
173
|
+
>>> id(t.find('regetni')) != id(t.find('integer'))
|
|
174
|
+
True
|
|
175
|
+
|
|
176
|
+
if '_type' is not provided, it will be filled in
|
|
177
|
+
>>> t = TypeSystem()
|
|
178
|
+
>>> i = copy.deepcopy(t.find('integer'))
|
|
179
|
+
>>> del(i['_type'])
|
|
180
|
+
>>> t.register('regetni', i)
|
|
181
|
+
>>> t.find('regetni')['_type'] == 'regetni'
|
|
182
|
+
True
|
|
183
|
+
|
|
184
|
+
inherated schemas are merged
|
|
185
|
+
>>> t = TypeSystem()
|
|
186
|
+
>>> t.register('foo', {'bar':{'baz':42}})
|
|
187
|
+
>>> t.register('bar', {'haz':{'quux':12}})
|
|
188
|
+
>>> t.register('quux', {'_inherit':['foo', 'bar']})
|
|
189
|
+
>>> t.find('quux')['bar']['baz'] == 42
|
|
190
|
+
True
|
|
191
|
+
>>> t.find('quux')['haz']['quux'] == 12
|
|
192
|
+
True
|
|
193
|
+
|
|
194
|
+
type parameters define subtypes specific to the schema, each subtype
|
|
195
|
+
is expanded
|
|
196
|
+
>>> t = TypeSystem()
|
|
197
|
+
>>> s = {'_0':'any', '_1':'any'}
|
|
198
|
+
>>> s['_type_parameters'] = ['0','1']
|
|
199
|
+
>>> s['_type'] = 'tuple'
|
|
200
|
+
>>> t.register('foo', s)
|
|
201
|
+
>>> t.find('foo')['_0']['_serialize'] == 'serialize_any'
|
|
202
|
+
True
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
if isinstance(schema, str):
|
|
206
|
+
schema = self.find(schema)
|
|
207
|
+
schema = copy.deepcopy(schema)
|
|
208
|
+
if self.exists(key) and update:
|
|
209
|
+
found = self.find(key)
|
|
210
|
+
schema = deep_merge(
|
|
211
|
+
found,
|
|
212
|
+
schema)
|
|
213
|
+
strict = False
|
|
214
|
+
|
|
215
|
+
if not(isinstance(schema, dict)):
|
|
216
|
+
raise Exception(
|
|
217
|
+
f'all type definitions must be dicts '
|
|
218
|
+
f'with the following keys: {type_schema_keys}\nnot: {schema}')
|
|
219
|
+
else:
|
|
220
|
+
if '_type' not in schema:
|
|
221
|
+
schema['_type'] = key
|
|
222
|
+
|
|
223
|
+
inherits = schema.get('_inherit', []) # list of immediate inherits
|
|
224
|
+
if isinstance(inherits, str):
|
|
225
|
+
inherits = [inherits]
|
|
226
|
+
schema['_inherit'] = inherits
|
|
227
|
+
|
|
228
|
+
self.inherits[key] = []
|
|
229
|
+
for inherit in inherits:
|
|
230
|
+
inherit_type = self.access(inherit)
|
|
231
|
+
new_schema = copy.deepcopy(inherit_type)
|
|
232
|
+
|
|
233
|
+
schema = self.merge_schemas(
|
|
234
|
+
new_schema,
|
|
235
|
+
schema)
|
|
236
|
+
|
|
237
|
+
self.inherits[key].append(
|
|
238
|
+
inherit_type)
|
|
239
|
+
|
|
240
|
+
parameters = schema.get('_type_parameters', [])
|
|
241
|
+
for subkey, subschema in schema.items():
|
|
242
|
+
if subkey == '_default' \
|
|
243
|
+
or subkey in TYPE_FUNCTION_KEYS \
|
|
244
|
+
or is_method_key(subkey, parameters):
|
|
245
|
+
if not(callable(subschema)):
|
|
246
|
+
schema[subkey] = subschema
|
|
247
|
+
else:
|
|
248
|
+
registry = self._find_registry(subkey)
|
|
249
|
+
function_name, module_key = registry.register_function(subschema)
|
|
250
|
+
|
|
251
|
+
schema[subkey] = function_name
|
|
252
|
+
|
|
253
|
+
elif subkey not in type_schema_keys:
|
|
254
|
+
if schema['_type'] in SYMBOL_TYPES:
|
|
255
|
+
schema[subkey] = subschema
|
|
256
|
+
else:
|
|
257
|
+
lookup = self.find(subschema)
|
|
258
|
+
if lookup is None:
|
|
259
|
+
raise Exception(
|
|
260
|
+
f'trying to register a new type ({key}), '
|
|
261
|
+
f'but it depends on a type ({subkey}) which is not in the registry')
|
|
262
|
+
else:
|
|
263
|
+
schema[subkey] = lookup
|
|
264
|
+
|
|
265
|
+
super().register(
|
|
266
|
+
key,
|
|
267
|
+
schema,
|
|
268
|
+
alternate_keys,
|
|
269
|
+
strict=strict)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def register_reaction(self, reaction_key, reaction):
|
|
273
|
+
self.react_registry.register(
|
|
274
|
+
reaction_key,
|
|
275
|
+
reaction)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def merge_schemas(self, current, update):
|
|
279
|
+
"""
|
|
280
|
+
deep merge of two dicts (recursive merge of sub-dicts)
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
result = current.copy()
|
|
284
|
+
|
|
285
|
+
for key, value in update.items():
|
|
286
|
+
if key in result \
|
|
287
|
+
and isinstance(result[key], dict) \
|
|
288
|
+
and isinstance(value, dict):
|
|
289
|
+
result[key] = self.merge_schemas(result[key], value)
|
|
290
|
+
else:
|
|
291
|
+
result[key] = value
|
|
292
|
+
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _sort(self, schema, state):
|
|
297
|
+
"""
|
|
298
|
+
separates state and schema if they are mixed
|
|
299
|
+
|
|
300
|
+
helper used by type_functions
|
|
301
|
+
"""
|
|
302
|
+
schema = self.access(schema)
|
|
303
|
+
|
|
304
|
+
sort_function = self.choose_method(
|
|
305
|
+
schema,
|
|
306
|
+
state,
|
|
307
|
+
'sort')
|
|
308
|
+
|
|
309
|
+
return sort_function(
|
|
310
|
+
self,
|
|
311
|
+
schema,
|
|
312
|
+
state)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def exists(self, type_key):
|
|
316
|
+
return type_key in self.registry
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def find(self, schema, strict=False):
|
|
320
|
+
"""
|
|
321
|
+
expand the schema to its full type information from the type registry
|
|
322
|
+
|
|
323
|
+
strings are looked up in the registry if present as a key
|
|
324
|
+
>>> core = TypeSystem()
|
|
325
|
+
>>> for k,v in core.registry.items():
|
|
326
|
+
... assert isinstance(k, str)
|
|
327
|
+
... assert v == core.find(k)
|
|
328
|
+
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
found = None
|
|
332
|
+
|
|
333
|
+
if schema is None:
|
|
334
|
+
return self.access('any', strict=strict)
|
|
335
|
+
|
|
336
|
+
elif isinstance(schema, int):
|
|
337
|
+
return schema
|
|
338
|
+
|
|
339
|
+
elif isinstance(schema, tuple):
|
|
340
|
+
tuple_schema = {
|
|
341
|
+
'_type': 'tuple',
|
|
342
|
+
'_type_parameters': []}
|
|
343
|
+
|
|
344
|
+
for index, element in enumerate(schema):
|
|
345
|
+
tuple_schema['_type_parameters'].append(str(index))
|
|
346
|
+
tuple_schema[f'_{index}'] = element
|
|
347
|
+
|
|
348
|
+
return self.access(
|
|
349
|
+
tuple_schema,
|
|
350
|
+
strict=strict)
|
|
351
|
+
|
|
352
|
+
elif isinstance(schema, str):
|
|
353
|
+
found = self.registry.get(schema)
|
|
354
|
+
|
|
355
|
+
if found is None and schema not in ('', '{}'):
|
|
356
|
+
try:
|
|
357
|
+
parse = parse_expression(schema)
|
|
358
|
+
if parse != schema:
|
|
359
|
+
found = self.access(
|
|
360
|
+
parse,
|
|
361
|
+
strict=strict)
|
|
362
|
+
elif not strict:
|
|
363
|
+
found = {'_type': schema}
|
|
364
|
+
|
|
365
|
+
except Exception:
|
|
366
|
+
print(f'type did not parse: {schema}')
|
|
367
|
+
traceback.print_exc()
|
|
368
|
+
|
|
369
|
+
elif isinstance(schema, dict):
|
|
370
|
+
if '_description' in schema:
|
|
371
|
+
return schema
|
|
372
|
+
|
|
373
|
+
elif '_union' in schema:
|
|
374
|
+
union_schema = {
|
|
375
|
+
'_type': 'union',
|
|
376
|
+
'_type_parameters': []}
|
|
377
|
+
|
|
378
|
+
for index, element in enumerate(schema['_union']):
|
|
379
|
+
union_schema['_type_parameters'].append(str(index))
|
|
380
|
+
union_schema[f'_{index}'] = element
|
|
381
|
+
|
|
382
|
+
return self.access(
|
|
383
|
+
union_schema,
|
|
384
|
+
strict=strict)
|
|
385
|
+
|
|
386
|
+
elif '_type' in schema:
|
|
387
|
+
registry_type = self.retrieve(
|
|
388
|
+
schema['_type'])
|
|
389
|
+
|
|
390
|
+
found = self.merge_schemas(
|
|
391
|
+
registry_type,
|
|
392
|
+
schema)
|
|
393
|
+
|
|
394
|
+
else:
|
|
395
|
+
found = {
|
|
396
|
+
key: self.access(
|
|
397
|
+
branch,
|
|
398
|
+
strict=strict) if key != '_default' else branch
|
|
399
|
+
for key, branch in schema.items()}
|
|
400
|
+
|
|
401
|
+
elif isinstance(schema, list):
|
|
402
|
+
if isinstance(schema[0], int):
|
|
403
|
+
return schema
|
|
404
|
+
|
|
405
|
+
bindings = []
|
|
406
|
+
if len(schema) > 1:
|
|
407
|
+
schema, bindings = schema
|
|
408
|
+
else:
|
|
409
|
+
schema = schema[0]
|
|
410
|
+
found = self.access(
|
|
411
|
+
schema,
|
|
412
|
+
strict=strict)
|
|
413
|
+
|
|
414
|
+
if len(bindings) > 0:
|
|
415
|
+
found = found.copy()
|
|
416
|
+
|
|
417
|
+
if '_type_parameters' not in found:
|
|
418
|
+
found['_type_parameters'] = []
|
|
419
|
+
for index, binding in enumerate(bindings):
|
|
420
|
+
found['_type_parameters'].append(str(index))
|
|
421
|
+
found[f'_{index}'] = binding
|
|
422
|
+
else:
|
|
423
|
+
for parameter, binding in zip(found['_type_parameters'], bindings):
|
|
424
|
+
binding_type = self.access(
|
|
425
|
+
binding,
|
|
426
|
+
strict=strict) or binding
|
|
427
|
+
|
|
428
|
+
found[f'_{parameter}'] = binding_type
|
|
429
|
+
|
|
430
|
+
return found
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def access(self, schema, strict=False):
|
|
434
|
+
if isinstance(schema, str):
|
|
435
|
+
return self.access_str(schema, strict=strict)
|
|
436
|
+
else:
|
|
437
|
+
return self.find(schema, strict=strict)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@functools.lru_cache(maxsize=None)
|
|
441
|
+
def access_str(self, schema, strict=False):
|
|
442
|
+
return self.find(
|
|
443
|
+
schema,
|
|
444
|
+
strict)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def retrieve(self, schema):
|
|
448
|
+
"""
|
|
449
|
+
like access(schema) but raises an exception if nothing is found
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
found = self.find(
|
|
453
|
+
schema,
|
|
454
|
+
strict=True)
|
|
455
|
+
|
|
456
|
+
if found is None:
|
|
457
|
+
raise Exception(f'schema not found for type: {schema}')
|
|
458
|
+
return found
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _find_parameter(self, schema, parameter):
|
|
462
|
+
"""
|
|
463
|
+
resolves the type parameter
|
|
464
|
+
|
|
465
|
+
helper for type_functions
|
|
466
|
+
"""
|
|
467
|
+
schema_key = f'_{parameter}'
|
|
468
|
+
if schema_key not in schema:
|
|
469
|
+
schema = self.access(schema)
|
|
470
|
+
if schema_key not in schema:
|
|
471
|
+
return 'any'
|
|
472
|
+
# raise Exception(f'parameter {parameter} not found in schema:\n {schema}')
|
|
473
|
+
|
|
474
|
+
parameter_type = self.access(
|
|
475
|
+
schema[schema_key])
|
|
476
|
+
|
|
477
|
+
return parameter_type
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _parameters_for(self, initial_schema):
|
|
481
|
+
"""
|
|
482
|
+
find any type parameters for this schema if they are present
|
|
483
|
+
|
|
484
|
+
helper for type_functions
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
if '_type_parameters' in initial_schema:
|
|
488
|
+
schema = initial_schema
|
|
489
|
+
else:
|
|
490
|
+
schema = self.access(initial_schema)
|
|
491
|
+
|
|
492
|
+
if '_type_parameters' not in schema:
|
|
493
|
+
return []
|
|
494
|
+
else:
|
|
495
|
+
result = []
|
|
496
|
+
for parameter in schema['_type_parameters']:
|
|
497
|
+
parameter_key = f'_{parameter}'
|
|
498
|
+
if parameter_key not in schema:
|
|
499
|
+
raise Exception(
|
|
500
|
+
f'looking for type parameter {parameter_key} but it is not in the schema:\n {pf(schema)}')
|
|
501
|
+
else:
|
|
502
|
+
parameter_type = schema[parameter_key]
|
|
503
|
+
result.append(
|
|
504
|
+
parameter_type)
|
|
505
|
+
|
|
506
|
+
return result
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def default(self, schema):
|
|
510
|
+
"""
|
|
511
|
+
produce the default value for the provided schema
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
default = None
|
|
515
|
+
found = self.retrieve(schema)
|
|
516
|
+
|
|
517
|
+
if '_default' in found:
|
|
518
|
+
default_value = found['_default']
|
|
519
|
+
if isinstance(default_value, str):
|
|
520
|
+
default_method = self.default_registry.access(default_value)
|
|
521
|
+
if default_method and callable(default_method):
|
|
522
|
+
default = default_method(found, self)
|
|
523
|
+
else:
|
|
524
|
+
default = self.deserialize(
|
|
525
|
+
found,
|
|
526
|
+
default_value)
|
|
527
|
+
|
|
528
|
+
elif not '_deserialize' in found:
|
|
529
|
+
raise Exception(
|
|
530
|
+
f'asking for default but no deserialize in {found}')
|
|
531
|
+
|
|
532
|
+
else:
|
|
533
|
+
default = self.deserialize(found, found['_default'])
|
|
534
|
+
|
|
535
|
+
else:
|
|
536
|
+
default = {}
|
|
537
|
+
for key, subschema in found.items():
|
|
538
|
+
if not is_schema_key(key):
|
|
539
|
+
default[key] = self.default(subschema)
|
|
540
|
+
|
|
541
|
+
return default
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def dataclass(type_system, schema, path=None):
|
|
545
|
+
path = path or []
|
|
546
|
+
if not isinstance(path, list):
|
|
547
|
+
path = [path]
|
|
548
|
+
|
|
549
|
+
dataclass_function = type_system.choose_method(
|
|
550
|
+
schema,
|
|
551
|
+
{},
|
|
552
|
+
'dataclass')
|
|
553
|
+
|
|
554
|
+
return dataclass_function(
|
|
555
|
+
schema,
|
|
556
|
+
path,
|
|
557
|
+
type_system)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def default_instance(core, schema, path=None):
|
|
561
|
+
dataclass = core.dataclass(schema, path)
|
|
562
|
+
default = core.default(schema)
|
|
563
|
+
|
|
564
|
+
return state_instance(dataclass, default)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def choose_method(self, schema, state, method_name):
|
|
568
|
+
"""
|
|
569
|
+
find in the provided state, or schema if not there,
|
|
570
|
+
a method for the given method_name
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
method_key = f'_{method_name}'
|
|
574
|
+
found = None
|
|
575
|
+
|
|
576
|
+
if isinstance(state, dict) and method_key in state:
|
|
577
|
+
found = state[method_key]
|
|
578
|
+
|
|
579
|
+
elif isinstance(state, dict) and '_type' in state:
|
|
580
|
+
method_type = self.find(
|
|
581
|
+
state['_type'])
|
|
582
|
+
|
|
583
|
+
if method_type:
|
|
584
|
+
found = method_type.get(method_key)
|
|
585
|
+
|
|
586
|
+
if not found and isinstance(schema, dict) and method_key in schema:
|
|
587
|
+
found = schema[method_key]
|
|
588
|
+
|
|
589
|
+
if found is None:
|
|
590
|
+
any_type = self.access('any')
|
|
591
|
+
found = any_type[method_key]
|
|
592
|
+
|
|
593
|
+
registry = self._lookup_registry(method_key)
|
|
594
|
+
method_function = registry.access(
|
|
595
|
+
found)
|
|
596
|
+
|
|
597
|
+
if method_function is None:
|
|
598
|
+
raise Exception(f'no method "{method_name}" found for state {state} and schema {schema}')
|
|
599
|
+
|
|
600
|
+
return method_function
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def slice(self, schema, state, path):
|
|
604
|
+
"""
|
|
605
|
+
find the subschema and substate at a node in the place graph
|
|
606
|
+
given by the provided path
|
|
607
|
+
"""
|
|
608
|
+
|
|
609
|
+
if not isinstance(path, (list, tuple)):
|
|
610
|
+
path = [path]
|
|
611
|
+
|
|
612
|
+
schema = self.access(schema)
|
|
613
|
+
if '..' in path:
|
|
614
|
+
path = resolve_path(path)
|
|
615
|
+
|
|
616
|
+
slice_function = self.choose_method(
|
|
617
|
+
schema,
|
|
618
|
+
state,
|
|
619
|
+
'slice')
|
|
620
|
+
|
|
621
|
+
return slice_function(
|
|
622
|
+
schema,
|
|
623
|
+
state,
|
|
624
|
+
path,
|
|
625
|
+
self)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def match_node(self, schema, state, pattern):
|
|
629
|
+
if isinstance(pattern, dict):
|
|
630
|
+
if not isinstance(state, dict):
|
|
631
|
+
return False
|
|
632
|
+
|
|
633
|
+
if '_type' in pattern and not self.is_compatible(schema, pattern):
|
|
634
|
+
return False
|
|
635
|
+
|
|
636
|
+
for key, subpattern in pattern.items():
|
|
637
|
+
if key.startswith('_'):
|
|
638
|
+
continue
|
|
639
|
+
|
|
640
|
+
if key in schema:
|
|
641
|
+
subschema = schema[key]
|
|
642
|
+
else:
|
|
643
|
+
subschema = schema
|
|
644
|
+
|
|
645
|
+
if key in state:
|
|
646
|
+
matches = self.match_node(
|
|
647
|
+
subschema,
|
|
648
|
+
state[key],
|
|
649
|
+
pattern[key])
|
|
650
|
+
if not matches:
|
|
651
|
+
return False
|
|
652
|
+
else:
|
|
653
|
+
return False
|
|
654
|
+
|
|
655
|
+
return True
|
|
656
|
+
|
|
657
|
+
else:
|
|
658
|
+
return pattern == state
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _match_recur(self, schema, state, pattern, mode='first', path=()):
|
|
662
|
+
"""
|
|
663
|
+
private helper method for match
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
matches = []
|
|
667
|
+
|
|
668
|
+
match = self.match_node(
|
|
669
|
+
schema,
|
|
670
|
+
state,
|
|
671
|
+
pattern)
|
|
672
|
+
|
|
673
|
+
if match:
|
|
674
|
+
if mode == 'first' or mode == 'immediate':
|
|
675
|
+
return [path]
|
|
676
|
+
else:
|
|
677
|
+
matches.append(path)
|
|
678
|
+
elif mode == 'immediate':
|
|
679
|
+
return []
|
|
680
|
+
|
|
681
|
+
if isinstance(state, dict):
|
|
682
|
+
for key, substate in state.items():
|
|
683
|
+
if key.startswith('_'):
|
|
684
|
+
continue
|
|
685
|
+
|
|
686
|
+
if key in schema:
|
|
687
|
+
subschema = schema[key]
|
|
688
|
+
else:
|
|
689
|
+
subschema = schema
|
|
690
|
+
|
|
691
|
+
submatches = self._match_recur(
|
|
692
|
+
subschema,
|
|
693
|
+
state[key],
|
|
694
|
+
pattern,
|
|
695
|
+
mode=mode,
|
|
696
|
+
path=path + (key,))
|
|
697
|
+
|
|
698
|
+
if mode == 'first' and len(submatches) > 0:
|
|
699
|
+
return submatches[0:1]
|
|
700
|
+
else:
|
|
701
|
+
matches.extend(submatches)
|
|
702
|
+
|
|
703
|
+
return matches
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def match(self, original_schema, state, pattern, mode='first', path=()):
|
|
707
|
+
"""
|
|
708
|
+
find the path or paths to any instances of a
|
|
709
|
+
given pattern in the tree.
|
|
710
|
+
|
|
711
|
+
"mode" can be a few things:
|
|
712
|
+
* immediate: only match top level
|
|
713
|
+
* first: only return the first match
|
|
714
|
+
* random: return a random match of all that matched
|
|
715
|
+
* all (or any other value): return every match in the tree
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
schema = self.access(original_schema)
|
|
719
|
+
|
|
720
|
+
matches = self._match_recur(
|
|
721
|
+
schema,
|
|
722
|
+
state,
|
|
723
|
+
pattern,
|
|
724
|
+
mode=mode,
|
|
725
|
+
path=path)
|
|
726
|
+
|
|
727
|
+
if mode == 'random':
|
|
728
|
+
matches_count = len(matches)
|
|
729
|
+
if matches_count > 0:
|
|
730
|
+
choice = random.randint(
|
|
731
|
+
0,
|
|
732
|
+
matches_count-1)
|
|
733
|
+
matches = [matches[choice]]
|
|
734
|
+
|
|
735
|
+
return matches
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _react(self, schema, state, reaction, mode='random'):
|
|
739
|
+
"""
|
|
740
|
+
helper for apply_update
|
|
741
|
+
"""
|
|
742
|
+
# TODO: explain all this
|
|
743
|
+
# TODO: after the reaction, fill in the state with missing values
|
|
744
|
+
# from the schema
|
|
745
|
+
|
|
746
|
+
# TODO: add schema to redex and reactum
|
|
747
|
+
|
|
748
|
+
if 'redex' in reaction or 'reactum' in reaction or 'calls' in reaction:
|
|
749
|
+
redex = reaction.get('redex', {})
|
|
750
|
+
reactum = reaction.get('reactum', {})
|
|
751
|
+
calls = reaction.get('calls', {})
|
|
752
|
+
else:
|
|
753
|
+
# single key with reaction name
|
|
754
|
+
reaction_key = list(reaction.keys())[0]
|
|
755
|
+
make_reaction = self.react_registry.access(
|
|
756
|
+
reaction_key)
|
|
757
|
+
react = make_reaction(
|
|
758
|
+
schema,
|
|
759
|
+
state,
|
|
760
|
+
reaction.get(reaction_key, {}),
|
|
761
|
+
self)
|
|
762
|
+
|
|
763
|
+
redex = react.get('redex', {})
|
|
764
|
+
reactum = react.get('reactum', {})
|
|
765
|
+
calls = react.get('calls', {})
|
|
766
|
+
|
|
767
|
+
paths = self.match(
|
|
768
|
+
schema,
|
|
769
|
+
state,
|
|
770
|
+
redex,
|
|
771
|
+
mode=mode)
|
|
772
|
+
|
|
773
|
+
# for path in paths:
|
|
774
|
+
# path_schema, path_state = self.slice(
|
|
775
|
+
# schema,
|
|
776
|
+
# state,
|
|
777
|
+
# path)
|
|
778
|
+
|
|
779
|
+
def _merge_state(before):
|
|
780
|
+
"""
|
|
781
|
+
helper used in call to transform_path
|
|
782
|
+
"""
|
|
783
|
+
remaining = remove_omitted(
|
|
784
|
+
redex,
|
|
785
|
+
reactum,
|
|
786
|
+
before)
|
|
787
|
+
|
|
788
|
+
merged = deep_merge(
|
|
789
|
+
remaining,
|
|
790
|
+
reactum)
|
|
791
|
+
|
|
792
|
+
return merged
|
|
793
|
+
|
|
794
|
+
for path in paths:
|
|
795
|
+
state = transform_path(
|
|
796
|
+
state,
|
|
797
|
+
path,
|
|
798
|
+
_merge_state)
|
|
799
|
+
|
|
800
|
+
return state
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def resolve(self, schema, update):
|
|
804
|
+
if update is None:
|
|
805
|
+
return schema
|
|
806
|
+
else:
|
|
807
|
+
resolve_function = self.choose_method(
|
|
808
|
+
schema,
|
|
809
|
+
update,
|
|
810
|
+
'resolve')
|
|
811
|
+
|
|
812
|
+
return resolve_function(
|
|
813
|
+
schema,
|
|
814
|
+
update,
|
|
815
|
+
self)
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def resolve_schemas(self, initial_current, initial_update):
|
|
819
|
+
"""
|
|
820
|
+
"""
|
|
821
|
+
if initial_current == initial_update:
|
|
822
|
+
return initial_current
|
|
823
|
+
|
|
824
|
+
current = self.access(initial_current)
|
|
825
|
+
update = self.access(initial_update)
|
|
826
|
+
# current = initial_current
|
|
827
|
+
# update = initial_update
|
|
828
|
+
|
|
829
|
+
if self.equivalent(current, update):
|
|
830
|
+
outcome = current
|
|
831
|
+
|
|
832
|
+
elif self.inherits_from(current, update):
|
|
833
|
+
outcome = current
|
|
834
|
+
|
|
835
|
+
elif self.inherits_from(update, current):
|
|
836
|
+
outcome = update
|
|
837
|
+
|
|
838
|
+
elif '_type' in current and '_type' in update and current['_type'] == update['_type']:
|
|
839
|
+
outcome = {}
|
|
840
|
+
|
|
841
|
+
for key in update:
|
|
842
|
+
if key == '_type_parameters' and '_type_parameters' in current:
|
|
843
|
+
for parameter in update['_type_parameters']:
|
|
844
|
+
parameter_key = f'_{parameter}'
|
|
845
|
+
if parameter in current['_type_parameters']:
|
|
846
|
+
if parameter_key in current:
|
|
847
|
+
if parameter_key in update:
|
|
848
|
+
outcome[parameter_key] = self.resolve_schemas(
|
|
849
|
+
current[parameter_key],
|
|
850
|
+
update[parameter_key])
|
|
851
|
+
else:
|
|
852
|
+
outcome[parameter_key] = current[parameter_key]
|
|
853
|
+
elif parameter_key in update:
|
|
854
|
+
outcome[parameter_key] = update[parameter_key]
|
|
855
|
+
else:
|
|
856
|
+
outcome[parameter_key] = update[parameter_key]
|
|
857
|
+
elif key not in current or type_parameter_key(current, key):
|
|
858
|
+
if update[key]:
|
|
859
|
+
outcome[key] = update[key]
|
|
860
|
+
else:
|
|
861
|
+
outcome[key] = current.get(key)
|
|
862
|
+
elif key in current and current[key]:
|
|
863
|
+
outcome[key] = self.resolve_schemas(
|
|
864
|
+
current[key],
|
|
865
|
+
update[key])
|
|
866
|
+
else:
|
|
867
|
+
outcome[key] = update[key]
|
|
868
|
+
|
|
869
|
+
elif '_type' in update and '_type' not in current:
|
|
870
|
+
outcome = self.resolve(update, current)
|
|
871
|
+
|
|
872
|
+
else:
|
|
873
|
+
# current = self.access(initial_current)
|
|
874
|
+
# update = self.access(initial_update)
|
|
875
|
+
outcome = self.resolve(current, update)
|
|
876
|
+
|
|
877
|
+
return outcome
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def check_state(self, schema, state):
|
|
881
|
+
schema = self.access(schema)
|
|
882
|
+
|
|
883
|
+
check_function = self.choose_method(
|
|
884
|
+
schema,
|
|
885
|
+
state,
|
|
886
|
+
'check')
|
|
887
|
+
|
|
888
|
+
return check_function(
|
|
889
|
+
schema,
|
|
890
|
+
state,
|
|
891
|
+
self)
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def check(self, initial_schema, state):
|
|
895
|
+
schema = self.retrieve(initial_schema)
|
|
896
|
+
return self.check_state(schema, state)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def fold_state(self, schema, state, method, values):
|
|
900
|
+
schema = self.access(schema)
|
|
901
|
+
|
|
902
|
+
fold_function = self.choose_method(
|
|
903
|
+
schema,
|
|
904
|
+
state,
|
|
905
|
+
'fold')
|
|
906
|
+
|
|
907
|
+
return fold_function(
|
|
908
|
+
schema,
|
|
909
|
+
state,
|
|
910
|
+
method,
|
|
911
|
+
values,
|
|
912
|
+
self)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def fold(self, initial_schema, state, method, values=None):
|
|
916
|
+
schema = self.retrieve(initial_schema)
|
|
917
|
+
return self.fold_state(
|
|
918
|
+
schema,
|
|
919
|
+
state,
|
|
920
|
+
method,
|
|
921
|
+
values)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def apply_update(self, schema, state, update,
|
|
925
|
+
top_schema=None, top_state=None, path=None):
|
|
926
|
+
schema = self.access(schema)
|
|
927
|
+
|
|
928
|
+
top_schema = top_schema or schema
|
|
929
|
+
top_state = top_state or state
|
|
930
|
+
path = path or []
|
|
931
|
+
|
|
932
|
+
if isinstance(update, dict) and '_react' in update:
|
|
933
|
+
new_state = self._react(
|
|
934
|
+
schema,
|
|
935
|
+
state,
|
|
936
|
+
update['_react'])
|
|
937
|
+
|
|
938
|
+
state = self.deserialize(schema, new_state)
|
|
939
|
+
|
|
940
|
+
elif isinstance(update, dict) and '_fold' in update:
|
|
941
|
+
fold = update['_fold']
|
|
942
|
+
|
|
943
|
+
if isinstance(fold, dict):
|
|
944
|
+
method = fold['method']
|
|
945
|
+
values = {
|
|
946
|
+
key: value
|
|
947
|
+
for key, value in fold.items()
|
|
948
|
+
if key != 'method'}
|
|
949
|
+
|
|
950
|
+
elif isinstance(fold, str):
|
|
951
|
+
method = fold
|
|
952
|
+
values = {}
|
|
953
|
+
|
|
954
|
+
else:
|
|
955
|
+
raise Exception(f'unknown fold: {pf(update)}')
|
|
956
|
+
|
|
957
|
+
state = self.fold(
|
|
958
|
+
schema,
|
|
959
|
+
state,
|
|
960
|
+
method,
|
|
961
|
+
values)
|
|
962
|
+
|
|
963
|
+
elif isinstance(schema, str) or isinstance(schema, list):
|
|
964
|
+
schema = self.access(schema)
|
|
965
|
+
state = self.apply_update(
|
|
966
|
+
schema,
|
|
967
|
+
state,
|
|
968
|
+
update,
|
|
969
|
+
top_schema=top_schema,
|
|
970
|
+
top_state=top_state,
|
|
971
|
+
path=path)
|
|
972
|
+
|
|
973
|
+
elif '_apply' in schema and schema['_apply'] != 'any':
|
|
974
|
+
apply_function = self.apply_registry.access(schema['_apply'])
|
|
975
|
+
|
|
976
|
+
state = apply_function(
|
|
977
|
+
schema,
|
|
978
|
+
state,
|
|
979
|
+
update,
|
|
980
|
+
top_schema,
|
|
981
|
+
top_state,
|
|
982
|
+
path,
|
|
983
|
+
self)
|
|
984
|
+
|
|
985
|
+
elif isinstance(update, dict):
|
|
986
|
+
for key, branch in update.items():
|
|
987
|
+
if key not in schema:
|
|
988
|
+
raise Exception(
|
|
989
|
+
f'trying to update a key that is not in the schema '
|
|
990
|
+
f'for state: {key}\n{state}\nwith schema:\n{schema}')
|
|
991
|
+
else:
|
|
992
|
+
subupdate = self.apply_update(
|
|
993
|
+
self.access(schema[key]),
|
|
994
|
+
state[key],
|
|
995
|
+
branch,
|
|
996
|
+
top_schema=top_schema,
|
|
997
|
+
top_state=top_state,
|
|
998
|
+
path=path + [key])
|
|
999
|
+
|
|
1000
|
+
state[key] = subupdate
|
|
1001
|
+
else:
|
|
1002
|
+
raise Exception(
|
|
1003
|
+
f'trying to apply update\n {update}\nto state\n {state}\n'
|
|
1004
|
+
f'with schema\n {schema}\nbut the update is not a dict')
|
|
1005
|
+
|
|
1006
|
+
return state
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def apply(self, original_schema, initial, update):
|
|
1010
|
+
schema = self.access(original_schema)
|
|
1011
|
+
state = copy.deepcopy(initial)
|
|
1012
|
+
return self.apply_update(
|
|
1013
|
+
schema,
|
|
1014
|
+
state,
|
|
1015
|
+
update)
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def merge_recur(self, schema, state, update):
|
|
1019
|
+
"""
|
|
1020
|
+
not a helper to merge, as it would first appear
|
|
1021
|
+
"""
|
|
1022
|
+
if is_empty(schema):
|
|
1023
|
+
merge_state = update
|
|
1024
|
+
|
|
1025
|
+
elif is_empty(update):
|
|
1026
|
+
merge_state = state
|
|
1027
|
+
|
|
1028
|
+
elif isinstance(update, dict):
|
|
1029
|
+
if isinstance(state, dict):
|
|
1030
|
+
for key, value in update.items():
|
|
1031
|
+
if is_schema_key(key):
|
|
1032
|
+
state[key] = value
|
|
1033
|
+
else:
|
|
1034
|
+
if isinstance(schema, str):
|
|
1035
|
+
schema = self.access(schema)
|
|
1036
|
+
|
|
1037
|
+
state[key] = self.merge_recur(
|
|
1038
|
+
schema.get(key),
|
|
1039
|
+
state.get(key),
|
|
1040
|
+
value)
|
|
1041
|
+
merge_state = state
|
|
1042
|
+
else:
|
|
1043
|
+
merge_state = update
|
|
1044
|
+
else:
|
|
1045
|
+
merge_state = update
|
|
1046
|
+
|
|
1047
|
+
return merge_state
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def merge(self, schema, state, path, update_schema, update_state, defer=False):
|
|
1051
|
+
top_schema, top_state = self.set_slice(
|
|
1052
|
+
schema,
|
|
1053
|
+
state,
|
|
1054
|
+
path,
|
|
1055
|
+
update_schema,
|
|
1056
|
+
update_state,
|
|
1057
|
+
defer)
|
|
1058
|
+
|
|
1059
|
+
return self.generate(top_schema, top_state)
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def bind(self, schema, state, key, target_schema, target_state):
|
|
1063
|
+
schema = self.retrieve(schema)
|
|
1064
|
+
|
|
1065
|
+
bind_function = self.choose_method(
|
|
1066
|
+
schema,
|
|
1067
|
+
state,
|
|
1068
|
+
'bind')
|
|
1069
|
+
|
|
1070
|
+
return bind_function(
|
|
1071
|
+
schema,
|
|
1072
|
+
state,
|
|
1073
|
+
key,
|
|
1074
|
+
target_schema,
|
|
1075
|
+
target_state,
|
|
1076
|
+
self)
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def set_slice(self, schema, state, path, target_schema, target_state, defer=False):
|
|
1080
|
+
"""
|
|
1081
|
+
Makes a local modification to the schema/state at the path, and
|
|
1082
|
+
returns the top_schema and top_state
|
|
1083
|
+
"""
|
|
1084
|
+
|
|
1085
|
+
path = resolve_path(path)
|
|
1086
|
+
|
|
1087
|
+
if len(path) == 0:
|
|
1088
|
+
# deal with paths of length 0
|
|
1089
|
+
# this should never happen?
|
|
1090
|
+
merged_schema = self.resolve_schemas(
|
|
1091
|
+
schema,
|
|
1092
|
+
target_schema)
|
|
1093
|
+
|
|
1094
|
+
merged_state = deep_merge(
|
|
1095
|
+
state,
|
|
1096
|
+
target_state)
|
|
1097
|
+
|
|
1098
|
+
return merged_schema, merged_state
|
|
1099
|
+
|
|
1100
|
+
elif len(path) == 1:
|
|
1101
|
+
key = path[0]
|
|
1102
|
+
destination_schema, destination_state = self.slice(
|
|
1103
|
+
schema,
|
|
1104
|
+
state,
|
|
1105
|
+
key)
|
|
1106
|
+
|
|
1107
|
+
final_schema = self.resolve_schemas(
|
|
1108
|
+
destination_schema,
|
|
1109
|
+
target_schema)
|
|
1110
|
+
|
|
1111
|
+
if not defer:
|
|
1112
|
+
result_state = self.merge_recur(
|
|
1113
|
+
final_schema,
|
|
1114
|
+
destination_state,
|
|
1115
|
+
target_state)
|
|
1116
|
+
|
|
1117
|
+
else:
|
|
1118
|
+
result_state = self.merge_recur(
|
|
1119
|
+
final_schema,
|
|
1120
|
+
target_state,
|
|
1121
|
+
destination_state)
|
|
1122
|
+
|
|
1123
|
+
return self.bind(
|
|
1124
|
+
schema,
|
|
1125
|
+
state,
|
|
1126
|
+
key,
|
|
1127
|
+
final_schema,
|
|
1128
|
+
result_state)
|
|
1129
|
+
|
|
1130
|
+
else:
|
|
1131
|
+
head = path[0]
|
|
1132
|
+
tail = path[1:]
|
|
1133
|
+
|
|
1134
|
+
down_schema, down_state = self.slice(
|
|
1135
|
+
schema,
|
|
1136
|
+
state,
|
|
1137
|
+
head)
|
|
1138
|
+
|
|
1139
|
+
if head == '*':
|
|
1140
|
+
result_schema, result_state = down_schema, down_state
|
|
1141
|
+
for key in down_state:
|
|
1142
|
+
if key in target_state:
|
|
1143
|
+
subtarget_schema, subtarget_state = self.slice(
|
|
1144
|
+
target_schema,
|
|
1145
|
+
target_state,
|
|
1146
|
+
key)
|
|
1147
|
+
|
|
1148
|
+
try:
|
|
1149
|
+
result_schema, result_state = self.set_slice(
|
|
1150
|
+
result_schema,
|
|
1151
|
+
result_state,
|
|
1152
|
+
tail,
|
|
1153
|
+
subtarget_schema,
|
|
1154
|
+
subtarget_state,
|
|
1155
|
+
defer=defer)
|
|
1156
|
+
|
|
1157
|
+
except Exception as e:
|
|
1158
|
+
raise Exception(
|
|
1159
|
+
f'failed to set_slice at path {path}\n{str(e)}')
|
|
1160
|
+
|
|
1161
|
+
schema, state = self.bind(
|
|
1162
|
+
schema,
|
|
1163
|
+
state,
|
|
1164
|
+
key,
|
|
1165
|
+
result_schema,
|
|
1166
|
+
result_state)
|
|
1167
|
+
|
|
1168
|
+
return schema, state
|
|
1169
|
+
|
|
1170
|
+
else:
|
|
1171
|
+
try:
|
|
1172
|
+
result_schema, result_state = self.set_slice(
|
|
1173
|
+
down_schema,
|
|
1174
|
+
down_state,
|
|
1175
|
+
tail,
|
|
1176
|
+
target_schema,
|
|
1177
|
+
target_state,
|
|
1178
|
+
defer=defer)
|
|
1179
|
+
except Exception as e:
|
|
1180
|
+
raise Exception(f'failed to set_slice at path {path}\n{str(e)}')
|
|
1181
|
+
|
|
1182
|
+
return self.bind(
|
|
1183
|
+
schema,
|
|
1184
|
+
state,
|
|
1185
|
+
head,
|
|
1186
|
+
result_schema,
|
|
1187
|
+
result_state)
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def serialize(self, schema, state):
|
|
1191
|
+
schema = self.retrieve(schema)
|
|
1192
|
+
|
|
1193
|
+
serialize_function = self.choose_method(
|
|
1194
|
+
schema,
|
|
1195
|
+
state,
|
|
1196
|
+
'serialize')
|
|
1197
|
+
|
|
1198
|
+
return serialize_function(
|
|
1199
|
+
schema,
|
|
1200
|
+
state,
|
|
1201
|
+
self)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def deserialize(self, schema, state):
|
|
1205
|
+
schema = self.retrieve(schema)
|
|
1206
|
+
|
|
1207
|
+
deserialize_function = self.choose_method(
|
|
1208
|
+
schema,
|
|
1209
|
+
state,
|
|
1210
|
+
'deserialize')
|
|
1211
|
+
|
|
1212
|
+
deserialized = deserialize_function(
|
|
1213
|
+
schema,
|
|
1214
|
+
state,
|
|
1215
|
+
self)
|
|
1216
|
+
|
|
1217
|
+
return deserialized
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
def fill_state(self, schema, state=None, top_schema=None, top_state=None, path=None, type_key=None, context=None):
|
|
1221
|
+
# if a port is disconnected, build a store
|
|
1222
|
+
# for it under the '_open' key in the current
|
|
1223
|
+
# node (?)
|
|
1224
|
+
|
|
1225
|
+
# inform the user that they have disconnected
|
|
1226
|
+
# ports somehow
|
|
1227
|
+
|
|
1228
|
+
if schema is None:
|
|
1229
|
+
return None
|
|
1230
|
+
if state is None:
|
|
1231
|
+
state = self.default(schema)
|
|
1232
|
+
if top_schema is None:
|
|
1233
|
+
top_schema = schema
|
|
1234
|
+
if top_state is None:
|
|
1235
|
+
top_state = state
|
|
1236
|
+
if path is None:
|
|
1237
|
+
path = []
|
|
1238
|
+
|
|
1239
|
+
if '_inputs' in schema:
|
|
1240
|
+
inputs = state.get('inputs', {})
|
|
1241
|
+
state = self.fill_ports(
|
|
1242
|
+
schema['_inputs'],
|
|
1243
|
+
wires=inputs,
|
|
1244
|
+
state=state,
|
|
1245
|
+
top_schema=top_schema,
|
|
1246
|
+
top_state=top_state,
|
|
1247
|
+
path=path)
|
|
1248
|
+
|
|
1249
|
+
if '_outputs' in schema:
|
|
1250
|
+
outputs = state.get('outputs', {})
|
|
1251
|
+
state = self.fill_ports(
|
|
1252
|
+
schema['_outputs'],
|
|
1253
|
+
wires=outputs,
|
|
1254
|
+
state=state,
|
|
1255
|
+
top_schema=top_schema,
|
|
1256
|
+
top_state=top_state,
|
|
1257
|
+
path=path)
|
|
1258
|
+
|
|
1259
|
+
if isinstance(schema, str):
|
|
1260
|
+
schema = self.access(schema)
|
|
1261
|
+
|
|
1262
|
+
branches = non_schema_keys(schema)
|
|
1263
|
+
|
|
1264
|
+
if isinstance(state, dict):
|
|
1265
|
+
for branch in branches:
|
|
1266
|
+
subpath = path + [branch]
|
|
1267
|
+
state[branch] = self.fill_state(
|
|
1268
|
+
schema[branch],
|
|
1269
|
+
state=state.get(branch),
|
|
1270
|
+
top_schema=top_schema,
|
|
1271
|
+
top_state=top_state,
|
|
1272
|
+
path=subpath)
|
|
1273
|
+
|
|
1274
|
+
state_keys = non_schema_keys(state)
|
|
1275
|
+
for key in set(state_keys) - set(branches):
|
|
1276
|
+
subschema, substate = self.slice(
|
|
1277
|
+
schema,
|
|
1278
|
+
state,
|
|
1279
|
+
key)
|
|
1280
|
+
|
|
1281
|
+
subpath = path + [key]
|
|
1282
|
+
state[key] = self.fill_state(
|
|
1283
|
+
subschema,
|
|
1284
|
+
substate,
|
|
1285
|
+
top_schema=top_schema,
|
|
1286
|
+
top_state=top_state,
|
|
1287
|
+
path=subpath)
|
|
1288
|
+
|
|
1289
|
+
return state
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def fill(self, original_schema, state=None):
|
|
1293
|
+
schema = self.access(original_schema)
|
|
1294
|
+
|
|
1295
|
+
return self.fill_state(
|
|
1296
|
+
schema,
|
|
1297
|
+
state=state)
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
def ports_schema(self, schema, instance, edge_path, ports_key='inputs'):
|
|
1301
|
+
found = self.access(schema)
|
|
1302
|
+
|
|
1303
|
+
edge_schema, edge_state = self.slice(
|
|
1304
|
+
schema,
|
|
1305
|
+
instance,
|
|
1306
|
+
edge_path)
|
|
1307
|
+
|
|
1308
|
+
ports_schema = edge_state.get(f'_{ports_key}',
|
|
1309
|
+
edge_schema.get(f'_{ports_key}'))
|
|
1310
|
+
ports = edge_state.get(ports_key)
|
|
1311
|
+
|
|
1312
|
+
return ports_schema, ports
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
# start here for different methods of execution
|
|
1316
|
+
def view(self, schema, wires, path, top_schema=None, top_state=None):
|
|
1317
|
+
result = {}
|
|
1318
|
+
|
|
1319
|
+
if isinstance(wires, str):
|
|
1320
|
+
wires = [wires]
|
|
1321
|
+
|
|
1322
|
+
if isinstance(wires, (list, tuple)):
|
|
1323
|
+
_, result = self.slice(
|
|
1324
|
+
top_schema,
|
|
1325
|
+
top_state,
|
|
1326
|
+
list(path) + list(wires))
|
|
1327
|
+
|
|
1328
|
+
elif isinstance(wires, dict):
|
|
1329
|
+
result = {}
|
|
1330
|
+
for port_key, port_path in wires.items():
|
|
1331
|
+
subschema, _ = self.slice(
|
|
1332
|
+
schema,
|
|
1333
|
+
{},
|
|
1334
|
+
port_key)
|
|
1335
|
+
|
|
1336
|
+
inner_view = self.view(
|
|
1337
|
+
subschema,
|
|
1338
|
+
port_path,
|
|
1339
|
+
path,
|
|
1340
|
+
top_schema=top_schema,
|
|
1341
|
+
top_state=top_state)
|
|
1342
|
+
|
|
1343
|
+
if inner_view is not None:
|
|
1344
|
+
result[port_key] = inner_view
|
|
1345
|
+
else:
|
|
1346
|
+
raise Exception(f'trying to view state with these ports:\n{schema}\nbut not sure what these wires are:\n{wires}')
|
|
1347
|
+
|
|
1348
|
+
return result
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def view_edge(self, schema, state, edge_path=None, ports_key='inputs'):
|
|
1352
|
+
"""
|
|
1353
|
+
project the current state into a form the edge expects, based on its ports.
|
|
1354
|
+
"""
|
|
1355
|
+
|
|
1356
|
+
if schema is None:
|
|
1357
|
+
return None
|
|
1358
|
+
if state is None:
|
|
1359
|
+
state = self.default(schema)
|
|
1360
|
+
if edge_path is None:
|
|
1361
|
+
edge_path = []
|
|
1362
|
+
|
|
1363
|
+
ports_schema, ports = self.ports_schema(
|
|
1364
|
+
schema,
|
|
1365
|
+
state,
|
|
1366
|
+
edge_path=edge_path,
|
|
1367
|
+
ports_key=ports_key)
|
|
1368
|
+
|
|
1369
|
+
if not ports_schema:
|
|
1370
|
+
return None
|
|
1371
|
+
if ports is None:
|
|
1372
|
+
return None
|
|
1373
|
+
|
|
1374
|
+
return self.view(
|
|
1375
|
+
ports_schema,
|
|
1376
|
+
ports,
|
|
1377
|
+
edge_path[:-1],
|
|
1378
|
+
top_schema=schema,
|
|
1379
|
+
top_state=state)
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
def project(self, ports, wires, path, states):
|
|
1383
|
+
result = {}
|
|
1384
|
+
|
|
1385
|
+
if isinstance(wires, str):
|
|
1386
|
+
wires = [wires]
|
|
1387
|
+
|
|
1388
|
+
if isinstance(wires, (list, tuple)):
|
|
1389
|
+
destination = resolve_path(list(path) + list(wires))
|
|
1390
|
+
result = set_star_path(
|
|
1391
|
+
result,
|
|
1392
|
+
destination,
|
|
1393
|
+
states)
|
|
1394
|
+
|
|
1395
|
+
elif isinstance(wires, dict):
|
|
1396
|
+
if isinstance(states, list):
|
|
1397
|
+
result = [
|
|
1398
|
+
self.project(ports, wires, path, state)
|
|
1399
|
+
for state in states]
|
|
1400
|
+
else:
|
|
1401
|
+
branches = []
|
|
1402
|
+
for key in wires.keys():
|
|
1403
|
+
subports, substates = self.slice(ports, states, key)
|
|
1404
|
+
projection = self.project(
|
|
1405
|
+
subports,
|
|
1406
|
+
wires[key],
|
|
1407
|
+
path,
|
|
1408
|
+
substates)
|
|
1409
|
+
|
|
1410
|
+
if projection is not None:
|
|
1411
|
+
branches.append(projection)
|
|
1412
|
+
|
|
1413
|
+
branches = [
|
|
1414
|
+
branch
|
|
1415
|
+
for branch in branches
|
|
1416
|
+
if branch is not None]
|
|
1417
|
+
|
|
1418
|
+
result = {}
|
|
1419
|
+
for branch in branches:
|
|
1420
|
+
deep_merge(result, branch)
|
|
1421
|
+
else:
|
|
1422
|
+
raise Exception(
|
|
1423
|
+
f'inverting state\n {states}\naccording to ports schema\n {ports}\nbut wires are not recognized\n {wires}')
|
|
1424
|
+
|
|
1425
|
+
return result
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
def project_edge(self, schema, instance, edge_path, states, ports_key='outputs'):
|
|
1429
|
+
"""
|
|
1430
|
+
Given states from the perspective of an edge (through its ports), produce states aligned to the tree
|
|
1431
|
+
the wires point to.
|
|
1432
|
+
(inverse of view)
|
|
1433
|
+
"""
|
|
1434
|
+
|
|
1435
|
+
if schema is None:
|
|
1436
|
+
return None
|
|
1437
|
+
if instance is None:
|
|
1438
|
+
instance = self.default(schema)
|
|
1439
|
+
|
|
1440
|
+
ports_schema, ports = self.ports_schema(
|
|
1441
|
+
schema,
|
|
1442
|
+
instance,
|
|
1443
|
+
edge_path,
|
|
1444
|
+
ports_key)
|
|
1445
|
+
|
|
1446
|
+
if ports_schema is None:
|
|
1447
|
+
return None
|
|
1448
|
+
if ports is None:
|
|
1449
|
+
return None
|
|
1450
|
+
|
|
1451
|
+
return self.project(
|
|
1452
|
+
ports_schema,
|
|
1453
|
+
ports,
|
|
1454
|
+
edge_path[:-1],
|
|
1455
|
+
states)
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
def equivalent(self, icurrent, iquestion):
|
|
1459
|
+
if icurrent == iquestion:
|
|
1460
|
+
return True
|
|
1461
|
+
|
|
1462
|
+
current = self.access(icurrent)
|
|
1463
|
+
question = self.access(iquestion)
|
|
1464
|
+
|
|
1465
|
+
if current is None:
|
|
1466
|
+
return question is None
|
|
1467
|
+
|
|
1468
|
+
if current == {}:
|
|
1469
|
+
return question == {}
|
|
1470
|
+
|
|
1471
|
+
elif '_type' in current:
|
|
1472
|
+
if '_type' in question:
|
|
1473
|
+
if current['_type'] == question['_type']:
|
|
1474
|
+
if '_type_parameters' in current:
|
|
1475
|
+
if '_type_parameters' in question:
|
|
1476
|
+
for parameter in current['_type_parameters']:
|
|
1477
|
+
parameter_key = f'_{parameter}'
|
|
1478
|
+
if parameter_key in question:
|
|
1479
|
+
if parameter_key not in current:
|
|
1480
|
+
return False
|
|
1481
|
+
|
|
1482
|
+
if not self.equivalent(current[parameter_key], question[parameter_key]):
|
|
1483
|
+
return False
|
|
1484
|
+
else:
|
|
1485
|
+
return False
|
|
1486
|
+
else:
|
|
1487
|
+
return False
|
|
1488
|
+
else:
|
|
1489
|
+
return False
|
|
1490
|
+
else:
|
|
1491
|
+
if '_type' in question:
|
|
1492
|
+
return False
|
|
1493
|
+
|
|
1494
|
+
for key, value in current.items():
|
|
1495
|
+
if not is_schema_key(key): # key not in type_schema_keys:
|
|
1496
|
+
if key not in question or not self.equivalent(current.get(key), question[key]):
|
|
1497
|
+
return False
|
|
1498
|
+
|
|
1499
|
+
for key in set(question.keys()) - set(current.keys()):
|
|
1500
|
+
if not is_schema_key(key): # key not in type_schema_keys:
|
|
1501
|
+
if key not in question or not self.equivalent(current.get(key), question[key]):
|
|
1502
|
+
return False
|
|
1503
|
+
|
|
1504
|
+
return True
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
def inherits_from(self, descendant, ancestor):
|
|
1508
|
+
descendant = self.access(descendant)
|
|
1509
|
+
ancestor = self.access(ancestor)
|
|
1510
|
+
|
|
1511
|
+
if descendant == {}:
|
|
1512
|
+
return ancestor == {}
|
|
1513
|
+
|
|
1514
|
+
if descendant is None:
|
|
1515
|
+
return True
|
|
1516
|
+
|
|
1517
|
+
if ancestor is None:
|
|
1518
|
+
return False
|
|
1519
|
+
|
|
1520
|
+
if isinstance(ancestor, int):
|
|
1521
|
+
if isinstance(descendant, int):
|
|
1522
|
+
return ancestor == descendant
|
|
1523
|
+
else:
|
|
1524
|
+
return False
|
|
1525
|
+
|
|
1526
|
+
elif isinstance(ancestor, list):
|
|
1527
|
+
if isinstance(descendant, list):
|
|
1528
|
+
if len(ancestor) == len(descendant):
|
|
1529
|
+
for a, d in zip(ancestor, descendant):
|
|
1530
|
+
if not self.inherits_from(d, a):
|
|
1531
|
+
return False
|
|
1532
|
+
else:
|
|
1533
|
+
return False
|
|
1534
|
+
|
|
1535
|
+
elif '_type' in ancestor and ancestor['_type'] == 'any':
|
|
1536
|
+
return True
|
|
1537
|
+
|
|
1538
|
+
elif '_type' in descendant:
|
|
1539
|
+
if '_inherit' in descendant:
|
|
1540
|
+
for inherit in descendant['_inherit']:
|
|
1541
|
+
|
|
1542
|
+
if self.equivalent(inherit, ancestor) or self.inherits_from(inherit, ancestor):
|
|
1543
|
+
return True
|
|
1544
|
+
|
|
1545
|
+
return False
|
|
1546
|
+
|
|
1547
|
+
elif '_type_parameters' in descendant:
|
|
1548
|
+
for type_parameter in descendant['_type_parameters']:
|
|
1549
|
+
parameter_key = f'_{type_parameter}'
|
|
1550
|
+
if parameter_key in ancestor and parameter_key in descendant:
|
|
1551
|
+
if not self.inherits_from(descendant[parameter_key], ancestor[parameter_key]):
|
|
1552
|
+
return False
|
|
1553
|
+
|
|
1554
|
+
if '_type' not in ancestor or descendant['_type'] != ancestor['_type']:
|
|
1555
|
+
return False
|
|
1556
|
+
|
|
1557
|
+
else:
|
|
1558
|
+
for key, value in ancestor.items():
|
|
1559
|
+
if key not in type_schema_keys:
|
|
1560
|
+
# if not key.startswith('_'):
|
|
1561
|
+
if key in descendant:
|
|
1562
|
+
if not self.inherits_from(descendant[key], value):
|
|
1563
|
+
return False
|
|
1564
|
+
else:
|
|
1565
|
+
return False
|
|
1566
|
+
|
|
1567
|
+
return True
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
def wire_schema(self, schema, wires, path=None):
|
|
1572
|
+
outcome = {}
|
|
1573
|
+
path = path or []
|
|
1574
|
+
|
|
1575
|
+
if isinstance(wires, dict):
|
|
1576
|
+
for key, subwires in wires.items():
|
|
1577
|
+
outcome[key] = self.wire_schema(
|
|
1578
|
+
schema,
|
|
1579
|
+
wires[key],
|
|
1580
|
+
path + [key])
|
|
1581
|
+
|
|
1582
|
+
else:
|
|
1583
|
+
_, outcome = self.slice('schema', schema, wires)
|
|
1584
|
+
|
|
1585
|
+
return outcome
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
def _generate_recur(self, schema, state, top_schema=None, top_state=None, path=None):
|
|
1589
|
+
"""
|
|
1590
|
+
helper for type_functions
|
|
1591
|
+
"""
|
|
1592
|
+
found = self.retrieve(
|
|
1593
|
+
schema)
|
|
1594
|
+
|
|
1595
|
+
generate_function = self.choose_method(
|
|
1596
|
+
found,
|
|
1597
|
+
state,
|
|
1598
|
+
'generate')
|
|
1599
|
+
|
|
1600
|
+
return generate_function(
|
|
1601
|
+
self,
|
|
1602
|
+
found,
|
|
1603
|
+
state,
|
|
1604
|
+
top_schema=top_schema,
|
|
1605
|
+
top_state=top_state,
|
|
1606
|
+
path=path)
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
def generate(self, schema, state):
|
|
1610
|
+
merged_schema, merged_state = self._sort(
|
|
1611
|
+
schema,
|
|
1612
|
+
state)
|
|
1613
|
+
|
|
1614
|
+
_, _, top_schema, top_state = self._generate_recur(
|
|
1615
|
+
merged_schema,
|
|
1616
|
+
merged_state)
|
|
1617
|
+
|
|
1618
|
+
return top_schema, top_state
|
|
1619
|
+
|
|
1620
|
+
|
|
1621
|
+
def find_method(self, schema, method_key):
|
|
1622
|
+
if not isinstance(schema, dict) or method_key not in schema:
|
|
1623
|
+
schema = self.access(schema)
|
|
1624
|
+
|
|
1625
|
+
if method_key in schema:
|
|
1626
|
+
registry = self._lookup_registry(
|
|
1627
|
+
method_key)
|
|
1628
|
+
|
|
1629
|
+
if registry is not None:
|
|
1630
|
+
method_name = schema[method_key]
|
|
1631
|
+
method = registry.access(method_name)
|
|
1632
|
+
|
|
1633
|
+
return method
|
|
1634
|
+
|
|
1635
|
+
def representation(self, schema, path=None, parents=None):
|
|
1636
|
+
"""
|
|
1637
|
+
produce a string representation of the schema
|
|
1638
|
+
|
|
1639
|
+
* intended to be the inverse of parse_expression()
|
|
1640
|
+
"""
|
|
1641
|
+
|
|
1642
|
+
path = path or []
|
|
1643
|
+
parents = parents or []
|
|
1644
|
+
schema_id = id(schema)
|
|
1645
|
+
|
|
1646
|
+
if schema_id in parents:
|
|
1647
|
+
index = parents.index(schema_id)
|
|
1648
|
+
reference = path[:index]
|
|
1649
|
+
output = '/'.join(reference)
|
|
1650
|
+
|
|
1651
|
+
return f'/{output}'
|
|
1652
|
+
|
|
1653
|
+
if isinstance(schema, str):
|
|
1654
|
+
return schema
|
|
1655
|
+
|
|
1656
|
+
elif isinstance(schema, tuple):
|
|
1657
|
+
inner = [
|
|
1658
|
+
self.representation(
|
|
1659
|
+
element,
|
|
1660
|
+
path + [index],
|
|
1661
|
+
parents + [schema_id])
|
|
1662
|
+
for index, element in enumerate(schema)]
|
|
1663
|
+
|
|
1664
|
+
pipes = '|'.join(inner)
|
|
1665
|
+
return f'({pipes})'
|
|
1666
|
+
|
|
1667
|
+
elif isinstance(schema, dict):
|
|
1668
|
+
if '_type' in schema:
|
|
1669
|
+
type = schema['_type']
|
|
1670
|
+
|
|
1671
|
+
inner = []
|
|
1672
|
+
block = ''
|
|
1673
|
+
if '_type_parameters' in schema:
|
|
1674
|
+
for parameter_key in schema['_type_parameters']:
|
|
1675
|
+
schema_key = f'_{parameter_key}'
|
|
1676
|
+
if schema_key in schema:
|
|
1677
|
+
parameter = self.representation(
|
|
1678
|
+
schema[schema_key],
|
|
1679
|
+
path + [schema_key],
|
|
1680
|
+
parents + [schema_id])
|
|
1681
|
+
inner.append(parameter)
|
|
1682
|
+
else:
|
|
1683
|
+
inner.append('()')
|
|
1684
|
+
|
|
1685
|
+
commas = ','.join(inner)
|
|
1686
|
+
block = f'[{commas}]'
|
|
1687
|
+
|
|
1688
|
+
if type == 'tuple':
|
|
1689
|
+
pipes = '|'.join(inner)
|
|
1690
|
+
return f'({pipes})'
|
|
1691
|
+
else:
|
|
1692
|
+
return f"{type}{block}"
|
|
1693
|
+
|
|
1694
|
+
else:
|
|
1695
|
+
inner = {}
|
|
1696
|
+
for key in non_schema_keys(schema):
|
|
1697
|
+
subschema = self.representation(
|
|
1698
|
+
schema[key],
|
|
1699
|
+
path + [key],
|
|
1700
|
+
parents + [schema_id])
|
|
1701
|
+
|
|
1702
|
+
inner[key] = subschema
|
|
1703
|
+
|
|
1704
|
+
colons = [
|
|
1705
|
+
f'{key}:{value}'
|
|
1706
|
+
for key, value in inner.items()]
|
|
1707
|
+
|
|
1708
|
+
pipes = '|'.join(colons)
|
|
1709
|
+
return f'({pipes})'
|
|
1710
|
+
else:
|
|
1711
|
+
return str(schema)
|
|
1712
|
+
|
|
1713
|
+
def validate_schema(self, schema, enforce_connections=False):
|
|
1714
|
+
"""
|
|
1715
|
+
"""
|
|
1716
|
+
# TODO: needs documentation and testing
|
|
1717
|
+
|
|
1718
|
+
# TODO:
|
|
1719
|
+
# check() always returns true or false,
|
|
1720
|
+
# validate() returns information about what doesn't match
|
|
1721
|
+
|
|
1722
|
+
# add ports and wires
|
|
1723
|
+
# validate ports are wired to a matching type,
|
|
1724
|
+
# either the same type or a subtype (more specific type)
|
|
1725
|
+
# declared ports are equivalent to the presence of a process
|
|
1726
|
+
# where the ports need to be looked up
|
|
1727
|
+
|
|
1728
|
+
report = {}
|
|
1729
|
+
|
|
1730
|
+
if schema is None:
|
|
1731
|
+
report = 'schema cannot be None'
|
|
1732
|
+
|
|
1733
|
+
elif isinstance(schema, str):
|
|
1734
|
+
typ = self.access(
|
|
1735
|
+
schema,
|
|
1736
|
+
strict=True)
|
|
1737
|
+
|
|
1738
|
+
if typ is None:
|
|
1739
|
+
report = f'type: {schema} is not in the registry'
|
|
1740
|
+
|
|
1741
|
+
elif isinstance(schema, dict):
|
|
1742
|
+
report = {}
|
|
1743
|
+
|
|
1744
|
+
schema_keys = set([])
|
|
1745
|
+
branches = set([])
|
|
1746
|
+
|
|
1747
|
+
for key, value in schema.items():
|
|
1748
|
+
if key == '_type':
|
|
1749
|
+
typ = self.access(
|
|
1750
|
+
value,
|
|
1751
|
+
strict=True)
|
|
1752
|
+
if typ is None:
|
|
1753
|
+
report[key] = f'type: {value} is not in the registry'
|
|
1754
|
+
|
|
1755
|
+
elif key in type_schema_keys:
|
|
1756
|
+
schema_keys.add(key)
|
|
1757
|
+
registry = self._lookup_registry(key)
|
|
1758
|
+
if registry is None or key == '_default':
|
|
1759
|
+
# deserialize and serialize back and check it is equal
|
|
1760
|
+
pass
|
|
1761
|
+
elif isinstance(value, str):
|
|
1762
|
+
element = registry.access(
|
|
1763
|
+
value,
|
|
1764
|
+
strict=True)
|
|
1765
|
+
|
|
1766
|
+
if element is None:
|
|
1767
|
+
report[key] = f'no entry in the {key} registry for: {value}'
|
|
1768
|
+
elif not inspect.isfunction(value):
|
|
1769
|
+
report[key] = f'unknown value for key {key}: {value}'
|
|
1770
|
+
else:
|
|
1771
|
+
branches.add(key)
|
|
1772
|
+
branch_report = self.validate_schema(value)
|
|
1773
|
+
if len(branch_report) > 0:
|
|
1774
|
+
report[key] = branch_report
|
|
1775
|
+
|
|
1776
|
+
return report
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
apply_slice = TypeSystemAdjunct.apply_slice
|
|
1780
|
+
complete = TypeSystemAdjunct.complete
|
|
1781
|
+
compose = TypeSystemAdjunct.compose
|
|
1782
|
+
define = TypeSystemAdjunct.define
|
|
1783
|
+
fill_ports = TypeSystemAdjunct.fill_ports
|
|
1784
|
+
hydrate = TypeSystemAdjunct.hydrate
|
|
1785
|
+
import_types = TypeSystemAdjunct.import_types
|
|
1786
|
+
infer_wires = TypeSystemAdjunct.infer_wires
|
|
1787
|
+
infer_edge = TypeSystemAdjunct.infer_edge
|
|
1788
|
+
infer_schema = TypeSystemAdjunct.infer_schema
|
|
1789
|
+
link_place = TypeSystemAdjunct.link_place
|
|
1790
|
+
lookup = TypeSystemAdjunct.lookup
|
|
1791
|
+
merge_schemas = TypeSystemAdjunct.merge_schemas
|
|
1792
|
+
query = TypeSystemAdjunct.query
|
|
1793
|
+
resolve_parameters = TypeSystemAdjunct.resolve_parameters
|
|
1794
|
+
set = TypeSystemAdjunct.set
|
|
1795
|
+
set_update = TypeSystemAdjunct.set_update
|
|
1796
|
+
types = TypeSystemAdjunct.types
|
|
1797
|
+
validate = TypeSystemAdjunct.validate
|
|
1798
|
+
validate_state = TypeSystemAdjunct.validate_state
|