ReFRACtor
base.py
Go to the documentation of this file.
1 import logging
2 from collections import OrderedDict
3 
4 # Try and use AttrDict as the dictionary class used to store config instantiation
5 # to make accessing config values easier
6 try:
7  from attrdict import AttrDict as ConfigDict
8 except ImportError:
9  from warnings import warn
10  warn("Could not import AttrDict, config values not accessible as attributes")
11  ConfigDict = dict
12 
13 from ..param import ConfigParam, ParamError, AnyValue, Iterable, InstanceOf, Scalar
14 
15 logger = logging.getLogger('factory.creator.base')
16 
17 # Other creators subscribed to the output of creators by type
18 # Maintain this once in the module so the reference to it contained in every single creator
19 subscribed_observers = OrderedDict()
20 
21 class CreatorError(Exception):
22  pass
23 
24 class ParameterAccessor(object):
25  "Proxy class to stand in place of ConfigParam defitions in Creator instances, to allow accessing as if a method of the creator class"
26 
27  def __init__(self, param_name, param_def, creator):
28  self.param_name = param_name
29  self.param_def = param_def
30  self.config_def = creator.config_def
31  self.common_store = creator.common_store
32  self.creator_name = creator.__class__.__name__
33 
34  def value(self, **kwargs):
35  "Retrieve a parameter from the configuration definition"
36 
37  # Get parameter from configuration definition first, then trying common store and
38  # if not required return None without error
39  if self.param_name in self.config_def:
40  param_val = self.config_def[self.param_name]
41  elif self.param_name in self.common_store:
42  param_val = self.common_store[self.param_name]
43  elif self.param_def.required:
44  raise KeyError("The parameter name %s requested from config definition by %s was not found and is required" % (self.param_name, self.creator_name))
45  else:
46  return self.param_def.default
47 
48  try:
49  return self.param_def.evaluate(param_val, **kwargs)
50  except ParamError as exc:
51  raise ParamError("The parameter named %s requested by creator %s fails with error: %s" % (self.param_name, self.creator_name, exc))
52 
53  def __call__(self, **kwargs):
54  return self.value(**kwargs)
55 
56 class Creator(object):
57  "Base creator object that handles ensuring the contract of creators parameters is kept and type checking parameters requested"
58 
59  def __init__(self, config_def, common_store=None):
60 
61  # Create a new common store if none are passed
62  if common_store is not None:
63  if not isinstance(common_store, dict):
64  raise TypeError("The common store passed must be a instance of a dict")
65  self.common_store = common_store
66  else:
67  self.common_store = {}
68 
69  # Ensure the config definition is of the appropriate type
70  if isinstance(config_def, dict):
71  self.config_def = self._process_config(config_def)
72  else:
73  raise TypeError("The config definition (config_def) argument passed to %s should be of type dict" % self.__class__.__name__)
74 
75  # Load parameters defined in class definition
76  self.parameters = {}
77  self._load_parameters()
78 
79  logger.debug("Initialized creator %s with parameters: %s" % (self.__class__.__name__, self.parameters.keys()))
80 
81  def _process_config(self, in_config_def):
82  "Process config definition, create nested Creators"
83 
84  out_config_def = ConfigDict()
85 
86  for config_name, config_val in in_config_def.items():
87  if isinstance(config_val, dict) and "creator" in config_val:
88  logger.debug("Initializing nested creator %s for %s" % (config_name, self.__class__.__name__))
89  out_config_def[config_name] = config_val["creator"](config_val, common_store=self.common_store)
90  else:
91  out_config_def[config_name] = config_val
92 
93  return out_config_def
94 
95  def register_parameter(self, param_name, param_def):
96  param_proxy = ParameterAccessor(param_name, param_def, self)
97  self.parameters[param_name] = param_proxy
98  setattr(self, param_name, param_proxy)
99 
100  def _load_parameters(self):
101  "Gather class parameter types and ensure config_def has necessary items"
102 
103  # Look through class attributes to find the ones that have values that
104  # inherit from ConfigParam to determine which are the parameters defined.
105  # Replace in the object the parameter definition with an ParamAccessor proxy
106  for attr_name in dir(self):
107  attr_val = getattr(self, attr_name)
108  if isinstance(attr_val, ConfigParam):
109  self.register_parameter(attr_name, attr_val)
110 
111  def param(self, param_name, **kwargs):
112  "Retrieve a parameter from the configuration definition"
113 
114  try:
115  param_proxy = self.parameters[param_name]
116  except KeyError:
117  # Creator requested a parameter that was not defined by a ConfigParam attribute
118  raise KeyError("Unregistered parameter %s requested by %s" % (param_name, self.__class__.__name__))
119  else:
120  return param_proxy.value(**kwargs)
121 
122  def register_to_receive(self, RfType):
123  dispatch_list = subscribed_observers.get(RfType, [])
124  dispatch_list.append(self)
125  subscribed_observers[RfType] = dispatch_list
126 
127  def deregister_to_receive(self, RfType=None):
128 
129  if RfType is None:
130  # Deregister this creator from all observations
131  for obj_type, dispatch_list in subscribed_observers.items():
132  if self in dispatch_list:
133  dispatch_list.remove(self)
134  else:
135  dispatch_list = subscribed_observers.get(RfType, [])
136  if self in dispatch_list:
137  dispatch_list.remove(self)
138 
139  # Clean out empty lists of creators
140  types_to_del = []
141  for obj_type in subscribed_observers.keys():
142  if len(subscribed_observers[obj_type]) == 0:
143  types_to_del.append(obj_type)
144 
145  for obj_type in types_to_del:
146  del subscribed_observers[obj_type]
147 
148  def _dispatch(self, rf_obj):
149  "Called when the creator's object has been created to be sent to other creators who have registered to recieve objects of that type"
150 
151  for RfType, dispatch_list in subscribed_observers.items():
152  if isinstance(rf_obj, RfType):
153  for observer in dispatch_list:
154  observer.receive(rf_obj)
155 
156  def receive(self, rf_obj):
157  "Recieves a dispatched object, needs to be implemented in inheriting class"
158  pass
159 
160  def create(self, **kwargs):
161  "Implements the action to create the object referred to by the Creator class. Should not be called directly."
162  raise NotImplementedError("Create must be defined in inheriting Creator classes")
163 
164  def __call__(self, **kwargs):
165  """Turns creators into callables so that they can be evaluated by ConfigParam as any other callable without it
166  needing to know any details of this class."""
167 
168  result = self.create(**kwargs)
169 
170  if isinstance(result, list):
171  for result_item in result:
172  self._dispatch(result_item)
173  elif isinstance(result, dict):
174  for result_item in result.items():
175  self._dispatch(result_item)
176  else:
177  self._dispatch(result)
178 
179  # Remove this class from the subscribed observers once its create routine has run
180  self.deregister_to_receive()
181 
182  return result
183 
184 
186  "Base class for creators that iterate over their parameters"
187 
188  order = Iterable(required=False)
189 
190  def __init__(self, config_def, param_order=None, **kwargs):
191  super().__init__(config_def, **kwargs)
192 
193  if param_order is None:
194  if "order" in self.config_def:
195  param_order = self.param("order")
196  else:
197  param_order = self.config_def.keys()
198 
199  # Filter out parameter names that should not be processed,
200  # such as the creator param
201  self.param_names = []
202  for param_name in param_order:
203  if param_name != "creator":
204  self.param_names.append(param_name)
205 
206  # Param type not defined so allow any value
207  if param_name not in self.parameters:
208  self.register_parameter(param_name, AnyValue())
209 
211  "Evaluates and passes configurations parameter through as the creator result"
212 
213  def create(self, **kwargs):
214 
215  result = ConfigDict()
216  for param_name in self.param_names:
217  logger.debug("Passing through parameter %s" % param_name)
218  result[param_name] = self.param(param_name, **kwargs)
219 
220  return result
221 
223  "Evaluates parameters and saves them into the common store, creator has no return value"
224 
225  def create(self, **kwargs):
226 
227  result = ConfigDict()
228  for param_name in self.param_names:
229  logger.debug("Saving to the common store parameter %s" % param_name)
230  result[param_name] = self.param(param_name, **kwargs)
231  self.common_store[param_name] = result[param_name]
232 
233  return result
234 
236  "Simply picks one of many nested child elements and returns that, can be used as a place holder before implementing some sort of choice logic Creator"
237 
238  child = Scalar(str)
239 
240  def create(self, **kwargs):
241  self.register_parameter(self.child(), AnyValue())
242  return self.param(self.child(), **kwargs)
def deregister_to_receive(self, RfType=None)
Definition: base.py:127
def __init__(self, param_name, param_def, creator)
Definition: base.py:27
def receive(self, rf_obj)
Definition: base.py:156
def _dispatch(self, rf_obj)
Definition: base.py:148
def __call__(self, kwargs)
Definition: base.py:164
def register_parameter(self, param_name, param_def)
Definition: base.py:95
def create(self, kwargs)
Definition: base.py:160
def param(self, param_name, kwargs)
Definition: base.py:111
def _process_config(self, in_config_def)
Definition: base.py:81
def __init__(self, config_def, common_store=None)
Definition: base.py:59
def register_to_receive(self, RfType)
Definition: base.py:122
def __init__(self, config_def, param_order=None, kwargs)
Definition: base.py:190

Copyright © 2017, California Institute of Technology.
ALL RIGHTS RESERVED.
U.S. Government Sponsorship acknowledged.
Generated Fri Aug 24 2018 15:44:10