bigraph-schema 0.0.63__tar.gz → 0.0.65__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.

Files changed (39) hide show
  1. {bigraph-schema-0.0.63/bigraph_schema.egg-info → bigraph-schema-0.0.65}/PKG-INFO +1 -1
  2. bigraph-schema-0.0.65/bigraph_schema/edge.py +105 -0
  3. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/parse.py +55 -94
  4. bigraph-schema-0.0.65/bigraph_schema/registry.py +287 -0
  5. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/type_functions.py +9 -12
  6. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65/bigraph_schema.egg-info}/PKG-INFO +1 -1
  7. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/pyproject.toml +1 -1
  8. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/setup.py +1 -1
  9. bigraph-schema-0.0.63/bigraph_schema/edge.py +0 -78
  10. bigraph-schema-0.0.63/bigraph_schema/registry.py +0 -412
  11. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/.github/workflows/notebook_to_html.yml +0 -0
  12. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/.github/workflows/pytest.yml +0 -0
  13. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/.gitignore +0 -0
  14. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/AUTHORS.md +0 -0
  15. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/CLA.md +0 -0
  16. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/CODE_OF_CONDUCT.md +0 -0
  17. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/CONTRIBUTING.md +0 -0
  18. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/LICENSE +0 -0
  19. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/README.md +0 -0
  20. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/__init__.py +0 -0
  21. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/protocols.py +0 -0
  22. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/tests.py +0 -0
  23. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/type_system.py +0 -0
  24. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/type_system_adjunct.py +0 -0
  25. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/units.py +0 -0
  26. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema/utilities.py +0 -0
  27. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema.egg-info/SOURCES.txt +0 -0
  28. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema.egg-info/dependency_links.txt +0 -0
  29. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema.egg-info/requires.txt +0 -0
  30. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/bigraph_schema.egg-info/top_level.txt +0 -0
  31. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/notebooks/core.ipynb +0 -0
  32. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/notebooks/demo.ipynb +0 -0
  33. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/notebooks/images/place-link.png +0 -0
  34. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/notebooks/images/reaction-after.png +0 -0
  35. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/notebooks/images/reaction-before.png +0 -0
  36. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/notebooks/images/redex-reactum.png +0 -0
  37. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/pytest.ini +0 -0
  38. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/release.sh +0 -0
  39. {bigraph-schema-0.0.63 → bigraph-schema-0.0.65}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bigraph-schema
3
- Version: 0.0.63
3
+ Version: 0.0.65
4
4
  Summary: A serializable type schema for compositional systems biology
5
5
  Home-page: https://github.com/vivarium-collective/bigraph-schema
6
6
  Author: Eran Agmon, Ryan Spangler
@@ -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
- tree['visit'][1]
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
- isinstance(tree, dict)
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
- merge.update(tree)
87
-
88
- return merge
89
-
80
+ merged.update(tree)
81
+ return merged
90
82
  else:
91
- values = []
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
- # return visit[1]
106
- if isinstance(visit[1], (list, tuple, dict)):
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 len(type_parameters) > 0:
119
- type_parameters = type_parameters[0]
120
- return [type_name, type_parameters]
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
- first_type = [visit[1]]
126
- rest_types = [
127
- inner['visit'][1]
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
- parse = parameter_grammar.parse(expression)
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
- type_parameters = visitor.visit(parse)
151
-
152
- return type_parameters
153
-
126
+ return visitor.visit(parsed)
154
127
 
155
128
  def is_type_expression(expression):
156
- return len(expression) == 2 and isinstance(expression[1], list)
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
- render = ','.join([
166
- render_expression(parameter)
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
- render = '|'.join([
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
- parts = [
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 key, tree in expression.items():
185
- render = render_expression(tree)
186
- parts.append(f'{key}:{render}')
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
- inner = '|'.join(parts)
189
- return f'({inner})'
190
-
191
-
192
- # Test the functions
154
+ # --- Debugging/Testing ---------------------------------------------------------
193
155
  def test_parse_parameters():
194
- for key, example in parameter_examples.items():
195
- types = parse_expression(example)
196
-
197
- print(f'{key}: {example}')
198
- if types:
199
- print(f' {types}')
200
- print(f' {render_expression(types)}')
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bigraph-schema
3
- Version: 0.0.63
3
+ Version: 0.0.65
4
4
  Summary: A serializable type schema for compositional systems biology
5
5
  Home-page: https://github.com/vivarium-collective/bigraph-schema
6
6
  Author: Eran Agmon, Ryan Spangler
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bigraph-schema"
3
- version = "0.0.63"
3
+ version = "0.0.65"
4
4
  description = "A serializable type schema for compositional systems biology"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.7"
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
 
4
- VERSION = '0.0.63'
4
+ VERSION = '0.0.65'
5
5
 
6
6
 
7
7
  with open("README.md", "r") as readme:
@@ -1,78 +0,0 @@
1
- """
2
- ====
3
- Edge
4
- ====
5
-
6
- Base class for all edges in the bigraph schema.
7
- """
8
-
9
- def default_wires(schema):
10
- return {
11
- key: [key]
12
- for key in schema}
13
-
14
-
15
- class Edge:
16
- config_schema = {}
17
-
18
-
19
- def __init__(self, config=None, core=None):
20
- if core is None:
21
- raise Exception('must provide a core')
22
-
23
- self.core = core
24
-
25
- if config is None:
26
- config = {}
27
-
28
- self.config = self.core.fill(
29
- self.config_schema,
30
- config)
31
-
32
- self.initialize(self.config)
33
-
34
-
35
- def initialize(self, config):
36
- pass
37
-
38
-
39
- def initial_state(self):
40
- """The initial state of the edge, which is passed to the core."""
41
- return {}
42
-
43
-
44
- @staticmethod
45
- def generate_state(config=None): # TODO -- config could have a schema
46
- """
47
- Generate an initial state statically, without any instance-specific config.
48
- This could be used to create user-configured initial states based on the Edge's requirements.
49
- """
50
- return {}
51
-
52
-
53
- def inputs(self):
54
- return {}
55
-
56
-
57
- def outputs(self):
58
- return {}
59
-
60
-
61
- def default_inputs(self):
62
- schema = self.inputs()
63
- wires = default_wires(schema)
64
- return wires
65
-
66
-
67
- def default_outputs(self):
68
- schema = self.outputs()
69
- wires = default_wires(schema)
70
- return wires
71
-
72
-
73
- def interface(self):
74
- """Returns the schema for this type"""
75
- return {
76
- 'inputs': self.inputs(),
77
- 'outputs': self.outputs()}
78
-
@@ -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