bigraph-schema 0.0.62__tar.gz → 0.0.64__tar.gz
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-0.0.62/bigraph_schema.egg-info → bigraph-schema-0.0.64}/PKG-INFO +1 -1
- bigraph-schema-0.0.64/bigraph_schema/edge.py +105 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/parse.py +55 -94
- bigraph-schema-0.0.64/bigraph_schema/registry.py +287 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/type_functions.py +9 -12
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64/bigraph_schema.egg-info}/PKG-INFO +1 -1
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/pyproject.toml +6 -1
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/setup.py +1 -1
- bigraph-schema-0.0.62/bigraph_schema/edge.py +0 -59
- bigraph-schema-0.0.62/bigraph_schema/registry.py +0 -412
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/.github/workflows/notebook_to_html.yml +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/.github/workflows/pytest.yml +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/.gitignore +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/AUTHORS.md +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/CLA.md +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/CODE_OF_CONDUCT.md +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/CONTRIBUTING.md +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/LICENSE +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/README.md +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/__init__.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/protocols.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/tests.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/type_system.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/type_system_adjunct.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/units.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema/utilities.py +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema.egg-info/SOURCES.txt +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema.egg-info/dependency_links.txt +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema.egg-info/requires.txt +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema.egg-info/top_level.txt +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/notebooks/core.ipynb +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/notebooks/demo.ipynb +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/notebooks/images/place-link.png +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/notebooks/images/reaction-after.png +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/notebooks/images/reaction-before.png +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/notebooks/images/redex-reactum.png +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/pytest.ini +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/release.sh +0 -0
- {bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/setup.cfg +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
====
|
|
3
|
+
Edge
|
|
4
|
+
====
|
|
5
|
+
|
|
6
|
+
Base class for all edges in the bigraph schema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def default_wires(schema):
|
|
10
|
+
"""
|
|
11
|
+
Create default wiring for a schema by connecting each port to a store of the same name.
|
|
12
|
+
"""
|
|
13
|
+
return {
|
|
14
|
+
key: [key]
|
|
15
|
+
for key in schema}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Edge:
|
|
19
|
+
"""
|
|
20
|
+
Base class for all computational edges in the bigraph schema.
|
|
21
|
+
|
|
22
|
+
Edges define the interface between simulation processes and the global state,
|
|
23
|
+
specifying the structure of input and output ports using bigraph types
|
|
24
|
+
(e.g., 'float', 'map[float]', 'list[integer]', etc.).
|
|
25
|
+
|
|
26
|
+
Upon instantiation, each edge registers its port types with the core.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
config_schema = {}
|
|
30
|
+
|
|
31
|
+
def __init__(self, config=None, core=None):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the edge with a config and simulation core.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
config (dict): Optional configuration dictionary. Defaults to empty dict.
|
|
37
|
+
core: The core simulation engine that manages this edge and its types.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
Exception: If `core` is not provided.
|
|
41
|
+
"""
|
|
42
|
+
if core is None:
|
|
43
|
+
raise Exception('must provide a core')
|
|
44
|
+
self.core = core
|
|
45
|
+
|
|
46
|
+
if config is None:
|
|
47
|
+
config = {}
|
|
48
|
+
|
|
49
|
+
self.config = self.core.fill(self.config_schema, config)
|
|
50
|
+
self.initialize(self.config)
|
|
51
|
+
|
|
52
|
+
# Register port types
|
|
53
|
+
self.register_interface()
|
|
54
|
+
|
|
55
|
+
def initialize(self, config):
|
|
56
|
+
"""Optional hook for subclass-specific initialization."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def initial_state(self):
|
|
60
|
+
"""Return initial state values, if applicable."""
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def generate_state(config=None):
|
|
65
|
+
"""Generate static initial state for user configuration or inspection."""
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
def inputs(self):
|
|
69
|
+
"""
|
|
70
|
+
Return a dictionary mapping input port names to bigraph types.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
{'glucose': 'float', 'biomass': 'map[float]'}
|
|
74
|
+
"""
|
|
75
|
+
return {}
|
|
76
|
+
|
|
77
|
+
def outputs(self):
|
|
78
|
+
"""
|
|
79
|
+
Return a dictionary mapping output port names to bigraph types.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
{'growth_rate': 'float'}
|
|
83
|
+
"""
|
|
84
|
+
return {}
|
|
85
|
+
|
|
86
|
+
def default_inputs(self):
|
|
87
|
+
"""Generate default wire paths for inputs: {port: [port]}"""
|
|
88
|
+
return default_wires(self.inputs())
|
|
89
|
+
|
|
90
|
+
def default_outputs(self):
|
|
91
|
+
"""Generate default wire paths for outputs: {port: [port]}"""
|
|
92
|
+
return default_wires(self.outputs())
|
|
93
|
+
|
|
94
|
+
def interface(self):
|
|
95
|
+
"""
|
|
96
|
+
Return combined interface schema as a dict:
|
|
97
|
+
{
|
|
98
|
+
'inputs': {port: type, ...},
|
|
99
|
+
'outputs': {port: type, ...}
|
|
100
|
+
}
|
|
101
|
+
"""
|
|
102
|
+
return {
|
|
103
|
+
'inputs': self.inputs(),
|
|
104
|
+
'outputs': self.outputs()
|
|
105
|
+
}
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
======================
|
|
3
3
|
Parse Bigraph Notation
|
|
4
4
|
======================
|
|
5
|
+
|
|
6
|
+
Parses and renders bigraph-style type expressions used in schema definitions,
|
|
7
|
+
such as parameterized types, nested trees, merges (`|`), and unions (`~`).
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
10
|
from parsimonious.grammar import Grammar
|
|
8
11
|
from parsimonious.nodes import NodeVisitor
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
# --- Example Expressions -----------------------------------------------------
|
|
11
14
|
parameter_examples = {
|
|
12
15
|
'no-parameters': 'simple',
|
|
13
16
|
'one-parameter': 'parameterized[A]',
|
|
@@ -25,7 +28,7 @@ parameter_examples = {
|
|
|
25
28
|
'units_type': 'length^2*mass/time^1_5',
|
|
26
29
|
'nothing': '()'}
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
# --- Grammar -----------------------------------------------------------------
|
|
29
32
|
parameter_grammar = Grammar(
|
|
30
33
|
"""
|
|
31
34
|
expression = merge / union / tree / nothing
|
|
@@ -54,46 +57,30 @@ parameter_grammar = Grammar(
|
|
|
54
57
|
""")
|
|
55
58
|
|
|
56
59
|
|
|
60
|
+
# --- Visitor -----------------------------------------------------------------
|
|
57
61
|
class ParameterVisitor(NodeVisitor):
|
|
62
|
+
"""Visitor that walks a parsed tree and builds structured type expressions."""
|
|
63
|
+
|
|
58
64
|
def visit_expression(self, node, visit):
|
|
59
65
|
return visit[0]
|
|
60
66
|
|
|
61
|
-
|
|
62
67
|
def visit_union(self, node, visit):
|
|
63
68
|
head = [visit[0]]
|
|
64
|
-
tail = [
|
|
65
|
-
|
|
66
|
-
for tree in visit[1]['visit']]
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
'_union': head + tail}
|
|
70
|
-
|
|
69
|
+
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
70
|
+
return {'_union': head + tail}
|
|
71
71
|
|
|
72
72
|
def visit_merge(self, node, visit):
|
|
73
73
|
head = [visit[0]]
|
|
74
|
-
tail = [
|
|
75
|
-
tree['visit'][1]
|
|
76
|
-
for tree in visit[1]['visit']]
|
|
77
|
-
|
|
74
|
+
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
78
75
|
nodes = head + tail
|
|
79
76
|
|
|
80
|
-
if all(
|
|
81
|
-
|
|
82
|
-
for tree in nodes]):
|
|
83
|
-
|
|
84
|
-
merge = {}
|
|
77
|
+
if all(isinstance(tree, dict) for tree in nodes):
|
|
78
|
+
merged = {}
|
|
85
79
|
for tree in nodes:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return merge
|
|
89
|
-
|
|
80
|
+
merged.update(tree)
|
|
81
|
+
return merged
|
|
90
82
|
else:
|
|
91
|
-
|
|
92
|
-
for tree in head + tail:
|
|
93
|
-
values.append(tree)
|
|
94
|
-
|
|
95
|
-
return tuple(values)
|
|
96
|
-
|
|
83
|
+
return tuple(nodes)
|
|
97
84
|
|
|
98
85
|
def visit_tree(self, node, visit):
|
|
99
86
|
return visit[0]
|
|
@@ -102,34 +89,23 @@ class ParameterVisitor(NodeVisitor):
|
|
|
102
89
|
return visit[0]
|
|
103
90
|
|
|
104
91
|
def visit_group(self, node, visit):
|
|
105
|
-
|
|
106
|
-
if isinstance(
|
|
107
|
-
return visit[1]
|
|
108
|
-
else:
|
|
109
|
-
return tuple([visit[1]])
|
|
92
|
+
group_value = visit[1]
|
|
93
|
+
return group_value if isinstance(group_value, (list, tuple, dict)) else (group_value,)
|
|
110
94
|
|
|
111
95
|
def visit_nest(self, node, visit):
|
|
112
|
-
return {
|
|
113
|
-
visit[0]: visit[2]}
|
|
96
|
+
return {visit[0]: visit[2]}
|
|
114
97
|
|
|
115
98
|
def visit_type_name(self, node, visit):
|
|
116
99
|
type_name = visit[0]
|
|
117
100
|
type_parameters = visit[1]['visit']
|
|
118
|
-
if
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
else:
|
|
122
|
-
return type_name
|
|
101
|
+
if type_parameters:
|
|
102
|
+
return [type_name, type_parameters[0]]
|
|
103
|
+
return type_name
|
|
123
104
|
|
|
124
105
|
def visit_parameter_list(self, node, visit):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
for inner in visit[2]['visit']]
|
|
129
|
-
|
|
130
|
-
parameters = first_type + rest_types
|
|
131
|
-
|
|
132
|
-
return parameters
|
|
106
|
+
first = [visit[1]]
|
|
107
|
+
rest = [inner['visit'][1] for inner in visit[2]['visit']]
|
|
108
|
+
return first + rest
|
|
133
109
|
|
|
134
110
|
def visit_symbol(self, node, visit):
|
|
135
111
|
return node.text
|
|
@@ -138,67 +114,52 @@ class ParameterVisitor(NodeVisitor):
|
|
|
138
114
|
return {}
|
|
139
115
|
|
|
140
116
|
def generic_visit(self, node, visit):
|
|
141
|
-
return {
|
|
142
|
-
'node': node,
|
|
143
|
-
'visit': visit,
|
|
144
|
-
}
|
|
145
|
-
|
|
117
|
+
return {'node': node, 'visit': visit}
|
|
146
118
|
|
|
119
|
+
# --- API ---------------------------------------------------------------------
|
|
147
120
|
def parse_expression(expression):
|
|
148
|
-
|
|
121
|
+
"""
|
|
122
|
+
Parse a bigraph-style type expression into a structured Python object.
|
|
123
|
+
"""
|
|
124
|
+
parsed = parameter_grammar.parse(expression)
|
|
149
125
|
visitor = ParameterVisitor()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return type_parameters
|
|
153
|
-
|
|
126
|
+
return visitor.visit(parsed)
|
|
154
127
|
|
|
155
128
|
def is_type_expression(expression):
|
|
156
|
-
|
|
157
|
-
|
|
129
|
+
"""
|
|
130
|
+
Return True if the expression is a parameterized type list.
|
|
131
|
+
"""
|
|
132
|
+
return isinstance(expression, list) and len(expression) == 2 and isinstance(expression[1], list)
|
|
158
133
|
|
|
159
134
|
def render_expression(expression):
|
|
135
|
+
"""
|
|
136
|
+
Render a structured type expression back into a bigraph-style string.
|
|
137
|
+
"""
|
|
160
138
|
if isinstance(expression, str):
|
|
161
139
|
return expression
|
|
162
|
-
|
|
163
140
|
elif isinstance(expression, list):
|
|
164
141
|
type_name, parameters = expression
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
for parameter in parameters])
|
|
168
|
-
return f'{type_name}[{render}]'
|
|
169
|
-
|
|
142
|
+
inner = ','.join(render_expression(p) for p in parameters)
|
|
143
|
+
return f'{type_name}[{inner}]'
|
|
170
144
|
elif isinstance(expression, tuple):
|
|
171
|
-
|
|
172
|
-
render_expression(subexpression)
|
|
173
|
-
for subexpression in expression])
|
|
174
|
-
return f'({render})'
|
|
175
|
-
|
|
145
|
+
return '(' + '|'.join(render_expression(e) for e in expression) + ')'
|
|
176
146
|
elif isinstance(expression, dict):
|
|
177
|
-
parts = []
|
|
178
147
|
if '_union' in expression:
|
|
179
|
-
|
|
180
|
-
render_expression(part)
|
|
181
|
-
for part in expression['_union']]
|
|
182
|
-
return '~'.join(parts)
|
|
148
|
+
return '~'.join(render_expression(p) for p in expression['_union'])
|
|
183
149
|
else:
|
|
184
|
-
for
|
|
185
|
-
|
|
186
|
-
|
|
150
|
+
parts = [f'{k}:{render_expression(v)}' for k, v in expression.items()]
|
|
151
|
+
return f'({ "|".join(parts) })'
|
|
152
|
+
return str(expression) # fallback for unknown structures
|
|
187
153
|
|
|
188
|
-
|
|
189
|
-
return f'({inner})'
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# Test the functions
|
|
154
|
+
# --- Debugging/Testing ---------------------------------------------------------
|
|
193
155
|
def test_parse_parameters():
|
|
194
|
-
for
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
print(f'{
|
|
198
|
-
if
|
|
199
|
-
print(f' {
|
|
200
|
-
print(f' {render_expression(
|
|
201
|
-
|
|
156
|
+
"""Test parsing and rendering for all example expressions."""
|
|
157
|
+
for label, expr in parameter_examples.items():
|
|
158
|
+
parsed = parse_expression(expr)
|
|
159
|
+
print(f'{label}: {expr}')
|
|
160
|
+
if parsed:
|
|
161
|
+
print(f' Parsed: {parsed}')
|
|
162
|
+
print(f' Rendered: {render_expression(parsed)}')
|
|
202
163
|
|
|
203
164
|
if __name__ == '__main__':
|
|
204
|
-
test_parse_parameters()
|
|
165
|
+
test_parse_parameters()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
========
|
|
3
|
+
Registry
|
|
4
|
+
========
|
|
5
|
+
|
|
6
|
+
Utilities for managing registry entries, hierarchical state structures,
|
|
7
|
+
deep merging, and schema cleaning. Includes the `Registry` class for storing
|
|
8
|
+
type declarations or functions by key.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import copy
|
|
13
|
+
import collections
|
|
14
|
+
import traceback
|
|
15
|
+
import functools
|
|
16
|
+
import numpy as np
|
|
17
|
+
import pytest
|
|
18
|
+
from pprint import pformat as pf
|
|
19
|
+
|
|
20
|
+
from bigraph_schema.protocols import local_lookup_module, function_module
|
|
21
|
+
|
|
22
|
+
# --- Merge Utilities ---------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def deep_merge_copy(dct, merge_dct):
|
|
25
|
+
"""Return a deep copy of `dct` with `merge_dct` deeply merged into it."""
|
|
26
|
+
return deep_merge(copy.deepcopy(dct), merge_dct)
|
|
27
|
+
|
|
28
|
+
def deep_merge(dct, merge_dct):
|
|
29
|
+
"""
|
|
30
|
+
Deep merge `merge_dct` into `dct`, modifying `dct` in-place.
|
|
31
|
+
|
|
32
|
+
Nested dictionaries are recursively merged.
|
|
33
|
+
"""
|
|
34
|
+
if dct is None:
|
|
35
|
+
dct = {}
|
|
36
|
+
if merge_dct is None:
|
|
37
|
+
merge_dct = {}
|
|
38
|
+
if not isinstance(merge_dct, dict):
|
|
39
|
+
return merge_dct
|
|
40
|
+
|
|
41
|
+
for k, v in merge_dct.items():
|
|
42
|
+
if (k in dct and isinstance(dct[k], dict)
|
|
43
|
+
and isinstance(v, collections.abc.Mapping)):
|
|
44
|
+
deep_merge(dct[k], v)
|
|
45
|
+
else:
|
|
46
|
+
dct[k] = v
|
|
47
|
+
return dct
|
|
48
|
+
|
|
49
|
+
def validate_merge(state, dct, merge_dct):
|
|
50
|
+
"""
|
|
51
|
+
Like `deep_merge`, but raises an exception if values conflict and are not in `state`.
|
|
52
|
+
"""
|
|
53
|
+
dct = dct or {}
|
|
54
|
+
merge_dct = merge_dct or {}
|
|
55
|
+
state = state or {}
|
|
56
|
+
|
|
57
|
+
for k, v in merge_dct.items():
|
|
58
|
+
if (k in dct and isinstance(dct[k], dict)
|
|
59
|
+
and isinstance(v, collections.abc.Mapping)):
|
|
60
|
+
if k not in state:
|
|
61
|
+
state[k] = {}
|
|
62
|
+
validate_merge(state[k], dct[k], v)
|
|
63
|
+
else:
|
|
64
|
+
if k in state:
|
|
65
|
+
dct[k] = state[k]
|
|
66
|
+
elif k in dct and dct[k] != v:
|
|
67
|
+
raise Exception(f'cannot merge dicts at key "{k}":\n{dct}\n{merge_dct}')
|
|
68
|
+
else:
|
|
69
|
+
dct[k] = v
|
|
70
|
+
return dct
|
|
71
|
+
|
|
72
|
+
# --- Tree Path Utilities -----------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def establish_path(tree, path, top=None, cursor=()):
|
|
75
|
+
"""
|
|
76
|
+
Create or traverse a path in a nested dictionary (tree structure).
|
|
77
|
+
"""
|
|
78
|
+
if tree is None:
|
|
79
|
+
tree = {}
|
|
80
|
+
if top is None:
|
|
81
|
+
top = tree
|
|
82
|
+
if path is None or len(path) == 0:
|
|
83
|
+
return tree
|
|
84
|
+
if isinstance(path, str):
|
|
85
|
+
path = (path,)
|
|
86
|
+
head = path[0]
|
|
87
|
+
if head == '..':
|
|
88
|
+
if len(cursor) == 0:
|
|
89
|
+
raise Exception(f'trying to travel above the top of the tree: {path}')
|
|
90
|
+
return establish_path(top, cursor[:-1])
|
|
91
|
+
if head not in tree:
|
|
92
|
+
tree[head] = {}
|
|
93
|
+
return establish_path(tree[head], path[1:], top=top, cursor=cursor + (head,))
|
|
94
|
+
|
|
95
|
+
def set_path(tree, path, value, top=None, cursor=None):
|
|
96
|
+
"""
|
|
97
|
+
Set `value` at the given `path` in a tree-like dictionary.
|
|
98
|
+
"""
|
|
99
|
+
if value is None:
|
|
100
|
+
return None
|
|
101
|
+
if len(path) == 0:
|
|
102
|
+
return value
|
|
103
|
+
final = path[-1]
|
|
104
|
+
destination = establish_path(tree, path[:-1])
|
|
105
|
+
destination[final] = value
|
|
106
|
+
return tree
|
|
107
|
+
|
|
108
|
+
def set_star_path(tree, path, value, top=None, cursor=()):
|
|
109
|
+
"""
|
|
110
|
+
Set a value at a wildcard (`*`) path, filling in multiple keys.
|
|
111
|
+
"""
|
|
112
|
+
if tree is None:
|
|
113
|
+
tree = {}
|
|
114
|
+
if top is None:
|
|
115
|
+
top = tree
|
|
116
|
+
if isinstance(path, str):
|
|
117
|
+
path = (path,)
|
|
118
|
+
if len(path) == 0:
|
|
119
|
+
return tree
|
|
120
|
+
|
|
121
|
+
head, tail = path[0], path[1:]
|
|
122
|
+
if head == '..':
|
|
123
|
+
if len(cursor) == 0:
|
|
124
|
+
raise Exception(f'trying to travel above the top of the tree: {path}')
|
|
125
|
+
return set_star_path(top, cursor[:-1], value)
|
|
126
|
+
elif head == '*':
|
|
127
|
+
for key in value:
|
|
128
|
+
tree[key] = set_star_path({}, tail, value[key], cursor=(key,))
|
|
129
|
+
return top
|
|
130
|
+
else:
|
|
131
|
+
if len(tail) == 0:
|
|
132
|
+
tree[head] = value
|
|
133
|
+
else:
|
|
134
|
+
if head not in tree:
|
|
135
|
+
tree[head] = {}
|
|
136
|
+
set_star_path(tree[head], tail, value, top=top, cursor=cursor + (head,))
|
|
137
|
+
return top
|
|
138
|
+
|
|
139
|
+
def transform_path(tree, path, transform):
|
|
140
|
+
"""
|
|
141
|
+
Apply a transformation function to a value at a specific path in a tree.
|
|
142
|
+
"""
|
|
143
|
+
before = establish_path(tree, path)
|
|
144
|
+
after = transform(before)
|
|
145
|
+
return set_path(tree, path, after)
|
|
146
|
+
|
|
147
|
+
def hierarchy_depth(hierarchy, path=()):
|
|
148
|
+
"""
|
|
149
|
+
Recursively collect all node paths in a hierarchy.
|
|
150
|
+
"""
|
|
151
|
+
base = {}
|
|
152
|
+
for key, inner in hierarchy.items():
|
|
153
|
+
down = path + (key,)
|
|
154
|
+
if is_schema_key(key):
|
|
155
|
+
base[path] = inner
|
|
156
|
+
elif isinstance(inner, dict) and 'instance' not in inner:
|
|
157
|
+
base.update(hierarchy_depth(inner, down))
|
|
158
|
+
else:
|
|
159
|
+
base[down] = inner
|
|
160
|
+
return base
|
|
161
|
+
|
|
162
|
+
# --- Schema Tools ------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def remove_omitted(before, after, tree):
|
|
165
|
+
"""
|
|
166
|
+
Remove keys from `tree` that exist in `before` but not in `after`.
|
|
167
|
+
"""
|
|
168
|
+
if isinstance(before, dict):
|
|
169
|
+
if not isinstance(tree, dict):
|
|
170
|
+
raise Exception(f'trying to remove an entry from non-dict: {tree}')
|
|
171
|
+
if not isinstance(after, dict):
|
|
172
|
+
return after
|
|
173
|
+
for key, down in before.items():
|
|
174
|
+
if not key.startswith('_'):
|
|
175
|
+
if key not in after:
|
|
176
|
+
tree.pop(key, None)
|
|
177
|
+
else:
|
|
178
|
+
tree[key] = remove_omitted(down, after[key], tree[key])
|
|
179
|
+
return tree
|
|
180
|
+
|
|
181
|
+
def is_schema_key(key):
|
|
182
|
+
"""Check if a key is a schema key (starts with underscore)."""
|
|
183
|
+
return isinstance(key, str) and key.startswith('_')
|
|
184
|
+
|
|
185
|
+
def non_schema_keys(schema):
|
|
186
|
+
"""Return list of non-schema keys from a dictionary."""
|
|
187
|
+
return [k for k in schema if not is_schema_key(k)]
|
|
188
|
+
|
|
189
|
+
def strip_schema_keys(state):
|
|
190
|
+
"""
|
|
191
|
+
Recursively remove all schema keys from a dictionary.
|
|
192
|
+
"""
|
|
193
|
+
if isinstance(state, dict):
|
|
194
|
+
return {k: strip_schema_keys(v) for k, v in state.items() if not is_schema_key(k)}
|
|
195
|
+
return state
|
|
196
|
+
|
|
197
|
+
def type_parameter_key(schema, key):
|
|
198
|
+
"""Check if a key is a special parameter override."""
|
|
199
|
+
return key.strip('_') not in schema.get('_type_parameters', []) and key.startswith('_')
|
|
200
|
+
|
|
201
|
+
def default(type, default):
|
|
202
|
+
"""Return a schema dictionary with a type and default."""
|
|
203
|
+
return {'_type': type, '_default': default}
|
|
204
|
+
|
|
205
|
+
# --- Registry ----------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
class Registry:
|
|
208
|
+
"""
|
|
209
|
+
A registry for managing keyed objects or functions (e.g., schema types).
|
|
210
|
+
|
|
211
|
+
Supports registering items under multiple keys, deep merging for dicts,
|
|
212
|
+
and loading functions by string-qualified names.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def __init__(self, function_keys=None):
|
|
216
|
+
self.registry = {}
|
|
217
|
+
self.main_keys = set()
|
|
218
|
+
self.function_keys = set(function_keys or [])
|
|
219
|
+
|
|
220
|
+
def register(self, key, item, alternate_keys=(), strict=False):
|
|
221
|
+
"""
|
|
222
|
+
Register an item under a key (and optional alternate keys).
|
|
223
|
+
Optionally enforce strict uniqueness.
|
|
224
|
+
|
|
225
|
+
If the item is a dict and already registered, a deep merge is attempted.
|
|
226
|
+
"""
|
|
227
|
+
keys = [key] + list(alternate_keys)
|
|
228
|
+
|
|
229
|
+
for registry_key in keys:
|
|
230
|
+
if registry_key in self.registry:
|
|
231
|
+
if item != self.registry[registry_key]:
|
|
232
|
+
if strict:
|
|
233
|
+
raise Exception(
|
|
234
|
+
f'Registry conflict for {registry_key}: {self.registry[registry_key]} vs {item}')
|
|
235
|
+
elif isinstance(item, dict):
|
|
236
|
+
self.registry[registry_key] = deep_merge(self.registry[registry_key], item)
|
|
237
|
+
else:
|
|
238
|
+
self.registry[registry_key] = item
|
|
239
|
+
else:
|
|
240
|
+
self.registry[registry_key] = item
|
|
241
|
+
|
|
242
|
+
self.main_keys.add(key)
|
|
243
|
+
|
|
244
|
+
def register_function(self, function):
|
|
245
|
+
"""
|
|
246
|
+
Register a function by name or function object.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
(function_name, module_key)
|
|
250
|
+
"""
|
|
251
|
+
if isinstance(function, str):
|
|
252
|
+
module_key = function
|
|
253
|
+
found = self.find(module_key) or local_lookup_module(module_key)
|
|
254
|
+
if found is None:
|
|
255
|
+
raise Exception(f'Function "{module_key}" not found for type data')
|
|
256
|
+
elif inspect.isfunction(function):
|
|
257
|
+
found = function
|
|
258
|
+
module_key = function_module(found)
|
|
259
|
+
else:
|
|
260
|
+
raise TypeError(f'Unsupported function type: {type(function)}')
|
|
261
|
+
|
|
262
|
+
function_name = module_key.split('.')[-1]
|
|
263
|
+
self.register(function_name, found)
|
|
264
|
+
self.register(module_key, found)
|
|
265
|
+
|
|
266
|
+
return function_name, module_key
|
|
267
|
+
|
|
268
|
+
def register_multiple(self, schemas, strict=False):
|
|
269
|
+
"""Register multiple schemas from a dictionary of {key: item}."""
|
|
270
|
+
for key, schema in schemas.items():
|
|
271
|
+
self.register(key, schema, strict=strict)
|
|
272
|
+
|
|
273
|
+
def find(self, key):
|
|
274
|
+
"""Retrieve an item from the registry by key (or None)."""
|
|
275
|
+
return self.registry.get(key)
|
|
276
|
+
|
|
277
|
+
def access(self, key):
|
|
278
|
+
"""Alias for `find()`."""
|
|
279
|
+
return self.find(key)
|
|
280
|
+
|
|
281
|
+
def list(self):
|
|
282
|
+
"""List all primary (non-alternate) keys in the registry."""
|
|
283
|
+
return list(self.main_keys)
|
|
284
|
+
|
|
285
|
+
def validate(self, item):
|
|
286
|
+
"""Stub for validation logic (currently always True)."""
|
|
287
|
+
return True
|
|
@@ -399,24 +399,21 @@ def apply_map(schema, current, update, top_schema, top_state, path, core=None):
|
|
|
399
399
|
del result[remove_key]
|
|
400
400
|
|
|
401
401
|
elif key not in current:
|
|
402
|
-
# This supports adding without the '_add' key, if the key is not in
|
|
402
|
+
# # This supports adding without the '_add' key, if the key is not in
|
|
403
403
|
# the state
|
|
404
|
-
_, generated_state, top_schema, top_state = core._generate_recur(
|
|
405
|
-
value_type,
|
|
406
|
-
update_value,
|
|
407
|
-
top_schema=top_schema,
|
|
408
|
-
top_state=top_state,
|
|
409
|
-
path=path + [key])
|
|
410
|
-
|
|
411
|
-
result[key] = generated_state
|
|
412
|
-
|
|
413
|
-
# generated_schema, generated_state = core.generate(
|
|
404
|
+
# _, generated_state, top_schema, top_state = core._generate_recur(
|
|
414
405
|
# value_type,
|
|
415
|
-
# update_value
|
|
406
|
+
# update_value,
|
|
407
|
+
# top_schema=top_schema,
|
|
408
|
+
# top_state=top_state,
|
|
409
|
+
# path=path + [key])
|
|
416
410
|
|
|
417
411
|
# result[key] = generated_state
|
|
418
412
|
|
|
413
|
+
# # Or raise an exception
|
|
419
414
|
# raise Exception(f'trying to update a key that does not exist:\n value: {current}\n update: {update}')
|
|
415
|
+
|
|
416
|
+
pass
|
|
420
417
|
else:
|
|
421
418
|
result[key] = core.apply_update(
|
|
422
419
|
value_type,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bigraph-schema"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.64"
|
|
4
4
|
description = "A serializable type schema for compositional systems biology"
|
|
5
5
|
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.7"
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "Eran Agmon"},
|
|
9
|
+
{name = "Ryan Spangler"}
|
|
10
|
+
]
|
|
6
11
|
dependencies = [
|
|
7
12
|
"fire",
|
|
8
13
|
"numpy",
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
====
|
|
3
|
-
Edge
|
|
4
|
-
====
|
|
5
|
-
|
|
6
|
-
Base class for all edges in the bigraph schema.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
class Edge:
|
|
10
|
-
config_schema = {}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def __init__(self, config=None, core=None):
|
|
14
|
-
if core is None:
|
|
15
|
-
raise Exception('must provide a core')
|
|
16
|
-
|
|
17
|
-
self.core = core
|
|
18
|
-
|
|
19
|
-
if config is None:
|
|
20
|
-
config = {}
|
|
21
|
-
|
|
22
|
-
self.config = self.core.fill(
|
|
23
|
-
self.config_schema,
|
|
24
|
-
config)
|
|
25
|
-
|
|
26
|
-
self.initialize(self.config)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def initialize(self, config):
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def initial_state(self):
|
|
34
|
-
"""The initial state of the edge, which is passed to the core."""
|
|
35
|
-
return {}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def generate_state(config=None): # TODO -- config could have a schema
|
|
40
|
-
"""
|
|
41
|
-
Generate an initial state statically, without any instance-specific config.
|
|
42
|
-
This could be used to create user-configured initial states based on the Edge's requirements.
|
|
43
|
-
"""
|
|
44
|
-
return {}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def inputs(self):
|
|
48
|
-
return {}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def outputs(self):
|
|
52
|
-
return {}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def interface(self):
|
|
56
|
-
"""Returns the schema for this type"""
|
|
57
|
-
return {
|
|
58
|
-
'inputs': self.inputs(),
|
|
59
|
-
'outputs': self.outputs()}
|
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
========
|
|
3
|
-
Registry
|
|
4
|
-
========
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import inspect
|
|
8
|
-
import copy
|
|
9
|
-
import collections
|
|
10
|
-
import pytest
|
|
11
|
-
import traceback
|
|
12
|
-
import functools
|
|
13
|
-
import numpy as np
|
|
14
|
-
|
|
15
|
-
from pprint import pformat as pf
|
|
16
|
-
|
|
17
|
-
from bigraph_schema.protocols import local_lookup_module, function_module
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def deep_merge_copy(dct, merge_dct):
|
|
22
|
-
return deep_merge(copy.deepcopy(dct), merge_dct)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def deep_merge(dct, merge_dct):
|
|
26
|
-
"""
|
|
27
|
-
Recursive dict merge
|
|
28
|
-
|
|
29
|
-
This mutates dct - the contents of merge_dct are added to dct (which is
|
|
30
|
-
also returned).
|
|
31
|
-
|
|
32
|
-
If you want to keep dct you could use deep_merge_copy
|
|
33
|
-
"""
|
|
34
|
-
if dct is None:
|
|
35
|
-
dct = {}
|
|
36
|
-
if merge_dct is None:
|
|
37
|
-
merge_dct = {}
|
|
38
|
-
if not isinstance(merge_dct, dict):
|
|
39
|
-
return merge_dct
|
|
40
|
-
|
|
41
|
-
for k, v in merge_dct.items():
|
|
42
|
-
if (k in dct and isinstance(dct[k], dict)
|
|
43
|
-
and isinstance(merge_dct[k], collections.abc.Mapping)):
|
|
44
|
-
deep_merge(dct[k], merge_dct[k])
|
|
45
|
-
else:
|
|
46
|
-
dct[k] = merge_dct[k]
|
|
47
|
-
return dct
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def validate_merge(state, dct, merge_dct):
|
|
51
|
-
"""
|
|
52
|
-
Recursive dict merge
|
|
53
|
-
|
|
54
|
-
This mutates dct - the contents of merge_dct are added to dct (which is
|
|
55
|
-
also returned).
|
|
56
|
-
|
|
57
|
-
If you want to keep dct you could call it like
|
|
58
|
-
deep_merge(copy.deepcopy(dct), merge_dct)
|
|
59
|
-
"""
|
|
60
|
-
dct = dct or {}
|
|
61
|
-
merge_dct = merge_dct or {}
|
|
62
|
-
state = state or {}
|
|
63
|
-
|
|
64
|
-
for k, v in merge_dct.items():
|
|
65
|
-
if (k in dct and isinstance(dct[k], dict)
|
|
66
|
-
and isinstance(merge_dct[k], collections.abc.Mapping)):
|
|
67
|
-
if k not in state:
|
|
68
|
-
state[k] = {}
|
|
69
|
-
|
|
70
|
-
validate_merge(
|
|
71
|
-
state[k],
|
|
72
|
-
dct[k],
|
|
73
|
-
merge_dct[k])
|
|
74
|
-
else:
|
|
75
|
-
if k in state:
|
|
76
|
-
dct[k] = state[k]
|
|
77
|
-
elif k in dct:
|
|
78
|
-
if dct[k] != merge_dct[k]:
|
|
79
|
-
raise Exception(f'cannot merge dicts at key "{k}":\n{dct}\n{merge_dct}')
|
|
80
|
-
else:
|
|
81
|
-
dct[k] = merge_dct[k]
|
|
82
|
-
return dct
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def establish_path(tree, path, top=None, cursor=()):
|
|
86
|
-
"""
|
|
87
|
-
Given a tree and a path in the tree that may or may not yet exist, add
|
|
88
|
-
nodes along the path and return the final node which is now at the given
|
|
89
|
-
path.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
- tree: the tree we are establishing a path in
|
|
93
|
-
- path: where the new subtree will be located in the tree
|
|
94
|
-
- top: (None) a reference to the top of the tree
|
|
95
|
-
- cursor: (()) the current location we are visiting in the tree
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
- node: the new node of the tree that exists at the given path
|
|
99
|
-
"""
|
|
100
|
-
|
|
101
|
-
if tree is None:
|
|
102
|
-
tree = {}
|
|
103
|
-
|
|
104
|
-
if top is None:
|
|
105
|
-
top = tree
|
|
106
|
-
if path is None or path == ():
|
|
107
|
-
return tree
|
|
108
|
-
elif len(path) == 0:
|
|
109
|
-
return tree
|
|
110
|
-
else:
|
|
111
|
-
if isinstance(path, str):
|
|
112
|
-
path = (path,)
|
|
113
|
-
|
|
114
|
-
head = path[0]
|
|
115
|
-
if head == '..':
|
|
116
|
-
if len(cursor) == 0:
|
|
117
|
-
raise Exception(
|
|
118
|
-
f'trying to travel above the top of the tree: {path}')
|
|
119
|
-
else:
|
|
120
|
-
return establish_path(
|
|
121
|
-
top,
|
|
122
|
-
cursor[:-1])
|
|
123
|
-
else:
|
|
124
|
-
if head not in tree:
|
|
125
|
-
tree[head] = {}
|
|
126
|
-
return establish_path(
|
|
127
|
-
tree[head],
|
|
128
|
-
path[1:],
|
|
129
|
-
top=top,
|
|
130
|
-
cursor=tuple(cursor) + (head,))
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def set_path(tree, path, value, top=None, cursor=None):
|
|
134
|
-
"""
|
|
135
|
-
Given a tree, a path, and a value, sets the location
|
|
136
|
-
in the tree corresponding to the path to the given value
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
- tree: the tree we are setting a value in
|
|
140
|
-
- path: where the new value will be located in the tree
|
|
141
|
-
- value: the value to set at the given path in the tree
|
|
142
|
-
- top: (None) a reference to the top of the tree
|
|
143
|
-
- cursor: (()) the current location we are visiting in the tree
|
|
144
|
-
|
|
145
|
-
Returns:
|
|
146
|
-
- node: the new node of the tree that exists at the given path
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
if value is None:
|
|
150
|
-
return None
|
|
151
|
-
if len(path) == 0:
|
|
152
|
-
return value
|
|
153
|
-
|
|
154
|
-
final = path[-1]
|
|
155
|
-
towards = path[:-1]
|
|
156
|
-
destination = establish_path(tree, towards)
|
|
157
|
-
destination[final] = value
|
|
158
|
-
return tree
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def set_star_path(tree, path, value, top=None, cursor=()):
|
|
162
|
-
if tree is None:
|
|
163
|
-
tree = {}
|
|
164
|
-
if top is None:
|
|
165
|
-
top = tree
|
|
166
|
-
if path is None or len(path) == 0:
|
|
167
|
-
return tree
|
|
168
|
-
else:
|
|
169
|
-
if isinstance(path, str):
|
|
170
|
-
path = (path,)
|
|
171
|
-
|
|
172
|
-
head = path[0]
|
|
173
|
-
tail = path[1:]
|
|
174
|
-
|
|
175
|
-
if head == '..':
|
|
176
|
-
if len(cursor) == 0:
|
|
177
|
-
raise Exception(
|
|
178
|
-
f'trying to travel above the top of the tree: {path}')
|
|
179
|
-
else:
|
|
180
|
-
return set_star_path(
|
|
181
|
-
top,
|
|
182
|
-
cursor[:-1],
|
|
183
|
-
value)
|
|
184
|
-
elif head == '*':
|
|
185
|
-
for key in value:
|
|
186
|
-
tree[key] = set_star_path(
|
|
187
|
-
{},
|
|
188
|
-
tail,
|
|
189
|
-
value[key],
|
|
190
|
-
cursor=(key,))
|
|
191
|
-
return top
|
|
192
|
-
else:
|
|
193
|
-
if len(tail) == 0:
|
|
194
|
-
tree[head] = value
|
|
195
|
-
return top
|
|
196
|
-
else:
|
|
197
|
-
if head not in tree:
|
|
198
|
-
tree[head] = {}
|
|
199
|
-
return set_star_path(
|
|
200
|
-
tree[head],
|
|
201
|
-
tail,
|
|
202
|
-
value,
|
|
203
|
-
top=top,
|
|
204
|
-
cursor=tuple(cursor) + (head,))
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def transform_path(tree, path, transform):
|
|
208
|
-
"""
|
|
209
|
-
Given a tree, a path, and a transform (function), mutate the tree by
|
|
210
|
-
replacing the subtree at the path by whatever is returned from applying the
|
|
211
|
-
transform to the existing value.
|
|
212
|
-
|
|
213
|
-
Args:
|
|
214
|
-
- tree: the tree we are setting a value in
|
|
215
|
-
- path: where the new value will be located in the tree
|
|
216
|
-
- transform: the function to apply to whatever currently lives at the given
|
|
217
|
-
path in the tree
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
- node: the node of the tree that exists at the given path
|
|
221
|
-
"""
|
|
222
|
-
before = establish_path(tree, path)
|
|
223
|
-
after = transform(before)
|
|
224
|
-
|
|
225
|
-
return set_path(tree, path, after)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def hierarchy_depth(hierarchy, path=()):
|
|
229
|
-
"""
|
|
230
|
-
Create a mapping of every path in the hierarchy to the node living at
|
|
231
|
-
that path in the hierarchy.
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
base = {}
|
|
235
|
-
|
|
236
|
-
for key, inner in hierarchy.items():
|
|
237
|
-
down = tuple(path + (key,))
|
|
238
|
-
if is_schema_key(key):
|
|
239
|
-
base[path] = inner
|
|
240
|
-
elif isinstance(inner, dict) and 'instance' not in inner:
|
|
241
|
-
base.update(hierarchy_depth(inner, down))
|
|
242
|
-
else:
|
|
243
|
-
base[down] = inner
|
|
244
|
-
|
|
245
|
-
return base
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def remove_omitted(before, after, tree):
|
|
249
|
-
"""
|
|
250
|
-
Removes anything in tree that was in before but not in after
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
if isinstance(before, dict):
|
|
254
|
-
if not isinstance(tree, dict):
|
|
255
|
-
raise Exception(
|
|
256
|
-
f'trying to remove an entry from something that is not a dict: {tree}')
|
|
257
|
-
|
|
258
|
-
if not isinstance(after, dict):
|
|
259
|
-
return after
|
|
260
|
-
|
|
261
|
-
for key, down in before.items():
|
|
262
|
-
if not key.startswith('_'):
|
|
263
|
-
if key not in after:
|
|
264
|
-
if key in tree:
|
|
265
|
-
del tree[key]
|
|
266
|
-
else:
|
|
267
|
-
tree[key] = remove_omitted(
|
|
268
|
-
down,
|
|
269
|
-
after[key],
|
|
270
|
-
tree[key])
|
|
271
|
-
|
|
272
|
-
return tree
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def is_schema_key(key):
|
|
276
|
-
return isinstance(key, str) and key.startswith('_')
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def non_schema_keys(schema):
|
|
280
|
-
"""
|
|
281
|
-
Filters out schema keys with the underscore prefix
|
|
282
|
-
"""
|
|
283
|
-
return [
|
|
284
|
-
element
|
|
285
|
-
for element in schema.keys()
|
|
286
|
-
if not is_schema_key(element)]
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def strip_schema_keys(state):
|
|
290
|
-
"""
|
|
291
|
-
remove schema keys from a state dictionary, including nested dictionaries
|
|
292
|
-
"""
|
|
293
|
-
if isinstance(state, dict):
|
|
294
|
-
output = {}
|
|
295
|
-
for key, value in state.items():
|
|
296
|
-
if not is_schema_key(key):
|
|
297
|
-
output[key] = strip_schema_keys(value)
|
|
298
|
-
else:
|
|
299
|
-
output = state
|
|
300
|
-
return output
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def type_parameter_key(schema, key):
|
|
304
|
-
return key.strip('_') not in schema.get('_type_parameters', []) and key.startswith('_')
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def default(type, default):
|
|
308
|
-
return {
|
|
309
|
-
'_type': type,
|
|
310
|
-
'_default': default}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
class Registry(object):
|
|
314
|
-
"""
|
|
315
|
-
A Registry holds a collection of functions or objects
|
|
316
|
-
"""
|
|
317
|
-
|
|
318
|
-
def __init__(self, function_keys=None):
|
|
319
|
-
function_keys = function_keys or []
|
|
320
|
-
self.registry = {}
|
|
321
|
-
self.main_keys = set([])
|
|
322
|
-
self.function_keys = set(function_keys)
|
|
323
|
-
|
|
324
|
-
def register(self, key, item, alternate_keys=tuple(), strict=False):
|
|
325
|
-
"""
|
|
326
|
-
Add an item to the registry.
|
|
327
|
-
|
|
328
|
-
Args:
|
|
329
|
-
- key: Item key.
|
|
330
|
-
- item: The item to add.
|
|
331
|
-
- alternate_keys: Additional keys under which to register the item.
|
|
332
|
-
These keys will not be included in the list returned by
|
|
333
|
-
``Registry.list()``. This may be useful if you want to be able to
|
|
334
|
-
look up an item in the registry under multiple keys.
|
|
335
|
-
- strict (bool): Disallow re-registration, overriding existing keys.
|
|
336
|
-
False by default.
|
|
337
|
-
"""
|
|
338
|
-
|
|
339
|
-
# check that registered function have the required function keys
|
|
340
|
-
# TODO -- make this work to check the function keys
|
|
341
|
-
if callable(item) and self.function_keys:
|
|
342
|
-
sig = inspect.signature(item)
|
|
343
|
-
sig_keys = set(sig.parameters.keys())
|
|
344
|
-
# assert all(
|
|
345
|
-
# key in self.function_keys for key in sig_keys), f"Function '{item.__name__}' keys {sig_keys} are not all " \
|
|
346
|
-
# f"in the function_keys {self.function_keys}"
|
|
347
|
-
|
|
348
|
-
keys = [key]
|
|
349
|
-
keys.extend(alternate_keys)
|
|
350
|
-
for registry_key in keys:
|
|
351
|
-
if registry_key in self.registry:
|
|
352
|
-
if item != self.registry[registry_key]:
|
|
353
|
-
if strict:
|
|
354
|
-
raise Exception(
|
|
355
|
-
'registry already contains an entry for {}: {} --> {}'.format(
|
|
356
|
-
registry_key, self.registry[key], item))
|
|
357
|
-
elif isinstance(item, dict):
|
|
358
|
-
self.registry[registry_key] = deep_merge(
|
|
359
|
-
self.registry[registry_key],
|
|
360
|
-
item)
|
|
361
|
-
else:
|
|
362
|
-
self.registry[registry_key] = item
|
|
363
|
-
|
|
364
|
-
else:
|
|
365
|
-
self.registry[registry_key] = item
|
|
366
|
-
self.main_keys.add(key)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
def register_function(self, function):
|
|
370
|
-
if isinstance(function, str):
|
|
371
|
-
module_key = function
|
|
372
|
-
found = self.find(module_key)
|
|
373
|
-
|
|
374
|
-
if found is None:
|
|
375
|
-
found = local_lookup_module(
|
|
376
|
-
module_key)
|
|
377
|
-
|
|
378
|
-
if found is None:
|
|
379
|
-
raise Exception(
|
|
380
|
-
f'function "{module_key}" not found for type data')
|
|
381
|
-
|
|
382
|
-
elif inspect.isfunction(function):
|
|
383
|
-
found = function
|
|
384
|
-
module_key = function_module(found)
|
|
385
|
-
|
|
386
|
-
function_name = module_key.split('.')[-1]
|
|
387
|
-
self.register(function_name, found)
|
|
388
|
-
self.register(module_key, found)
|
|
389
|
-
|
|
390
|
-
return function_name, module_key
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
def register_multiple(self, schemas, strict=False):
|
|
394
|
-
for key, schema in schemas.items():
|
|
395
|
-
self.register(key, schema, strict=strict)
|
|
396
|
-
|
|
397
|
-
def find(self, key):
|
|
398
|
-
return self.registry.get(key)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
def access(self, key):
|
|
402
|
-
"""
|
|
403
|
-
get an item by key from the registry.
|
|
404
|
-
"""
|
|
405
|
-
|
|
406
|
-
return self.find(key)
|
|
407
|
-
|
|
408
|
-
def list(self):
|
|
409
|
-
return list(self.main_keys)
|
|
410
|
-
|
|
411
|
-
def validate(self, item):
|
|
412
|
-
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bigraph-schema-0.0.62 → bigraph-schema-0.0.64}/bigraph_schema.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|