Skip to content

Commit 667bb29

Browse files
committed
Added subcomponents
1 parent 3916877 commit 667bb29

File tree

9 files changed

+188
-75
lines changed

9 files changed

+188
-75
lines changed

ngcsimlib/_src/compartment/compartment.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def root(self):
4545

4646
@property
4747
def targeted(self) -> bool:
48-
return self._target != self._root_target
48+
return not isinstance(self._target, str) or (self._target != self._root_target)
4949

5050
@property
5151
def fixed(self) -> bool:
@@ -54,11 +54,12 @@ def fixed(self) -> bool:
5454
"""
5555
return self._fixed
5656

57-
def _setup(self, objName, compName, path):
57+
def _setup(self, compName, path):
5858
self.name = compName
59-
self._root_target = path + ":" + objName + ":" + self.name
60-
self._target = self._root_target
61-
self.set(self._initial_value)
59+
self._root_target = path + ":" + self.name
60+
if self.target is None:
61+
self._target = self._root_target
62+
self.set(self._initial_value)
6263
gState.add_compartment(self)
6364

6465
def set(self, value: T) -> None:
@@ -119,7 +120,8 @@ def _to_ast(self, node, ctx):
119120
return self.target._to_ast(node, ctx)
120121

121122
def __rrshift__(self, other):
122-
gcm.current_context.add_connection(other, self)
123+
if gcm.current_context is not None:
124+
gcm.current_context.add_connection(other, self)
123125
self.target = other
124126

125127
def __rshift__(self, other):

ngcsimlib/_src/context/context.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from ngcsimlib._src.modules.modules_manager import modules_manager as modManager
77
from ngcsimlib._src.operations.BaseOp import BaseOp
88

9+
from ngcsimlib._src.global_state.manager import global_state_manager
10+
911
from enum import Enum
1012
import os, shutil
1113

@@ -84,6 +86,7 @@ def recompile(self):
8486
"""
8587
priorities = {}
8688

89+
8790
for objectType in self.objects.keys():
8891
_objs = self.get_objects_by_type(objectType)
8992
for objName, obj in _objs.items():
@@ -93,6 +96,7 @@ def recompile(self):
9396

9497
if p not in priorities:
9598
priorities[p] = []
99+
96100
priorities[p].append(obj)
97101

98102

@@ -255,10 +259,11 @@ def save_to_json(self, directory: str, model_name: Union[str, None] = None,
255259

256260
path = make_unique_path(directory, model_name)
257261

258-
contextMeta = {"types": list(self.objects.keys())}
262+
contextMeta = {"types": list(self.objects.keys()),
263+
"path": self.path}
259264

260265
with open(f"{path}/contextData.json", "w") as f:
261-
f.write(json.dumps(contextMeta))
266+
f.write(json.dumps(contextMeta, indent=4))
262267

263268
for _type in self.objects.keys():
264269
type_path = f"{path}/{make_safe_filename(_type)}"
@@ -307,11 +312,11 @@ def load(directory: str, module_name: str):
307312
"existing context")
308313
return gcm.get_context(gcm.append_path(module_name))
309314

310-
with Context(module_name) as ctx:
311-
path = directory + "/" + module_name
312-
with open(f"{path}/contextData.json", "r") as f:
313-
metaData = json.load(f)
315+
path = directory + "/" + module_name
316+
with open(f"{path}/contextData.json", "r") as f:
317+
metaData = json.load(f)
314318

319+
with Context(metaData.get("path", module_name)) as ctx:
315320
delayed_load = []
316321

317322
for _type in metaData["types"]:

ngcsimlib/_src/context/contextAwareObject.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import json
12
from typing import Dict, Any
23

34
from .context_manager import global_context_manager as gcm
45
from .contextAwareObjectMeta import ContextAwareObjectMeta
56
from ngcsimlib._src.parser.utils import compileObject
7+
from ngcsimlib._src.logger import warn
68

79
class ContextAwareObject(object, metaclass=ContextAwareObjectMeta):
810
"""
@@ -22,8 +24,24 @@ def to_json(self) -> Dict[str, Any]:
2224
2325
Returns: A dictionary of data that can be serialized by JSON.
2426
"""
25-
data = {"args": self._args,
26-
"kwargs": self._kwargs}
27+
safe_args = []
28+
for a in self._args:
29+
try:
30+
json.dumps(a)
31+
safe_args.append(a)
32+
except:
33+
warn(f"In {self.name}, unable to serialize positional argument {a}")
34+
35+
safe_kwargs = {}
36+
for key, val in self._kwargs.items():
37+
try:
38+
json.dumps(val)
39+
safe_kwargs[key] = val
40+
except:
41+
warn(f"In {self.name}, unable to serialize keyword argument {key}: {val}")
42+
43+
data = {"args": safe_args,
44+
"kwargs": safe_kwargs}
2745
return data
2846

