Skip to content

Commit

Permalink
Merge branch 'tickets/DM-33992'
Browse files Browse the repository at this point in the history
  • Loading branch information
natelust committed Mar 16, 2022
2 parents 717a6e9 + 1e6a4e5 commit 0f82ab2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 3 deletions.
Expand Up @@ -63,6 +63,16 @@ def __set__(self, instance, value, at=None, label="assignment"):
history = instance._history.setdefault(self.name, [])
history.append(("config value set", at, label))

def save(self, outfile, instance):
# docstring inherited from parent
# This is different that the parent class in that this field must
# serialize which config class is assigned to this field prior to
# serializing any assignments to that config class's fields.
value = self.__get__(instance)
fullname = _joinNamePath(instance._name, self.name)
outfile.write(f"{fullname}={_typeStr(value)}\n")
super().save(outfile, instance)

def __init__(self, doc, dtype=ConfigurableAction, default=None, check=None, deprecated=None):
if not issubclass(dtype, ConfigurableAction):
raise ValueError("dtype must be a subclass of ConfigurableAction")
Expand Down
Expand Up @@ -163,7 +163,8 @@ def __setattr__(self, attr: str, value: Union[ConfigurableAction, Type[Configura
raise FieldValidationError(self._field, self._config, msg)

if attr not in (self.__dict__.keys() | type(self).__dict__.keys()):
name = _joinNamePath(self._config._name, self._field.name, attr)
base_name = _joinNamePath(self._config._name, self._field.name)
name = _joinNamePath(base_name, attr)
if at is None:
at = getCallStack()
if isinstance(value, ConfigurableAction):
Expand Down Expand Up @@ -257,7 +258,8 @@ def rename(self, instance: Config):
actionStruct: ConfigurableActionStruct = self.__get__(instance)
if actionStruct is not None:
for k, v in actionStruct.items():
fullname = _joinNamePath(instance._name, self.name, k)
base_name = _joinNamePath(instance._name, self.name)
fullname = _joinNamePath(base_name, k)
v._rename(fullname)

def validate(self, instance):
Expand All @@ -282,7 +284,6 @@ def save(self, outfile, instance):
outfile.write(u"{}={!r}\n".format(fullname, actionStruct))
return

outfile.write(u"{}={!r}\n".format(fullname, {}))
for v in actionStruct:
outfile.write(u"{}={}()\n".format(v._name, _typeStr(v)))
v._save(outfile)
Expand All @@ -293,6 +294,13 @@ def freeze(self, instance):
for v in actionStruct:
v.freeze()

def _collectImports(self, instance, imports):
# docstring inherited from Field
actionStruct = self.__get__(instance)
for v in actionStruct:
v._collectImports()
imports |= v._imports

def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
"""Compare two fields for equality.
Expand Down
35 changes: 35 additions & 0 deletions python/lsst/pipe/tasks/dataFrameActions/_evalColumnExpression.py
Expand Up @@ -85,6 +85,40 @@ def makeColumnExpressionAction(className: str, expr: str,
Type[DataFrameAction]]]] = None,
docstring: str = None
) -> Type[DataFrameAction]:
"""Factory function for producing ConfigurableAction classes which are
realizations of arithmetic operations.
Parameters
----------
className : `str`
The name of the class that will be produced
expr : `str`
An arithmetic expression that will be parsed to produce the output
ConfigurableAction. Individual variable names will be the name of
individual `ConfigActions` inside the expression (i.e. "x+y" will
produce an action with configAction.actions.x and
configAction.actions.y). Expression can contain arithmatic python
operators as well as; sin, cos, sinh, cosh, log (which is base 10).
exprDefaults : `Mapping` of `str` to `DataFrameAction` optional
A mapping of strings which correspond to the names in the expression to
values which are default `ConfigurableActions` to assign in the
expression. If no default for a action is supplied `SingleColumnAction`
is set as the default.
docstring : `str`
A string that is assigned as the resulting classes docstring
Returns
-------
action : `Type` of `DataFrameAction`
A `DataFrameAction` class that was programatically constructed from the
input expression.
"""
# inspect is used because this is a factory function used to produce classes
# and it is desireable that the classes generated appear to be in the
# module of the calling frame, instead of something defined within the
# scope of this function call.
import inspect
new_module = inspect.stack()[1].frame.f_locals['__name__']
node = ast.parse(expr, mode='eval')

# gather the specified names
Expand Down Expand Up @@ -123,5 +157,6 @@ def columns(self) -> Iterable[str]:
if docstring is not None:
dct['__doc__'] = docstring
dct.update(**fields)
dct['__module__'] = new_module

return type(className, (DataFrameAction, ), dct)

0 comments on commit 0f82ab2

Please sign in to comment.