Skip to content

Commit a11d96a

Browse files
authored
Dev (#17)
* Logger & Resolver (#14) * Added more functionality to logger * Added functionality to get resolver Resolvers now parse through parent classes looking for resolvers. Added using_resolver decorator that when decorating a class will add resolvers to the global list based on keyword arguments * Fixed bug with multiple components with the same class name when auto-generating modules.json (#15) * Modules generation (#16) * Fixed bug with multiple components with the same class name when auto-generating modules.json * simplified call * Added compatability with python 3.8 * added log message
1 parent 17d70b8 commit a11d96a

File tree

7 files changed

+172
-31
lines changed

7 files changed

+172
-31
lines changed

ngcsimlib/compilers/command_compiler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ def dynamic_compile(*components, compile_key=None):
132132
Returns:
133133
compiled_command, needed_arguments
134134
"""
135-
assert compile_key is not None
135+
if compile_key is None:
136+
critical("Can not compile a command without a compile key")
136137
return _compile(compile_key, {c.name: c for c in components})
137138

138139

ngcsimlib/compilers/component_compiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def parse(component, compile_key):
3434
3535
"""
3636
(pure_fn, output_compartments), (args, parameters, compartments, parse_varnames) = \
37-
get_resolver(component.__class__.__name__, compile_key)
37+
get_resolver(component.__class__, compile_key)
3838

3939
if parse_varnames:
4040
args = []
@@ -85,7 +85,7 @@ def compiled(**kwargs):
8585
funArgs = {narg: kwargs.get(narg) for _, narg in (list(_args))}
8686
funComps = {narg.split('/')[-1]: kwargs.get(narg) for narg in comp_ids}
8787

88-
return pure_fn(**funParams, **funArgs, **funComps)
88+
return pure_fn.__func__(**funParams, **funArgs, **funComps)
8989

9090
exc_order.append((compiled, out_ids, component.name))
9191
return exc_order

ngcsimlib/compilers/op_compiler.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
by the compiled command.
1212
"""
1313
from ngcsimlib.operations.baseOp import BaseOp
14+
from ngcsimlib.logger import critical
1415

1516

1617
def parse(op):
@@ -28,7 +29,10 @@ def parse(op):
2829
for s in op.sources:
2930
if isinstance(s, BaseOp):
3031
needed_inputs, dest = parse(s)
31-
assert dest is None
32+
if dest is not None:
33+
critical(
34+
"An operation with a destination compartment is being used as a source object for another "
35+
"operation")
3236
for inp in needed_inputs:
3337
if inp not in inputs:
3438
inputs.append(inp)

ngcsimlib/context.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from ngcsimlib.utils import make_unique_path, check_attributes, check_serializable, load_from_path
2-
from ngcsimlib.logger import warn, info
2+
from ngcsimlib.logger import warn, info, critical
33
from ngcsimlib.utils import get_compartment_by_name, \
44
get_context, add_context, get_current_path, get_current_context, set_new_context, load_module, is_pre_loaded
55
from ngcsimlib import preload_modules
66
from ngcsimlib.compilers.command_compiler import dynamic_compile, wrap_command
7-
import json, os
7+
import json, os, shutil
8+
89

910

1011
class Context:
@@ -30,11 +31,13 @@ def __new__(cls, name, *args, **kwargs):
3031
Args:
3132
name: the name of the context
3233
"""
33-
assert len(name) > 0
34+
if len(name) == 0:
35+
critical("Name can not be empty")
3436
con = get_context(str(name))
3537
if con is None:
3638
return super().__new__(cls)
3739
else:
40+
info(f"Returning already existing context: {name}")
3841
return con
3942

4043
def __init__(self, name):
@@ -144,6 +147,7 @@ def register_component(self, component, *args, **kwargs):
144147

145148
c_path = component.path
146149
c_class = component.__class__.__name__
150+
c_mod = component.__class__.__module__
147151

148152
_args = []
149153

@@ -160,7 +164,7 @@ def register_component(self, component, *args, **kwargs):
160164
del _kwargs[key]
161165
info("Failed to serialize \"", key, "\" in ", component.path, sep="")
162166

163-
obj = {"class": c_class, "args": _args, "kwargs": _kwargs}
167+
obj = {"class": c_class, "module": c_mod, "args": _args, "kwargs": _kwargs}
164168
self._json_objects['components'][c_path] = obj
165169

166170
def add_component(self, component):
@@ -189,7 +193,7 @@ def add_command(self, command, name=None):
189193
self.commands[name] = command
190194
self.__setattr__(name, command)
191195

192-
def save_to_json(self, directory, model_name=None, custom_save=True):
196+
def save_to_json(self, directory, model_name=None, custom_save=True, overwrite=False):
193197
"""
194198
Dumps all the required json files to rebuild the current controller to a specified directory. If there is a
195199
`save` command present on the controller and custom_save is True, it will run that command as well.
@@ -204,11 +208,26 @@ def save_to_json(self, directory, model_name=None, custom_save=True):
204208
custom_save: A boolean that if true will attempt to call the `save`
205209
command if present on the controller (Default: True)
206210
211+
overwrite: A boolean for if the saved model should be in a unique folder or if it should overwrite
212+
existing folders
213+
207214
Returns:
208215
a tuple where the first value is the path to the model, and the
209216
second is the path to the custom save folder if custom_save is
210217
true and None if false
211218
"""
219+
if overwrite and os.path.isdir(directory + "/" + model_name):
220+
for filename in os.listdir(directory + "/" + model_name):
221+
file_path = os.path.join(directory + "/" + model_name, filename)
222+
try:
223+
if os.path.isfile(file_path) or os.path.islink(file_path):
224+
os.unlink(file_path)
225+
elif os.path.isdir(file_path):
226+
shutil.rmtree(file_path)
227+
except Exception as e:
228+
print('Failed to delete %s. Reason: %s' % (file_path, e))
229+
shutil.rmtree(directory + "/" + model_name)
230+
212231
path = make_unique_path(directory, model_name)
213232

214233
with open(path + "/modules.json", "w") as fp:
@@ -254,6 +273,8 @@ def save_to_json(self, directory, model_name=None, custom_save=True):
254273
for c_path, component in self._json_objects['components'].items():
255274
if "parameter_map" in component['kwargs'].keys():
256275
del component['kwargs']["parameter_map"]
276+
if "module" in component.keys():
277+
del component['module']
257278

258279
obj = {"components": self._json_objects['components']}
259280
if len(hp.keys()) != 0:
@@ -359,7 +380,8 @@ def make_commands(self, path_to_commands_file):
359380
name=c_name)
360381
else:
361382
klass = load_from_path(command['class'])
362-
klass(*command['args'], **command['kwargs'], components=self.get_components(*command['components'], unwrap=False),
383+
klass(*command['args'], **command['kwargs'],
384+
components=self.get_components(*command['components'], unwrap=False),
363385
command_name=c_name)
364386

365387
def _make_op(self, op_spec):
@@ -421,7 +443,6 @@ def compile_by_key(self, *components, compile_key, name=None):
421443
self.__setattr__(name, cmd)
422444
return cmd, args
423445

424-
425446
def wrap_and_add_command(self, command, name=None):
426447
"""
427448
wraps a command and adds it to the context, if no name is provided it will use the command's internal name
@@ -437,9 +458,7 @@ def make_modules(self):
437458
modules = {}
438459
jComponents = self._json_objects['components']
439460
for c_path, c in jComponents.items():
440-
mod = load_module(c["class"]).__name__
441-
442-
module = ".".join(mod.split(".")[:-1])
461+
module = c["module"]
443462
klass = c["class"]
444463

445464
if module not in modules.keys():
@@ -461,7 +480,6 @@ def make_modules(self):
461480
if klass not in map(lambda x: x["name"], modules[module]["attributes"]):
462481
modules[module]["attributes"].append({"name": klass})
463482

464-
465483
jCommands = self._json_objects['commands']
466484
for c_name, c in jCommands.items():
467485
if c["class"] == "dynamic_compiled":

ngcsimlib/logger.py

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,80 @@
11
from ngcsimlib.configManager import get_config
22
import logging
33
import sys
4+
from datetime import datetime
45

56

6-
def _concatArgs(*args, **kwargs):
7+
def _concatArgs(func):
78
"""Internal Decorator for concatenating arguments into a single string"""
9+
def wrapped(*wargs, sep=" ", end="", **kwargs):
10+
msg = sep.join(str(a) for a in wargs) + end
11+
return func(msg, **kwargs)
812

9-
def inner(func):
10-
def wrapped(*wargs, sep=" ", end="", **kwargs):
11-
msg = sep.join(str(a) for a in wargs) + end
12-
return func(msg, **kwargs)
13+
return wrapped
1314

14-
return wrapped
1515

16-
return inner
16+
_ngclogger = logging.getLogger("ngclogger")
1717

18+
_mapped_calls = {}
1819

19-
_ngclogger = logging.getLogger("ngclogger")
20+
def addLoggingLevel(levelName, levelNum, methodName=None):
21+
"""
22+
Comprehensively adds a new logging level to the `logging` module and the
23+
currently configured logging class.
24+
25+
`levelName` becomes an attribute of the `logging` module with the value
26+
`levelNum`. `methodName` becomes a convenience method for both `logging`
27+
itself and the class returned by `logging.getLoggerClass()` (usually just
28+
`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
29+
used.
30+
31+
Credit: https://stackoverflow.com/a/35804945
32+
33+
Args:
34+
levelName: The custom level name
35+
36+
levelNum: The custom level number
37+
38+
methodName: The name of the method
39+
"""
40+
41+
42+
if not methodName:
43+
methodName = levelName.lower()
44+
45+
if hasattr(logging, levelName):
46+
raise AttributeError('{} already defined in logging module'.format(levelName))
47+
if hasattr(logging, methodName):
48+
raise AttributeError('{} already defined in logging module'.format(methodName))
49+
if hasattr(logging.getLoggerClass(), methodName):
50+
raise AttributeError('{} already defined in logger class'.format(methodName))
51+
52+
def logForLevel(self, message, *args, **kwargs):
53+
if self.isEnabledFor(levelNum):
54+
self._log(levelNum, message, args, **kwargs)
55+
def logToRoot(message, *args, **kwargs):
56+
logging.log(levelNum, message, *args, **kwargs)
57+
58+
logging.addLevelName(levelNum, levelName)
59+
setattr(logging, levelName, levelNum)
60+
setattr(logging.getLoggerClass(), methodName, logForLevel)
61+
setattr(logging, methodName, logToRoot)
2062

63+
_mapped_calls[levelNum] = getattr(_ngclogger, methodName)
64+
_mapped_calls[levelName] = getattr(_ngclogger, methodName)
2165

2266
def init_logging():
2367
loggingConfig = get_config("logging")
2468
if loggingConfig is None:
2569
loggingConfig = {"logging_file": None,
2670
"logging_level": logging.WARNING,
27-
"hide_console": False}
71+
"hide_console": False,
72+
"custom_levels": {"ANALYSIS", 25}}
73+
74+
if loggingConfig.get("custom_levels", None) is not None:
75+
for level_name, level_num in loggingConfig.get("custom_levels", {}).items():
76+
addLoggingLevel(level_name.upper(), level_num)
77+
2878

2979
if isinstance(loggingConfig.get("logging_level", None), str):
3080
loggingConfig["logging_level"] = \
@@ -40,12 +90,16 @@ def init_logging():
4090
_ngclogger.addHandler(err_stream_handler)
4191

4292
if loggingConfig.get("logging_file", None) is not None:
93+
with open(loggingConfig.get("logging_file", None), "a+") as fp:
94+
fp.write(f"~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
95+
f"New Log {f'{datetime.utcnow():%m/%d/%Y %H:%M:%S}'}"
96+
f"\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")
4397
file_handler = logging.FileHandler(filename=loggingConfig.get("logging_file", None))
4498
file_handler.setFormatter(formatter)
4599
_ngclogger.addHandler(file_handler)
46100

47101

48-
@_concatArgs()
102+
@_concatArgs
49103
def warn(msg):
50104
"""
51105
Logs a warning message
@@ -57,7 +111,7 @@ def warn(msg):
57111
_ngclogger.warning(msg)
58112

59113

60-
@_concatArgs()
114+
@_concatArgs
61115
def error(msg):
62116
"""
63117
Logs an error message
@@ -70,7 +124,7 @@ def error(msg):
70124
raise RuntimeError(msg)
71125

72126

73-
@_concatArgs()
127+
@_concatArgs
74128
def critical(msg):
75129
"""
76130
Logs a critical message
@@ -83,7 +137,7 @@ def critical(msg):
83137
raise RuntimeError(msg)
84138

85139

86-
@_concatArgs()
140+
@_concatArgs
87141
def info(msg):
88142
"""
89143
Logs an info message
@@ -93,3 +147,29 @@ def info(msg):
93147
msg: message to log
94148
"""
95149
_ngclogger.info(msg)
150+
151+
152+
@_concatArgs
153+
def debug(msg):
154+
"""
155+
Logs a debug message
156+
This is decorated to have the same functionality of python's print argument concatenation
157+
158+
Args:
159+
msg: message to log
160+
"""
161+
_ngclogger.debug(msg)
162+
163+
164+
@_concatArgs
165+
def custom_log(msg, logging_level=None):
166+
if isinstance(logging_level, str):
167+
logging_level = logging_level.upper()
168+
169+
if logging_level is None:
170+
warn("No logging level passed into message")
171+
elif logging_level not in _mapped_calls.keys():
172+
warn("Attempted to log to undefined level", logging_level)
173+
else:
174+
_mapped_calls[logging_level](msg)
175+

ngcsimlib/resolver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def _wrapped(self=None, *_args, **_kwargs):
104104
params = {key: self.__dict__[key] for key in parameters}
105105
cargs = {key: _kwargs.get(key) for key in args}
106106

107-
vals = pure_fn(**cargs, **params, **comps)
107+
vals = pure_fn.__func__(**cargs, **params, **comps)
108108
if expand_args and len(output_compartments) > 1:
109109
fn(self, *vals)
110110
else:

0 commit comments

Comments
 (0)