2947
def compile(self):
Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,68 @@
1+
import inspect
2+
13
from ngcsimlib.logger import error, warn
24
from .context_manager import global_context_manager as gcm
35
from collections.abc import Iterable
46

7+
def extract_name(cls, args, kwargs):
8+
init = cls.__init__
9+
sig = inspect.signature(init)
10+
bound = sig.bind_partial(None, *args, **kwargs)
11+
bound.apply_defaults()
12+
13+
if "name" in bound.arguments:
14+
return bound.arguments["name"]
15+
return None
16+
517
class ContextAwareObjectMeta(type):
18+
def __new__(cls, name, bases, attrs):
19+
if '__enter__' not in attrs:
20+
def __enter__(self):
21+
gcm.step(self._inferred_name, catch_empty=False)
22+
23+
attrs['__enter__'] = __enter__
24+
25+
if '__exit__' not in attrs:
26+
def __exit__(self, type, value, traceback):
27+
gcm.step_back()
28+
29+
attrs['__exit__'] = __exit__
30+
31+
32+
return super().__new__(cls, name, bases, attrs)
33+
634
"""
735
This is the metaclass for objects that want to interact with the context
836
they were created in. Generally use the base class "ContextAwareObject" over
937
this metaclass.
1038
"""
1139
def __call__(cls, *args, **kwargs):
12-
obj = super().__call__(*args, **kwargs)
40+
obj = cls.__new__(cls, *args, **kwargs)
41+
obj._inferred_name = extract_name(cls, args, kwargs)
1342

14-
obj._args = args
15-
obj._kwargs = kwargs
43+
with obj:
44+
cls.__init__(obj, *args, **kwargs)
1645

17-
if not hasattr(obj, 'name'):
18-
error(f"Created context objects must have a name. "
19-
f"Error occurred when making an object of class {cls.__name__}")
46+
obj._args = args
47+
obj._kwargs = kwargs
2048

21-
contextRef = gcm.current_context
22-
if contextRef is None:
23-
warn(f"An instance of a context aware object was initialized while "
24-
f"the current context is None. Did you forget to place it in "
25-
f"a \"with\" block?")
26-
return obj
49+
if not hasattr(obj, 'name'):
50+
error(f"Created context objects must have a name. "
51+
f"Error occurred when making an object of class {cls.__name__}")
52+
53+
# if contextRef is None:
54+
# warn(f"An instance of a context aware object was initialized while "
55+
# f"the current context is None. Did you forget to place it in "
56+
# f"a \"with\" block?")
57+
# return obj
2758

28-
contextRef.registerObj(obj)
2959

30-
if hasattr(obj, "compartments") and isinstance(obj.compartments, Iterable) and not isinstance(obj.compartments, (str, bytes)):
31-
for (comp_name, comp) in obj.compartments:
32-
if hasattr(comp, "_setup") and callable(comp._setup):
33-
comp._setup(obj.name, comp_name, gcm.current_path)
60+
if hasattr(obj, "compartments") and isinstance(obj.compartments, Iterable) and not isinstance(obj.compartments, (str, bytes)):
61+
for (comp_name, comp) in obj.compartments:
62+
if hasattr(comp, "_setup") and callable(comp._setup):
63+
comp._setup(comp_name, gcm.current_path)
3464

65+
contextRef = gcm.current_context
66+
if contextRef is not None:
67+
contextRef.registerObj(obj)
3568
return obj

