Skip to content

Commit 8c4d013

Browse files
authored
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
1 parent 17d70b8 commit 8c4d013

File tree

6 files changed

+165
-25
lines changed

6 files changed

+165
-25
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: 1 addition & 1 deletion
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 = []

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: 24 additions & 7 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
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):
@@ -189,7 +192,7 @@ def add_command(self, command, name=None):
189192
self.commands[name] = command
190193
self.__setattr__(name, command)
191194

192-
def save_to_json(self, directory, model_name=None, custom_save=True):
195+
def save_to_json(self, directory, model_name=None, custom_save=True, overwrite=False):
193196
"""
194197
Dumps all the required json files to rebuild the current controller to a specified directory. If there is a
195198
`save` command present on the controller and custom_save is True, it will run that command as well.
@@ -204,11 +207,26 @@ def save_to_json(self, directory, model_name=None, custom_save=True):
204207
custom_save: A boolean that if true will attempt to call the `save`
205208
command if present on the controller (Default: True)
206209
210+
overwrite: A boolean for if the saved model should be in a unique folder or if it should overwrite
211+
existing folders
212+
207213
Returns:
208214
a tuple where the first value is the path to the model, and the
209215
second is the path to the custom save folder if custom_save is
210216
true and None if false
211217
"""
218+
if overwrite and os.path.isdir(directory + "/" + model_name):
219+
for filename in os.listdir(directory + "/" + model_name):
220+
file_path = os.path.join(directory + "/" + model_name, filename)
221+
try:
222+
if os.path.isfile(file_path) or os.path.islink(file_path):
223+
os.unlink(file_path)
224+
elif os.path.isdir(file_path):
225+
shutil.rmtree(file_path)
226+
except Exception as e:
227+
print('Failed to delete %s. Reason: %s' % (file_path, e))
228+
shutil.rmtree(directory + "/" + model_name)
229+
212230
path = make_unique_path(directory, model_name)
213231

214232
with open(path + "/modules.json", "w") as fp:
@@ -359,7 +377,8 @@ def make_commands(self, path_to_commands_file):
359377
name=c_name)
360378
else:
361379
klass = load_from_path(command['class'])
362-
klass(*command['args'], **command['kwargs'], components=self.get_components(*command['components'], unwrap=False),
380+
klass(*command['args'], **command['kwargs'],
381+
components=self.get_components(*command['components'], unwrap=False),
363382
command_name=c_name)
364383

365384
def _make_op(self, op_spec):
@@ -421,7 +440,6 @@ def compile_by_key(self, *components, compile_key, name=None):
421440
self.__setattr__(name, cmd)
422441
return cmd, args
423442

424-
425443
def wrap_and_add_command(self, command, name=None):
426444
"""
427445
wraps a command and adds it to the context, if no name is provided it will use the command's internal name
@@ -461,7 +479,6 @@ def make_modules(self):
461479
if klass not in map(lambda x: x["name"], modules[module]["attributes"]):
462480
modules[module]["attributes"].append({"name": klass})
463481

464-
465482
jCommands = self._json_objects['commands']
466483
for c_name, c in jCommands.items():
467484
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/utils.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
import sys, uuid, os, json
55
from importlib import import_module
6-
from ngcsimlib.logger import info
6+
from ngcsimlib.logger import info, critical, debug
77

88
## Globally tracking all the modules, and attributes have been dynamically loaded
99
_Loaded_Attributes = {}
@@ -301,10 +301,30 @@ def get_compartment_by_name(context, name):
301301
__resolver_meta_data = {}
302302

303303

304-
def get_resolver(class_name, resolver_key):
304+
def get_resolver(klass, resolver_key, root=None):
305305
"""
306306
A helper method for searching through the resolver list
307307
"""
308+
class_name = klass.__name__
309+
310+
if class_name + "/" + resolver_key not in __component_resolvers.keys():
311+
parent_classes = klass.__bases__
312+
if len(parent_classes) == 0:
313+
return None, None
314+
315+
resolver = None
316+
for parent in parent_classes:
317+
resolver, meta = get_resolver(parent, resolver_key, root=klass if root is None else root)
318+
if resolver is not None:
319+
return resolver, meta
320+
321+
if resolver is None and root is None:
322+
critical(class_name, "has no resolver for", resolver_key)
323+
if resolver is None:
324+
return None, None
325+
326+
if root is not None:
327+
debug(f"{root.__name__} is using the resolver from {class_name} for resolving key \"{resolver_key}\"")
308328
return __component_resolvers[class_name + "/" + resolver_key], __resolver_meta_data[class_name + "/" + resolver_key]
309329

310330

@@ -321,6 +341,24 @@ def add_resolver_meta(class_name, resolver_key, data):
321341
"""
322342
__resolver_meta_data[class_name + "/" + resolver_key] = data
323343

344+
def using_resolver(**kwargs):
345+
"""
346+
A decorator for linking resolvers defined in other classes to this class.
347+
the keyword arguments are compile_key=class_to_inherit_resolver_from. This
348+
will add the resolver directly to the class and thus will get used before
349+
any resolvers in parent classes.
350+
351+
Args:
352+
**kwargs: any number or compile_key=class_to_inherit_resolver_from
353+
"""
354+
def _klass_wrapper(cls):
355+
klass_name = cls.__name__
356+
for key, value in kwargs.items():
357+
resolver, data = get_resolver(value, key)
358+
add_component_resolver(klass_name, key, resolver)
359+
add_resolver_meta(klass_name, key, data)
360+
return cls
361+
return _klass_wrapper
324362

325363
## Contexts
326364
__current_context = ""

0 commit comments

Comments
 (0)