LSSTApplications  17.0+50,17.0+84,17.0+9,18.0.0+14,18.0.0+2,18.0.0+30,18.0.0+4,18.0.0+9,18.0.0-2-ge43143a+4,18.1.0-1-g0001055,18.1.0-1-g0896a44+6,18.1.0-1-g1349e88+4,18.1.0-1-g2505f39+3,18.1.0-1-g380d4d4+4,18.1.0-1-g5e4b7ea,18.1.0-1-g85f8cd4+3,18.1.0-1-g9a6769a+2,18.1.0-1-ga1a4c1a+2,18.1.0-1-gc037db8,18.1.0-1-gd55f500+1,18.1.0-1-ge10677a+3,18.1.0-10-g73b8679e+7,18.1.0-11-g311e899+3,18.1.0-12-g0d73a3591,18.1.0-12-gc95f69a+3,18.1.0-2-g000ad9a+3,18.1.0-2-g31c43f9+3,18.1.0-2-g9c63283+4,18.1.0-2-gdf0b915+4,18.1.0-2-gf03bb23,18.1.0-3-g2e29e3d+6,18.1.0-3-g52aa583+2,18.1.0-3-g9cb968e+3,18.1.0-4-gd2e8982+6,18.1.0-5-g510c42a+3,18.1.0-5-gaeab27e+4,18.1.0-6-gdda7f3e+6,18.1.0-7-g89824ecc+4,w.2019.32
LSSTDataManagementBasePackage
config.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2015 AURA/LSST.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <https://www.lsstcorp.org/LegalNotices/>.
21 #
22 __all__ = ("Config", "Field", "FieldValidationError")
23 
24 import io
25 import os
26 import re
27 import sys
28 import math
29 import copy
30 import tempfile
31 import shutil
32 import warnings
33 
34 from .comparison import getComparisonName, compareScalars, compareConfigs
35 from .callStack import getStackFrame, getCallStack
36 
37 
38 def _joinNamePath(prefix=None, name=None, index=None):
39  """Generate nested configuration names.
40  """
41  if not prefix and not name:
42  raise ValueError("Invalid name: cannot be None")
43  elif not name:
44  name = prefix
45  elif prefix and name:
46  name = prefix + "." + name
47 
48  if index is not None:
49  return "%s[%r]" % (name, index)
50  else:
51  return name
52 
53 
54 def _autocast(x, dtype):
55  """Cast a value to a type, if appropriate.
56 
57  Parameters
58  ----------
59  x : object
60  A value.
61  dtype : tpye
62  Data type, such as `float`, `int`, or `str`.
63 
64  Returns
65  -------
66  values : object
67  If appropriate, the returned value is ``x`` cast to the given type
68  ``dtype``. If the cast cannot be performed the original value of
69  ``x`` is returned.
70  """
71  if dtype == float and isinstance(x, int):
72  return float(x)
73  return x
74 
75 
76 def _typeStr(x):
77  """Generate a fully-qualified type name.
78 
79  Returns
80  -------
81  `str`
82  Fully-qualified type name.
83 
84  Notes
85  -----
86  This function is used primarily for writing config files to be executed
87  later upon with the 'load' function.
88  """
89  if hasattr(x, '__module__') and hasattr(x, '__name__'):
90  xtype = x
91  else:
92  xtype = type(x)
93  if (sys.version_info.major <= 2 and xtype.__module__ == '__builtin__') or xtype.__module__ == 'builtins':
94  return xtype.__name__
95  else:
96  return "%s.%s" % (xtype.__module__, xtype.__name__)
97 
98 
100  """A metaclass for `lsst.pex.config.Config`.
101 
102  Notes
103  -----
104  ``ConfigMeta`` adds a dictionary containing all `~lsst.pex.config.Field`
105  class attributes as a class attribute called ``_fields``, and adds
106  the name of each field as an instance variable of the field itself (so you
107  don't have to pass the name of the field to the field constructor).
108  """
109 
110  def __init__(cls, name, bases, dict_):
111  type.__init__(cls, name, bases, dict_)
112  cls._fields = {}
113  cls._source = getStackFrame()
114 
115  def getFields(classtype):
116  fields = {}
117  bases = list(classtype.__bases__)
118  bases.reverse()
119  for b in bases:
120  fields.update(getFields(b))
121 
122  for k, v in classtype.__dict__.items():
123  if isinstance(v, Field):
124  fields[k] = v
125  return fields
126 
127  fields = getFields(cls)
128  for k, v in fields.items():
129  setattr(cls, k, copy.deepcopy(v))
130 
131  def __setattr__(cls, name, value):
132  if isinstance(value, Field):
133  value.name = name
134  cls._fields[name] = value
135  type.__setattr__(cls, name, value)
136 
137 
138 class FieldValidationError(ValueError):
139  """Raised when a ``~lsst.pex.config.Field`` is not valid in a
140  particular ``~lsst.pex.config.Config``.
141 
142  Parameters
143  ----------
144  field : `lsst.pex.config.Field`
145  The field that was not valid.
146  config : `lsst.pex.config.Config`
147  The config containing the invalid field.
148  msg : `str`
149  Text describing why the field was not valid.
150  """
151 
152  def __init__(self, field, config, msg):
153  self.fieldType = type(field)
154  """Type of the `~lsst.pex.config.Field` that incurred the error.
155  """
156 
157  self.fieldName = field.name
158  """Name of the `~lsst.pex.config.Field` instance that incurred the
159  error (`str`).
160 
161  See also
162  --------
163  lsst.pex.config.Field.name
164  """
165 
166  self.fullname = _joinNamePath(config._name, field.name)
167  """Fully-qualified name of the `~lsst.pex.config.Field` instance
168  (`str`).
169  """
170 
171  self.history = config.history.setdefault(field.name, [])
172  """Full history of all changes to the `~lsst.pex.config.Field`
173  instance.
174  """
175 
176  self.fieldSource = field.source
177  """File and line number of the `~lsst.pex.config.Field` definition.
178  """
179 
180  self.configSource = config._source
181  error = "%s '%s' failed validation: %s\n"\
182  "For more information see the Field definition at:\n%s"\
183  " and the Config definition at:\n%s" % \
184  (self.fieldType.__name__, self.fullname, msg,
185  self.fieldSource.format(), self.configSource.format())
186  super().__init__(error)
187 
188 
189 class Field:
190  """A field in a `~lsst.pex.config.Config` that supports `int`, `float`,
191  `complex`, `bool`, and `str` data types.
192 
193  Parameters
194  ----------
195  doc : `str`
196  A description of the field for users.
197  dtype : type
198  The field's data type. ``Field`` only supports basic data types:
199  `int`, `float`, `complex`, `bool`, and `str`. See
200  `Field.supportedTypes`.
201  default : object, optional
202  The field's default value.
203  check : callable, optional
204  A callable that is called with the field's value. This callable should
205  return `False` if the value is invalid. More complex inter-field
206  validation can be written as part of the
207  `lsst.pex.config.Config.validate` method.
208  optional : `bool`, optional
209  This sets whether the field is considered optional, and therefore
210  doesn't need to be set by the user. When `False`,
211  `lsst.pex.config.Config.validate` fails if the field's value is `None`.
212  deprecated : None or `str`, optional
213  A description of why this Field is deprecated, including removal date.
214  If not None, the string is appended to the docstring for this Field.
215 
216  Raises
217  ------
218  ValueError
219  Raised when the ``dtype`` parameter is not one of the supported types
220  (see `Field.supportedTypes`).
221 
222  See also
223  --------
224  ChoiceField
225  ConfigChoiceField
226  ConfigDictField
227  ConfigField
228  ConfigurableField
229  DictField
230  ListField
231  RangeField
232  RegistryField
233 
234  Notes
235  -----
236  ``Field`` instances (including those of any subclass of ``Field``) are used
237  as class attributes of `~lsst.pex.config.Config` subclasses (see the
238  example, below). ``Field`` attributes work like the `property` attributes
239  of classes that implement custom setters and getters. `Field` attributes
240  belong to the class, but operate on the instance. Formally speaking,
241  `Field` attributes are `descriptors
242  <https://docs.python.org/3/howto/descriptor.html>`_.
243 
244  When you access a `Field` attribute on a `Config` instance, you don't
245  get the `Field` instance itself. Instead, you get the value of that field,
246  which might be a simple type (`int`, `float`, `str`, `bool`) or a custom
247  container type (like a `lsst.pex.config.List`) depending on the field's
248  type. See the example, below.
249 
250  Examples
251  --------
252  Instances of ``Field`` should be used as class attributes of
253  `lsst.pex.config.Config` subclasses:
254 
255  >>> from lsst.pex.config import Config, Field
256  >>> class Example(Config):
257  ... myInt = Field("An integer field.", int, default=0)
258  ...
259  >>> print(config.myInt)
260  0
261  >>> config.myInt = 5
262  >>> print(config.myInt)
263  5
264  """
265 
266  supportedTypes = set((str, bool, float, int, complex))
267  """Supported data types for field values (`set` of types).
268  """
269 
270  def __init__(self, doc, dtype, default=None, check=None, optional=False, deprecated=None):
271  if dtype not in self.supportedTypes:
272  raise ValueError("Unsupported Field dtype %s" % _typeStr(dtype))
273 
274  source = getStackFrame()
275  self._setup(doc=doc, dtype=dtype, default=default, check=check, optional=optional, source=source,
276  deprecated=deprecated)
277 
278  def _setup(self, doc, dtype, default, check, optional, source, deprecated):
279  """Set attributes, usually during initialization.
280  """
281  self.dtype = dtype
282  """Data type for the field.
283  """
284 
285  # append the deprecation message to the docstring.
286  if deprecated is not None:
287  doc = f"{doc} Deprecated: {deprecated}"
288  self.doc = doc
289  """A description of the field (`str`).
290  """
291 
292  self.deprecated = deprecated
293  """If not None, a description of why this field is deprecated (`str`).
294  """
295 
296  self.__doc__ = f"{doc} (`{dtype.__name__}`"
297  if optional or default is not None:
298  self.__doc__ += f", default ``{default!r}``"
299  self.__doc__ += ")"
300 
301  self.default = default
302  """Default value for this field.
303  """
304 
305  self.check = check
306  """A user-defined function that validates the value of the field.
307  """
308 
309  self.optional = optional
310  """Flag that determines if the field is required to be set (`bool`).
311 
312  When `False`, `lsst.pex.config.Config.validate` will fail if the
313  field's value is `None`.
314  """
315 
316  self.source = source
317  """The stack frame where this field is defined (`list` of
318  `lsst.pex.config.callStack.StackFrame`).
319  """
320 
321  def rename(self, instance):
322  """Rename the field in a `~lsst.pex.config.Config` (for internal use
323  only).
324 
325  Parameters
326  ----------
327  instance : `lsst.pex.config.Config`
328  The config instance that contains this field.
329 
330  Notes
331  -----
332  This method is invoked by the `lsst.pex.config.Config` object that
333  contains this field and should not be called directly.
334 
335  Renaming is only relevant for `~lsst.pex.config.Field` instances that
336  hold subconfigs. `~lsst.pex.config.Fields` that hold subconfigs should
337  rename each subconfig with the full field name as generated by
338  `lsst.pex.config.config._joinNamePath`.
339  """
340  pass
341 
342  def validate(self, instance):
343  """Validate the field (for internal use only).
344 
345  Parameters
346  ----------
347  instance : `lsst.pex.config.Config`
348  The config instance that contains this field.
349 
350  Raises
351  ------
352  lsst.pex.config.FieldValidationError
353  Raised if verification fails.
354 
355  Notes
356  -----
357  This method provides basic validation:
358 
359  - Ensures that the value is not `None` if the field is not optional.
360  - Ensures type correctness.
361  - Ensures that the user-provided ``check`` function is valid.
362 
363  Most `~lsst.pex.config.Field` subclasses should call
364  `lsst.pex.config.field.Field.validate` if they re-implement
365  `~lsst.pex.config.field.Field.validate`.
366  """
367  value = self.__get__(instance)
368  if not self.optional and value is None:
369  raise FieldValidationError(self, instance, "Required value cannot be None")
370 
371  def freeze(self, instance):
372  """Make this field read-only (for internal use only).
373 
374  Parameters
375  ----------
376  instance : `lsst.pex.config.Config`
377  The config instance that contains this field.
378 
379  Notes
380  -----
381  Freezing is only relevant for fields that hold subconfigs. Fields which
382  hold subconfigs should freeze each subconfig.
383 
384  **Subclasses should implement this method.**
385  """
386  pass
387 
388  def _validateValue(self, value):
389  """Validate a value.
390 
391  Parameters
392  ----------
393  value : object
394  The value being validated.
395 
396  Raises
397  ------
398  TypeError
399  Raised if the value's type is incompatible with the field's
400  ``dtype``.
401  ValueError
402  Raised if the value is rejected by the ``check`` method.
403  """
404  if value is None:
405  return
406 
407  if not isinstance(value, self.dtype):
408  msg = "Value %s is of incorrect type %s. Expected type %s" % \
409  (value, _typeStr(value), _typeStr(self.dtype))
410  raise TypeError(msg)
411  if self.check is not None and not self.check(value):
412  msg = "Value %s is not a valid value" % str(value)
413  raise ValueError(msg)
414 
415  def _collectImports(self, instance, imports):
416  """This function should call the _collectImports method on all config
417  objects the field may own, and union them with the supplied imports
418  set.
419 
420  Parameters
421  ----------
422  instance : instance or subclass of `lsst.pex.config.Config`
423  A config object that has this field defined on it
424  imports : `set`
425  Set of python modules that need imported after persistence
426  """
427  pass
428 
429  def save(self, outfile, instance):
430  """Save this field to a file (for internal use only).
431 
432  Parameters
433  ----------
434  outfile : file-like object
435  A writeable field handle.
436  instance : `Config`
437  The `Config` instance that contains this field.
438 
439  Notes
440  -----
441  This method is invoked by the `~lsst.pex.config.Config` object that
442  contains this field and should not be called directly.
443 
444  The output consists of the documentation string
445  (`lsst.pex.config.Field.doc`) formatted as a Python comment. The second
446  line is formatted as an assignment: ``{fullname}={value}``.
447 
448  This output can be executed with Python.
449  """
450  value = self.__get__(instance)
451  fullname = _joinNamePath(instance._name, self.name)
452 
453  # write full documentation string as comment lines (i.e. first character is #)
454  doc = "# " + str(self.doc).replace("\n", "\n# ")
455  if isinstance(value, float) and (math.isinf(value) or math.isnan(value)):
456  # non-finite numbers need special care
457  outfile.write(u"{}\n{}=float('{!r}')\n\n".format(doc, fullname, value))
458  else:
459  outfile.write(u"{}\n{}={!r}\n\n".format(doc, fullname, value))
460 
461  def toDict(self, instance):
462  """Convert the field value so that it can be set as the value of an
463  item in a `dict` (for internal use only).
464 
465  Parameters
466  ----------
467  instance : `Config`
468  The `Config` that contains this field.
469 
470  Returns
471  -------
472  value : object
473  The field's value. See *Notes*.
474 
475  Notes
476  -----
477  This method invoked by the owning `~lsst.pex.config.Config` object and
478  should not be called directly.
479 
480  Simple values are passed through. Complex data structures must be
481  manipulated. For example, a `~lsst.pex.config.Field` holding a
482  subconfig should, instead of the subconfig object, return a `dict`
483  where the keys are the field names in the subconfig, and the values are
484  the field values in the subconfig.
485  """
486  return self.__get__(instance)
487 
488  def __get__(self, instance, owner=None, at=None, label="default"):
489  """Define how attribute access should occur on the Config instance
490  This is invoked by the owning config object and should not be called
491  directly
492 
493  When the field attribute is accessed on a Config class object, it
494  returns the field object itself in order to allow inspection of
495  Config classes.
496 
497  When the field attribute is access on a config instance, the actual
498  value described by the field (and held by the Config instance) is
499  returned.
500  """
501  if instance is None or not isinstance(instance, Config):
502  return self
503  else:
504  return instance._storage[self.name]
505 
506  def __set__(self, instance, value, at=None, label='assignment'):
507  """Set an attribute on the config instance.
508 
509  Parameters
510  ----------
511  instance : `lsst.pex.config.Config`
512  The config instance that contains this field.
513  value : obj
514  Value to set on this field.
515  at : `list` of `lsst.pex.config.callStack.StackFrame`
516  The call stack (created by
517  `lsst.pex.config.callStack.getCallStack`).
518  label : `str`, optional
519  Event label for the history.
520 
521  Notes
522  -----
523  This method is invoked by the owning `lsst.pex.config.Config` object
524  and should not be called directly.
525 
526  Derived `~lsst.pex.config.Field` classes may need to override the
527  behavior. When overriding ``__set__``, `~lsst.pex.config.Field` authors
528  should follow the following rules:
529 
530  - Do not allow modification of frozen configs.
531  - Validate the new value **before** modifying the field. Except if the
532  new value is `None`. `None` is special and no attempt should be made
533  to validate it until `lsst.pex.config.Config.validate` is called.
534  - Do not modify the `~lsst.pex.config.Config` instance to contain
535  invalid values.
536  - If the field is modified, update the history of the
537  `lsst.pex.config.field.Field` to reflect the changes.
538 
539  In order to decrease the need to implement this method in derived
540  `~lsst.pex.config.Field` types, value validation is performed in the
541  `lsst.pex.config.Field._validateValue`. If only the validation step
542  differs in the derived `~lsst.pex.config.Field`, it is simpler to
543  implement `lsst.pex.config.Field._validateValue` than to reimplement
544  ``__set__``. More complicated behavior, however, may require
545  reimplementation.
546  """
547  if instance._frozen:
548  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
549 
550  history = instance._history.setdefault(self.name, [])
551  if value is not None:
552  value = _autocast(value, self.dtype)
553  try:
554  self._validateValue(value)
555  except BaseException as e:
556  raise FieldValidationError(self, instance, str(e))
557 
558  instance._storage[self.name] = value
559  if at is None:
560  at = getCallStack()
561  history.append((value, at, label))
562 
563  def __delete__(self, instance, at=None, label='deletion'):
564  """Delete an attribute from a `lsst.pex.config.Config` instance.
565 
566  Parameters
567  ----------
568  instance : `lsst.pex.config.Config`
569  The config instance that contains this field.
570  at : `list` of `lsst.pex.config.callStack.StackFrame`
571  The call stack (created by
572  `lsst.pex.config.callStack.getCallStack`).
573  label : `str`, optional
574  Event label for the history.
575 
576  Notes
577  -----
578  This is invoked by the owning `~lsst.pex.config.Config` object and
579  should not be called directly.
580  """
581  if at is None:
582  at = getCallStack()
583  self.__set__(instance, None, at=at, label=label)
584 
585  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
586  """Compare a field (named `Field.name`) in two
587  `~lsst.pex.config.Config` instances for equality.
588 
589  Parameters
590  ----------
591  instance1 : `lsst.pex.config.Config`
592  Left-hand side `Config` instance to compare.
593  instance2 : `lsst.pex.config.Config`
594  Right-hand side `Config` instance to compare.
595  shortcut : `bool`, optional
596  **Unused.**
597  rtol : `float`, optional
598  Relative tolerance for floating point comparisons.
599  atol : `float`, optional
600  Absolute tolerance for floating point comparisons.
601  output : callable, optional
602  A callable that takes a string, used (possibly repeatedly) to
603  report inequalities.
604 
605  Notes
606  -----
607  This method must be overridden by more complex `Field` subclasses.
608 
609  See also
610  --------
611  lsst.pex.config.compareScalars
612  """
613  v1 = getattr(instance1, self.name)
614  v2 = getattr(instance2, self.name)
615  name = getComparisonName(
616  _joinNamePath(instance1._name, self.name),
617  _joinNamePath(instance2._name, self.name)
618  )
619  return compareScalars(name, v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output)
620 
621 
623  """Importer (for `sys.meta_path`) that records which modules are being
624  imported.
625 
626  *This class does not do any importing itself.*
627 
628  Examples
629  --------
630  Use this class as a context manager to ensure it is properly uninstalled
631  when done:
632 
633  >>> with RecordingImporter() as importer:
634  ... # import stuff
635  ... import numpy as np
636  ... print("Imported: " + importer.getModules())
637  """
638 
639  def __init__(self):
640  self._modules = set()
641 
642  def __enter__(self):
643  self.origMetaPath = sys.meta_path
644  sys.meta_path = [self] + sys.meta_path
645  return self
646 
647  def __exit__(self, *args):
648  self.uninstall()
649  return False # Don't suppress exceptions
650 
651  def uninstall(self):
652  """Uninstall the importer.
653  """
654  sys.meta_path = self.origMetaPath
655 
656  def find_module(self, fullname, path=None):
657  """Called as part of the ``import`` chain of events.
658  """
659  self._modules.add(fullname)
660  # Return None because we don't do any importing.
661  return None
662 
663  def getModules(self):
664  """Get the set of modules that were imported.
665 
666  Returns
667  -------
668  modules : `set` of `str`
669  Set of imported module names.
670  """
671  return self._modules
672 
673 
674 class Config(metaclass=ConfigMeta):
675  """Base class for configuration (*config*) objects.
676 
677  Notes
678  -----
679  A ``Config`` object will usually have several `~lsst.pex.config.Field`
680  instances as class attributes. These are used to define most of the base
681  class behavior.
682 
683  ``Config`` implements a mapping API that provides many `dict`-like methods,
684  such as `keys`, `values`, `items`, `iteritems`, `iterkeys`, and
685  `itervalues`. ``Config`` instances also support the ``in`` operator to
686  test if a field is in the config. Unlike a `dict`, ``Config`` classes are
687  not subscriptable. Instead, access individual fields as attributes of the
688  configuration instance.
689 
690  Examples
691  --------
692  Config classes are subclasses of ``Config`` that have
693  `~lsst.pex.config.Field` instances (or instances of
694  `~lsst.pex.config.Field` subclasses) as class attributes:
695 
696  >>> from lsst.pex.config import Config, Field, ListField
697  >>> class DemoConfig(Config):
698  ... intField = Field(doc="An integer field", dtype=int, default=42)
699  ... listField = ListField(doc="List of favorite beverages.", dtype=str,
700  ... default=['coffee', 'green tea', 'water'])
701  ...
702  >>> config = DemoConfig()
703 
704  Configs support many `dict`-like APIs:
705 
706  >>> config.keys()
707  ['intField', 'listField']
708  >>> 'intField' in config
709  True
710 
711  Individual fields can be accessed as attributes of the configuration:
712 
713  >>> config.intField
714  42
715  >>> config.listField.append('earl grey tea')
716  >>> print(config.listField)
717  ['coffee', 'green tea', 'water', 'earl grey tea']
718  """
719 
720  def __iter__(self):
721  """Iterate over fields.
722  """
723  return self._fields.__iter__()
724 
725  def keys(self):
726  """Get field names.
727 
728  Returns
729  -------
730  names : `list`
731  List of `lsst.pex.config.Field` names.
732 
733  See also
734  --------
735  lsst.pex.config.Config.iterkeys
736  """
737  return list(self._storage.keys())
738 
739  def values(self):
740  """Get field values.
741 
742  Returns
743  -------
744  values : `list`
745  List of field values.
746 
747  See also
748  --------
749  lsst.pex.config.Config.itervalues
750  """
751  return list(self._storage.values())
752 
753  def items(self):
754  """Get configurations as ``(field name, field value)`` pairs.
755 
756  Returns
757  -------
758  items : `list`
759  List of tuples for each configuration. Tuple items are:
760 
761  0. Field name.
762  1. Field value.
763 
764  See also
765  --------
766  lsst.pex.config.Config.iteritems
767  """
768  return list(self._storage.items())
769 
770  def iteritems(self):
771  """Iterate over (field name, field value) pairs.
772 
773  Yields
774  ------
775  item : `tuple`
776  Tuple items are:
777 
778  0. Field name.
779  1. Field value.
780 
781  See also
782  --------
783  lsst.pex.config.Config.items
784  """
785  return iter(self._storage.items())
786 
787  def itervalues(self):
788  """Iterate over field values.
789 
790  Yields
791  ------
792  value : obj
793  A field value.
794 
795  See also
796  --------
797  lsst.pex.config.Config.values
798  """
799  return iter(self.storage.values())
800 
801  def iterkeys(self):
802  """Iterate over field names
803 
804  Yields
805  ------
806  key : `str`
807  A field's key (attribute name).
808 
809  See also
810  --------
811  lsst.pex.config.Config.values
812  """
813  return iter(self.storage.keys())
814 
815  def __contains__(self, name):
816  """!Return True if the specified field exists in this config
817 
818  @param[in] name field name to test for
819  """
820  return self._storage.__contains__(name)
821 
822  def __new__(cls, *args, **kw):
823  """Allocate a new `lsst.pex.config.Config` object.
824 
825  In order to ensure that all Config object are always in a proper state
826  when handed to users or to derived `~lsst.pex.config.Config` classes,
827  some attributes are handled at allocation time rather than at
828  initialization.
829 
830  This ensures that even if a derived `~lsst.pex.config.Config` class
831  implements ``__init__``, its author does not need to be concerned about
832  when or even the base ``Config.__init__`` should be called.
833  """
834  name = kw.pop("__name", None)
835  at = kw.pop("__at", getCallStack())
836  # remove __label and ignore it
837  kw.pop("__label", "default")
838 
839  instance = object.__new__(cls)
840  instance._frozen = False
841  instance._name = name
842  instance._storage = {}
843  instance._history = {}
844  instance._imports = set()
845  # load up defaults
846  for field in instance._fields.values():
847  instance._history[field.name] = []
848  field.__set__(instance, field.default, at=at + [field.source], label="default")
849  # set custom default-overides
850  instance.setDefaults()
851  # set constructor overides
852  instance.update(__at=at, **kw)
853  return instance
854 
855  def __reduce__(self):
856  """Reduction for pickling (function with arguments to reproduce).
857 
858  We need to condense and reconstitute the `~lsst.pex.config.Config`,
859  since it may contain lambdas (as the ``check`` elements) that cannot
860  be pickled.
861  """
862  # The stream must be in characters to match the API but pickle requires bytes
863  stream = io.StringIO()
864  self.saveToStream(stream)
865  return (unreduceConfig, (self.__class__, stream.getvalue().encode()))
866 
867  def setDefaults(self):
868  """Subclass hook for computing defaults.
869 
870  Notes
871  -----
872  Derived `~lsst.pex.config.Config` classes that must compute defaults
873  rather than using the `~lsst.pex.config.Field` instances's defaults
874  should do so here. To correctly use inherited defaults,
875  implementations of ``setDefaults`` must call their base class's
876  ``setDefaults``.
877  """
878  pass
879 
880  def update(self, **kw):
881  """Update values of fields specified by the keyword arguments.
882 
883  Parameters
884  ----------
885  kw
886  Keywords are configuration field names. Values are configuration
887  field values.
888 
889  Notes
890  -----
891  The ``__at`` and ``__label`` keyword arguments are special internal
892  keywords. They are used to strip out any internal steps from the
893  history tracebacks of the config. Do not modify these keywords to
894  subvert a `~lsst.pex.config.Config` instance's history.
895 
896  Examples
897  --------
898  This is a config with three fields:
899 
900  >>> from lsst.pex.config import Config, Field
901  >>> class DemoConfig(Config):
902  ... fieldA = Field(doc='Field A', dtype=int, default=42)
903  ... fieldB = Field(doc='Field B', dtype=bool, default=True)
904  ... fieldC = Field(doc='Field C', dtype=str, default='Hello world')
905  ...
906  >>> config = DemoConfig()
907 
908  These are the default values of each field:
909 
910  >>> for name, value in config.iteritems():
911  ... print(f"{name}: {value}")
912  ...
913  fieldA: 42
914  fieldB: True
915  fieldC: 'Hello world'
916 
917  Using this method to update ``fieldA`` and ``fieldC``:
918 
919  >>> config.update(fieldA=13, fieldC='Updated!')
920 
921  Now the values of each field are:
922 
923  >>> for name, value in config.iteritems():
924  ... print(f"{name}: {value}")
925  ...
926  fieldA: 13
927  fieldB: True
928  fieldC: 'Updated!'
929  """
930  at = kw.pop("__at", getCallStack())
931  label = kw.pop("__label", "update")
932 
933  for name, value in kw.items():
934  try:
935  field = self._fields[name]
936  field.__set__(self, value, at=at, label=label)
937  except KeyError:
938  raise KeyError("No field of name %s exists in config type %s" % (name, _typeStr(self)))
939 
940  def load(self, filename, root="config"):
941  """Modify this config in place by executing the Python code in a
942  configuration file.
943 
944  Parameters
945  ----------
946  filename : `str`
947  Name of the configuration file. A configuration file is Python
948  module.
949  root : `str`, optional
950  Name of the variable in file that refers to the config being
951  overridden.
952 
953  For example, the value of root is ``"config"`` and the file
954  contains::
955 
956  config.myField = 5
957 
958  Then this config's field ``myField`` is set to ``5``.
959 
960  **Deprecated:** For backwards compatibility, older config files
961  that use ``root="root"`` instead of ``root="config"`` will be
962  loaded with a warning printed to `sys.stderr`. This feature will be
963  removed at some point.
964 
965  See also
966  --------
967  lsst.pex.config.Config.loadFromStream
968  lsst.pex.config.Config.save
969  lsst.pex.config.Config.saveFromStream
970  """
971  with open(filename, "r") as f:
972  code = compile(f.read(), filename=filename, mode="exec")
973  self.loadFromStream(stream=code, root=root)
974 
975  def loadFromStream(self, stream, root="config", filename=None):
976  """Modify this Config in place by executing the Python code in the
977  provided stream.
978 
979  Parameters
980  ----------
981  stream : file-like object, `str`, or compiled string
982  Stream containing configuration override code.
983  root : `str`, optional
984  Name of the variable in file that refers to the config being
985  overridden.
986 
987  For example, the value of root is ``"config"`` and the file
988  contains::
989 
990  config.myField = 5
991 
992  Then this config's field ``myField`` is set to ``5``.
993 
994  **Deprecated:** For backwards compatibility, older config files
995  that use ``root="root"`` instead of ``root="config"`` will be
996  loaded with a warning printed to `sys.stderr`. This feature will be
997  removed at some point.
998  filename : `str`, optional
999  Name of the configuration file, or `None` if unknown or contained
1000  in the stream. Used for error reporting.
1001 
1002  See also
1003  --------
1004  lsst.pex.config.Config.load
1005  lsst.pex.config.Config.save
1006  lsst.pex.config.Config.saveFromStream
1007  """
1008  with RecordingImporter() as importer:
1009  try:
1010  local = {root: self}
1011  exec(stream, {}, local)
1012  except NameError as e:
1013  if root == "config" and "root" in e.args[0]:
1014  if filename is None:
1015  # try to determine the file name; a compiled string has attribute "co_filename",
1016  # an open file has attribute "name", else give up
1017  filename = getattr(stream, "co_filename", None)
1018  if filename is None:
1019  filename = getattr(stream, "name", "?")
1020  print(f"Config override file {filename!r}"
1021  " appears to use 'root' instead of 'config'; trying with 'root'", file=sys.stderr)
1022  local = {"root": self}
1023  exec(stream, {}, local)
1024  else:
1025  raise
1026 
1027  self._imports.update(importer.getModules())
1028 
1029  def save(self, filename, root="config"):
1030  """Save a Python script to the named file, which, when loaded,
1031  reproduces this config.
1032 
1033  Parameters
1034  ----------
1035  filename : `str`
1036  Desination filename of this configuration.
1037  root : `str`, optional
1038  Name to use for the root config variable. The same value must be
1039  used when loading (see `lsst.pex.config.Config.load`).
1040 
1041  See also
1042  --------
1043  lsst.pex.config.Config.saveToStream
1044  lsst.pex.config.Config.load
1045  lsst.pex.config.Config.loadFromStream
1046  """
1047  d = os.path.dirname(filename)
1048  with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=d) as outfile:
1049  self.saveToStream(outfile, root)
1050  # tempfile is hardcoded to create files with mode '0600'
1051  # for an explantion of these antics see:
1052  # https://stackoverflow.com/questions/10291131/how-to-use-os-umask-in-python
1053  umask = os.umask(0o077)
1054  os.umask(umask)
1055  os.chmod(outfile.name, (~umask & 0o666))
1056  # chmod before the move so we get quasi-atomic behavior if the
1057  # source and dest. are on the same filesystem.
1058  # os.rename may not work across filesystems
1059  shutil.move(outfile.name, filename)
1060 
1061  def saveToStream(self, outfile, root="config"):
1062  """Save a configuration file to a stream, which, when loaded,
1063  reproduces this config.
1064 
1065  Parameters
1066  ----------
1067  outfile : file-like object
1068  Destination file object write the config into. Accepts strings not
1069  bytes.
1070  root
1071  Name to use for the root config variable. The same value must be
1072  used when loading (see `lsst.pex.config.Config.load`).
1073 
1074  See also
1075  --------
1076  lsst.pex.config.Config.save
1077  lsst.pex.config.Config.load
1078  lsst.pex.config.Config.loadFromStream
1079  """
1080  tmp = self._name
1081  self._rename(root)
1082  try:
1083  self._collectImports()
1084  # Remove self from the set, as it is handled explicitly below
1085  self._imports.remove(self.__module__)
1086  configType = type(self)
1087  typeString = _typeStr(configType)
1088  outfile.write(u"import {}\n".format(configType.__module__))
1089  outfile.write(u"assert type({})=={}, 'config is of type %s.%s ".format(root, typeString))
1090  outfile.write(u"instead of {}' % (type({}).__module__, type({}).__name__)\n".format(typeString,
1091  root,
1092  root))
1093  for imp in self._imports:
1094  if imp in sys.modules and sys.modules[imp] is not None:
1095  outfile.write(u"import {}\n".format(imp))
1096  self._save(outfile)
1097  finally:
1098  self._rename(tmp)
1099 
1100  def freeze(self):
1101  """Make this config, and all subconfigs, read-only.
1102  """
1103  self._frozen = True
1104  for field in self._fields.values():
1105  field.freeze(self)
1106 
1107  def _save(self, outfile):
1108  """Save this config to an open stream object.
1109 
1110  Parameters
1111  ----------
1112  outfile : file-like object
1113  Destination file object write the config into. Accepts strings not
1114  bytes.
1115  """
1116  for field in self._fields.values():
1117  field.save(outfile, self)
1118 
1119  def _collectImports(self):
1120  """Adds module containing self to the list of things to import and
1121  then loops over all the fields in the config calling a corresponding
1122  collect method. The field method will call _collectImports on any configs
1123  it may own and return the set of things to import. This returned set
1124  will be merged with the set of imports for this config class.
1125  """
1126  self._imports.add(self.__module__)
1127  for name, field in self._fields.items():
1128  field._collectImports(self, self._imports)
1129 
1130  def toDict(self):
1131  """Make a dictionary of field names and their values.
1132 
1133  Returns
1134  -------
1135  dict_ : `dict`
1136  Dictionary with keys that are `~lsst.pex.config.Field` names.
1137  Values are `~lsst.pex.config.Field` values.
1138 
1139  See also
1140  --------
1141  lsst.pex.config.Field.toDict
1142 
1143  Notes
1144  -----
1145  This method uses the `~lsst.pex.config.Field.toDict` method of
1146  individual fields. Subclasses of `~lsst.pex.config.Field` may need to
1147  implement a ``toDict`` method for *this* method to work.
1148  """
1149  dict_ = {}
1150  for name, field in self._fields.items():
1151  dict_[name] = field.toDict(self)
1152  return dict_
1153 
1154  def names(self):
1155  """Get all the field names in the config, recursively.
1156 
1157  Returns
1158  -------
1159  names : `list` of `str`
1160  Field names.
1161  """
1162  #
1163  # Rather than sort out the recursion all over again use the
1164  # pre-existing saveToStream()
1165  #
1166  with io.StringIO() as strFd:
1167  self.saveToStream(strFd, "config")
1168  contents = strFd.getvalue()
1169  strFd.close()
1170  #
1171  # Pull the names out of the dumped config
1172  #
1173  keys = []
1174  for line in contents.split("\n"):
1175  if re.search(r"^((assert|import)\s+|\s*$|#)", line):
1176  continue
1177 
1178  mat = re.search(r"^(?:config\.)?([^=]+)\s*=\s*.*", line)
1179  if mat:
1180  keys.append(mat.group(1))
1181 
1182  return keys
1183 
1184  def _rename(self, name):
1185  """Rename this config object in its parent `~lsst.pex.config.Config`.
1186 
1187  Parameters
1188  ----------
1189  name : `str`
1190  New name for this config in its parent `~lsst.pex.config.Config`.
1191 
1192  Notes
1193  -----
1194  This method uses the `~lsst.pex.config.Field.rename` method of
1195  individual `lsst.pex.config.Field` instances.
1196  `lsst.pex.config.Field` subclasses may need to implement a ``rename``
1197  method for *this* method to work.
1198 
1199  See also
1200  --------
1201  lsst.pex.config.Field.rename
1202  """
1203  self._name = name
1204  for field in self._fields.values():
1205  field.rename(self)
1206 
1207  def validate(self):
1208  """Validate the Config, raising an exception if invalid.
1209 
1210  Raises
1211  ------
1212  lsst.pex.config.FieldValidationError
1213  Raised if verification fails.
1214 
1215  Notes
1216  -----
1217  The base class implementation performs type checks on all fields by
1218  calling their `~lsst.pex.config.Field.validate` methods.
1219 
1220  Complex single-field validation can be defined by deriving new Field
1221  types. For convenience, some derived `lsst.pex.config.Field`-types
1222  (`~lsst.pex.config.ConfigField` and
1223  `~lsst.pex.config.ConfigChoiceField`) are defined in `lsst.pex.config`
1224  that handle recursing into subconfigs.
1225 
1226  Inter-field relationships should only be checked in derived
1227  `~lsst.pex.config.Config` classes after calling this method, and base
1228  validation is complete.
1229  """
1230  for field in self._fields.values():
1231  field.validate(self)
1232 
1233  def formatHistory(self, name, **kwargs):
1234  """Format a configuration field's history to a human-readable format.
1235 
1236  Parameters
1237  ----------
1238  name : `str`
1239  Name of a `~lsst.pex.config.Field` in this config.
1240  kwargs
1241  Keyword arguments passed to `lsst.pex.config.history.format`.
1242 
1243  Returns
1244  -------
1245  history : `str`
1246  A string containing the formatted history.
1247 
1248  See also
1249  --------
1250  lsst.pex.config.history.format
1251  """
1252  import lsst.pex.config.history as pexHist
1253  return pexHist.format(self, name, **kwargs)
1254 
1255  history = property(lambda x: x._history)
1256  """Read-only history.
1257  """
1258 
1259  def __setattr__(self, attr, value, at=None, label="assignment"):
1260  """Set an attribute (such as a field's value).
1261 
1262  Notes
1263  -----
1264  Unlike normal Python objects, `~lsst.pex.config.Config` objects are
1265  locked such that no additional attributes nor properties may be added
1266  to them dynamically.
1267 
1268  Although this is not the standard Python behavior, it helps to protect
1269  users from accidentally mispelling a field name, or trying to set a
1270  non-existent field.
1271  """
1272  if attr in self._fields:
1273  if self._fields[attr].deprecated is not None:
1274  fullname = _joinNamePath(self._name, self._fields[attr].name)
1275  warnings.warn(f"Config field {fullname} is deprecated: {self._fields[attr].deprecated}",
1276  FutureWarning)
1277  if at is None:
1278  at = getCallStack()
1279  # This allows Field descriptors to work.
1280  self._fields[attr].__set__(self, value, at=at, label=label)
1281  elif hasattr(getattr(self.__class__, attr, None), '__set__'):
1282  # This allows properties and other non-Field descriptors to work.
1283  return object.__setattr__(self, attr, value)
1284  elif attr in self.__dict__ or attr in ("_name", "_history", "_storage", "_frozen", "_imports"):
1285  # This allows specific private attributes to work.
1286  self.__dict__[attr] = value
1287  else:
1288  # We throw everything else.
1289  raise AttributeError("%s has no attribute %s" % (_typeStr(self), attr))
1290 
1291  def __delattr__(self, attr, at=None, label="deletion"):
1292  if attr in self._fields:
1293  if at is None:
1294  at = getCallStack()
1295  self._fields[attr].__delete__(self, at=at, label=label)
1296  else:
1297  object.__delattr__(self, attr)
1298 
1299  def __eq__(self, other):
1300  if type(other) == type(self):
1301  for name in self._fields:
1302  thisValue = getattr(self, name)
1303  otherValue = getattr(other, name)
1304  if isinstance(thisValue, float) and math.isnan(thisValue):
1305  if not math.isnan(otherValue):
1306  return False
1307  elif thisValue != otherValue:
1308  return False
1309  return True
1310  return False
1311 
1312  def __ne__(self, other):
1313  return not self.__eq__(other)
1314 
1315  def __str__(self):
1316  return str(self.toDict())
1317 
1318  def __repr__(self):
1319  return "%s(%s)" % (
1320  _typeStr(self),
1321  ", ".join("%s=%r" % (k, v) for k, v in self.toDict().items() if v is not None)
1322  )
1323 
1324  def compare(self, other, shortcut=True, rtol=1E-8, atol=1E-8, output=None):
1325  """Compare this configuration to another `~lsst.pex.config.Config` for
1326  equality.
1327 
1328  Parameters
1329  ----------
1330  other : `lsst.pex.config.Config`
1331  Other `~lsst.pex.config.Config` object to compare against this
1332  config.
1333  shortcut : `bool`, optional
1334  If `True`, return as soon as an inequality is found. Default is
1335  `True`.
1336  rtol : `float`, optional
1337  Relative tolerance for floating point comparisons.
1338  atol : `float`, optional
1339  Absolute tolerance for floating point comparisons.
1340  output : callable, optional
1341  A callable that takes a string, used (possibly repeatedly) to
1342  report inequalities.
1343 
1344  Returns
1345  -------
1346  isEqual : `bool`
1347  `True` when the two `lsst.pex.config.Config` instances are equal.
1348  `False` if there is an inequality.
1349 
1350  See also
1351  --------
1352  lsst.pex.config.compareConfigs
1353 
1354  Notes
1355  -----
1356  Unselected targets of `~lsst.pex.config.RegistryField` fields and
1357  unselected choices of `~lsst.pex.config.ConfigChoiceField` fields
1358  are not considered by this method.
1359 
1360  Floating point comparisons are performed by `numpy.allclose`.
1361  """
1362  name1 = self._name if self._name is not None else "config"
1363  name2 = other._name if other._name is not None else "config"
1364  name = getComparisonName(name1, name2)
1365  return compareConfigs(name, self, other, shortcut=shortcut,
1366  rtol=rtol, atol=atol, output=output)
1367 
1368 
1369 def unreduceConfig(cls, stream):
1370  """Create a `~lsst.pex.config.Config` from a stream.
1371 
1372  Parameters
1373  ----------
1374  cls : `lsst.pex.config.Config`-type
1375  A `lsst.pex.config.Config` type (not an instance) that is instantiated
1376  with configurations in the ``stream``.
1377  stream : file-like object, `str`, or compiled string
1378  Stream containing configuration override code.
1379 
1380  Returns
1381  -------
1382  config : `lsst.pex.config.Config`
1383  Config instance.
1384 
1385  See also
1386  --------
1387  lsst.pex.config.Config.loadFromStream
1388  """
1389  config = cls()
1390  config.loadFromStream(stream)
1391  return config
def toDict(self, instance)
Definition: config.py:461
def __eq__(self, other)
Definition: config.py:1299
def formatHistory(self, name, kwargs)
Definition: config.py:1233
def load(self, filename, root="config")
Definition: config.py:940
def _rename(self, name)
Definition: config.py:1184
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:105
def unreduceConfig(cls, stream)
Definition: config.py:1369
def _save(self, outfile)
Definition: config.py:1107
def __init__(self, field, config, msg)
Definition: config.py:152
def saveToStream(self, outfile, root="config")
Definition: config.py:1061
def __delattr__(self, attr, at=None, label="deletion")
Definition: config.py:1291
daf::base::PropertySet * set
Definition: fits.cc:884
def getCallStack(skip=0)
Definition: callStack.py:169
def __setattr__(cls, name, value)
Definition: config.py:131
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:488
def save(self, filename, root="config")
Definition: config.py:1029
def __ne__(self, other)
Definition: config.py:1312
def getStackFrame(relative=0)
Definition: callStack.py:52
def __contains__(self, name)
Return True if the specified field exists in this config.
Definition: config.py:815
table::Key< int > type
Definition: Detector.cc:167
def save(self, outfile, instance)
Definition: config.py:429
def _validateValue(self, value)
Definition: config.py:388
def validate(self, instance)
Definition: config.py:342
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
def __new__(cls, args, kw)
Definition: config.py:822
def __init__(cls, name, bases, dict_)
Definition: config.py:110
def __setattr__(self, attr, value, at=None, label="assignment")
Definition: config.py:1259
def freeze(self, instance)
Definition: config.py:371
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:56
def __init__(self, doc, dtype, default=None, check=None, optional=False, deprecated=None)
Definition: config.py:270
def __set__(self, instance, value, at=None, label='assignment')
Definition: config.py:506
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:278
def loadFromStream(self, stream, root="config", filename=None)
Definition: config.py:975
def compare(self, other, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: config.py:1324
daf::base::PropertyList * list
Definition: fits.cc:885
def getComparisonName(name1, name2)
Definition: comparison.py:34
def rename(self, instance)
Definition: config.py:321
def __delete__(self, instance, at=None, label='deletion')
Definition: config.py:563
def find_module(self, fullname, path=None)
Definition: config.py:656