ngcsimlib/_src/context/context_manager.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,19 @@ def clear(self):
4444
"""
4545
self.__contexts.clear()
4646

47-
def step(self, location: str) -> bool:
47+
def step(self, location: str, catch_empty=True) -> bool:
4848
"""
4949
Steps one step forward in the hierarchy of contexts
5050
Args:
5151
location: The path to step into
52+
catch_empty: warn if stepping into a location that does not have a context
5253
5354
Returns: if there is a registered context at the path stepped into (it will
5455
still step either way)
5556
5657
"""
5758
self.__current_path.append(location)
58-
if self.exists():
59+
if self.exists() or not catch_empty:
5960
return True
6061
warn(f"Stepping into a context path that does not have an associated "
6162
f"context ({self.join_path()}).")

ngcsimlib/_src/parser/contextTransformer.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import ast
2+
3+
from ngcsimlib._src.context.contextAwareObjectMeta import ContextAwareObjectMeta
24
from ngcsimlib._src.global_state.manager import global_state_manager
35
from ngcsimlib._src.logger import warn, error
46
from ngcsimlib._src.compartment.compartment import Compartment
@@ -14,9 +16,11 @@ def __init__(self, obj, method, subMethod=False):
1416
self.method = method
1517
self.current_args = set()
1618
self.needed_keys = set()
17-
self.needed_methods = {}
1819
self.subMethod = subMethod
20+
21+
self.needed_methods = {}
1922
self.needed_globals = {}
23+
self.auxiliary_ast = {}
2024

2125
def visit_Return(self, node):
2226
if self.subMethod:
@@ -46,8 +50,7 @@ def visit_FunctionDef(self, node):
4650

4751

4852
node.decorator_list = new_decorators
49-
50-
node.name = self.obj.name + "_" + node.name
53+
node.name = self.obj.context_path.replace(":", "_") + "_" + node.name
5154
self.generic_visit(node)
5255

5356
if not self.subMethod:
@@ -74,21 +77,47 @@ def visit_Attribute(self, node):
7477

7578
return ast.fix_missing_locations(new_node)
7679

80+
if hasattr(stateVal, '_is_compilable') and isinstance(type(stateVal), ContextAwareObjectMeta):
81+
return node
82+
7783
if callable(stateVal):
78-
method_name = f"{self.obj.name}_{node.attr}"
84+
method_name = f"{self.obj.context_path.replace(':', '_')}_{node.attr}"
7985
new_node = ast.copy_location(ast.Name(id=method_name, ctx=node.ctx), node)
8086
self.needed_methods[method_name] = node.attr
8187
return ast.fix_missing_locations(new_node)
8288

83-
attr_name = f"{self.obj.name}_{node.attr}"
89+
attr_name = f"{self.obj.context_path.replace(':', '_')}_{node.attr}"
8490
new_node = ast.copy_location(ast.Name(id=attr_name, ctx=node.ctx), node)
8591
self.needed_globals[attr_name] = stateVal
8692
return ast.fix_missing_locations(new_node)
8793

8894
return node
8995

9096
def visit_Call(self, node):
97+
9198
node = self.generic_visit(node)
99+
100+
if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Attribute) \
101+
and isinstance(node.func.value.value, ast.Name) and node.func.value.value.id == "self":
102+
attr = getattr(self.obj, node.func.value.attr)
103+
if not isinstance(type(attr), ContextAwareObjectMeta):
104+
return node
105+
106+
subAttr = getattr(attr, node.func.attr)
107+
if not hasattr(subAttr, "compiled"):
108+
error("Attempting to use a method of a subcomponent that is not compiled/compilable")
109+
110+
method_id = f"{attr.context_path.replace(':', '_')}_{node.func.attr}"
111+
subAst = subAttr.compiled.ast
112+
subAst.body[0].body = subAst.body[0].body[:-1]
113+
114+
self.auxiliary_ast[method_id] = subAst
115+
self.auxiliary_ast.update(subAttr.compiled.auxiliary_ast)
116+
self.needed_globals.update(subAttr.compiled.extra_globals)
117+
118+
node.func = ast.Name(id=method_id, ctx=ast.Load())
119+
node.args = [ast.Name(id='ctx', ctx=ast.Load())] + node.args
120+
92121
if isinstance(node.func, ast.Attribute) and node.func.attr == "get":
93122
return node.func.value
94123

@@ -97,19 +126,6 @@ def visit_Call(self, node):
97126

98127
return node
99128

100-
# def visit_Assign(self, node):
101-
# for target in node.targets:
102-
# if isinstance(target, ast.Name):
103-
# target.id = f"{self.obj.name}_{target.id}"
104-
# self.local_vars.add(target.id)
105-
# return self.generic_visit(node)
106-
#
107-
#
108-
# def visit_Name(self, node):
109-
# # if node.id in self.local_vars:
110-
# # node.id = f"{self.obj.name}_{node.id}"
111-
# return node
112-
113129
def visit_Expr(self, node):
114130
node = self.generic_visit(node)
115131
if isinstance(node.value, ast.Call):

0 commit comments

Comments
 (0)