pyiron_base.storage.has_stored_traits.HasStoredTraits#

class pyiron_base.storage.has_stored_traits.HasStoredTraits(**kwargs: Any)[source]#

Bases: HasTraits, HasStorage, ABC

A base class for input to pyiron jobs, combining pyiron’s HasStorage and traitlets.HasTraits for ease of access and validation/callbacks.

Child classes can define traits like a normal HasTraits child, and these get automatically put in storage and serialized when to/from_hdf is called.

read_only#

Whether the traits (recursively in case and traits are also of this class) are allowed to be updated or are read-only. This attribute is itself read-only, but can be updated with the lock() and unlock() methods. (Default is False, allow both reading and writing of traits.)

Type:

bool

Note

If any of your traits are complex objects that pyiron_base.DataContainer doesn’t already know how to serialize, you will need to make sure these objects have their own to/from_hdf methods, e.g. by making sure they inherit from HasHDF and defining their _to/_from_hdf methods accordingly.

Note

If you write __init__ in any child class, be sure to pass super().__init__(*args, group_name=group_name, **kwargs) to ensure that the group name for HasStorage gets set, at the trait values (if any are set during instantiation) get set.

Example

>>> from traitlets import (
...     Bool,
...     default,
...     Instance,
...     Int,
...     List,
...     observe,
...     TraitError,
...     TraitType,
...     Unicode,
...     validate
... )
>>>
>>> from pyiron_base.interfaces.has_hdf import HasHDF
>>> from pyiron_base.storage.has_stored_traits import HasStoredTraits
>>>
>>>
>>> class Omelette(HasStoredTraits):
...     '''
...     A toy model for cooking an omelette with traitlets.
...     '''
...
...     # The traits
...     n_eggs = Int(default_value=2)
...     acceptable = Bool()
...     ingredients = List(default_value=[], trait=Unicode())
...
...     @default('acceptable')
...     def wait_for_a_complaint(self):
...         '''
...         Default values can be assigned using the keyword, for mutable defaults always use a separate
...         function decorated with the `@default` decorator.
...         '''
...         return True
...
...     @validate('n_eggs')
...     def _gotta_crack_some_eggs(self, proposal):
...         '''
...         Validation proposals have the keys `['trait', 'value', 'owner']`.
...         The returned value is assigned to the trait, so you can do coercion here, or if all is well simply
...         return `proposal['value']`
...         '''
...         if proposal['value'] <= 0:
...             raise TraitError(
...                 f"You gotta crack some eggs to make a omelette, but asked for {proposal['value']}."
...             )
...         return proposal['value']
...
...     @observe('ingredients')
...     def _picky_eater(self, change):
...         '''
...         Observation changes have the keys `['name', 'old', 'new', 'owner', 'type']`.
...         Observations can also be set up with a method call, like
...         `self.observe(_picky_eater, names=['ingredients']`.
...         They don't need to return anything.
...         '''
...         wont_eat = ['mushrooms', 'zucchini']
...         for picky in wont_eat:
...             if picky in change['new']:
...                 self.acceptable = False
>>>
>>>
>>> class Beverage(HasHDF):
...     '''
...     We can store custom objects in our input classes, but since they will ultimately be passed to a
...     `pyiron_base.DataContainer` for serialization, they either need to be of a type that `DataContainer`
...     can already handle, or they'll need to have `to_hdf` and `from_hdf` methods, e.g. by inheriting from
...     `pyiron_base.HasHDF` or `pyiron_base.HasStorage`.
...     '''
...     _types = ['coffee', 'tea', 'orange juice', 'water']
...
...     def __init__(self, type_='coffee'):
...         if type_ not in self._types:
...             raise ValueError(f"The beverage type must be chosen from {self._types}")
...         self.type_ = type_
...
...     def __repr__(self):
...         return self.type_
...
...     def _to_hdf(self, hdf):
...         hdf['drink_type'] = self.type_
...
...     def _from_hdf(self, hdf, version=None):
...         self.type_ = hdf['drink_type']
>>>
>>>
>>> class CaffeinatedTrait(TraitType):
...     '''
...     We can even make our own trait types.
...
...     In this case, our default value is mutable, so we need to be careful! Normally we would just assign the
...     default to `default_value` (shown bug commented out). For mutable types we instead need to define a
...     function with the name `make_dynamic_default`.
...
...     (This is not well documented in readthedocs for traitlets, but is easy to see in the source code for
...     `TraitType`)
...     '''
...     # default_value = Beverage('coffee')  # DON'T DO THIS WITH MUTABLE DEFAULTS
...     def make_dynamic_default(self):  # Do this instead
...         return Beverage('coffee')
...
...     def validate(self, obj, value):
...         '''
...         Let's just make sure it's a caffeinated beverage.
...
...         Validations should return the value if everything works fine (maybe after some coercion), and hit
...         `self.error(obj, value)` if something goes wrong.
...         '''
...         if not isinstance(value, Beverage):
...             self.error(obj, value)
...         elif value.type_ not in ['coffee', 'tea']:
...             raise TraitError(f"Expected a caffeinated beverage, but got {value.type_}")
...         return value
>>>
>>>
>>> class HasDrink(HasStoredTraits):
...     '''
...     We can use our special trait type in `HasTraits` classes, but a lot of the time it will be overkill
...     thanks to the `Instance` trait type. In this case we can accomplish the same functionality with
...     `@default` and `@validate` decorators.
...     '''
...     drink1 = CaffeinatedTrait()
...     drink2 = Instance(klass=Beverage)
...
...     @default('drink2')
...     def _default_drink2(self):
...         '''
...         Similar to the danger with a custom `TraitType`, we can't just use
...         '''
...         return Beverage('orange juice')
...
...     @validate('drink2')
...     def _non_caffeinated(self, proposal):
...         if proposal['value'].type_ not in ['orange juice', 'water']:
...         raise TraitError(
...             f"Expected a beverage of type 'orange juice' or 'water', but got {proposal['value'].type_}"
...         )
...         return proposal['value']
>>>
>>>
>>> class ComposedBreakfast(Omelette, HasDrink):
...     '''
...     We can then put our Input children together very easily in a composition pattern.
...     Just don't forget to call `super().__init__(*args, **kwargs)` any time you override `__init__` to make sure
...     initialization of the traits gets passed through the MRO appropriately.
...     '''
...     pass
>>>
>>>
>>> class NestedBreakfast(Omelette):
...     '''
...     We can also nest input classes together.
...
...     Again, since our trait is an instance of something mutable, we want to use the `@default` decorator instead of the
...     `default_value` kwarg.
...     '''
...     drinks = Instance(klass=HasDrink)
...
...     @default('drinks')
...     def _drinks_default(self):
...         return HasDrink()

Now let’s look at a few features in action.

We can pass trait values in at initialization: >>> Omelette(n_eggs=3).n_eggs == 3 True

We can get traits to update automatically based on the value of other traits: >>> omelette = Omelette() >>> omelette.ingredients = [‘ham’, ‘mushrooms’] >>> omelette.acceptable False

But we need to be careful, because we can observe internal changes to mutable traits! >>> omelette = Omelette() >>> omelette.ingredients.append(‘zucchini’) # Unacceptable! >>> omelette.acceptable True

We saw that we could combine different subclasses together either by composition or by nesting. These are both totally valid choices, and it just depends what you want your data access to look like – deep or wide?

However, when we choose the nested architecture, that means we have a mutable trait, and we need to be careful with those: we warned about using a mutable object as a default value, however, make_dynamic_default method, we safely get separate instances for each trait owner: >>> cb = ComposedBreakfast() >>> nb = NestedBreakfast() >>> cb.drink1 == nb.drinks.drink1 False

Similarly, when defining our trait with the Instance type and using the @default decorator: >>> cb.drink2 == np.drinks.drink2 False

We can (recursively) change the traits to read-only (read/write) mode using the lock() (unlock()) method, e.g. if the child class is being used as input for a job you may want to lock the input when the job is run. >>> nb.lock() >>> nb.drinks.drink2 = Beverage(‘tea’) RuntimeError: HasDrink is locked, so the trait drink2 cannot be updated to tea. Call .unlock() first if you’re sure you know what you’re doing.

Be a bit careful though, since as with observer callbacks mutable traits can still be mutated: >>> nb.drinks.drink2.type_ = ‘tea’ >>> nb.drinks.drink2 tea

Further reading: the tests for this class use the same examples we have here, but are more in depth.

__init__(*args: Any, **kwargs: Any) None#

Methods

__init__(*args, **kwargs)

add_traits(**traits)

Dynamically add trait attributes to the HasTraits instance.

class_own_trait_events(name)

Get a dict of all event handlers defined on this class, not a parent.

class_own_traits(**metadata)

Get a dict of all the traitlets defined on this class, not a parent.

class_trait_names(**metadata)

Get a list of all the names of this class' traits.

class_traits(**metadata)

Get a dict of all the traits of this class.

from_hdf(hdf[, group_name])

Read object to HDF.

from_hdf_args(hdf)

Read arguments for instance creation from HDF5 file.

has_trait(name)

Returns True if the object has a trait with the specified name.

hold_trait_notifications()

Context manager for bundling trait change notifications and cross validation.

lock()

Recursively make all traits read-only.

notify_change(change)

Notify observers of a change event

observe(handler[, names, type])

Setup a handler to be called when a trait changes.

on_trait_change([handler, name, remove])

DEPRECATED: Setup a handler to be called when a trait changes.

rewrite_hdf(hdf[, group_name])

Update the HDF representation.

set_trait(name, value)

Forcibly sets trait attribute, including read-only attributes.

setup_instance(**kwargs)

This is called before self.__init__ is called.

to_hdf(hdf[, group_name])

Write object to HDF.

trait_defaults(*names, **metadata)

Return a trait's default value or a dictionary of them

trait_events([name])

Get a dict of all the event handlers of this class.

trait_has_value(name)

Returns True if the specified trait has a value.

trait_metadata(traitname, key[, default])

Get metadata values for trait by key.

trait_names(**metadata)

Get a list of all the names of this class' traits.

trait_values(**metadata)

A dict of trait names and their values.

traits(**metadata)

Get a dict of all the traits of this class.

unlock()

Recursively make all traits both readable and writeable.

unobserve(handler[, names, type])

Remove a trait change handler.

unobserve_all([name])

Remove trait change handlers of any type for the specified name.

Attributes

cross_validation_lock

A contextmanager for running a block with our cross validation lock set to True.

read_only

Get the read-only status of the traits.

storage

add_traits(**traits: Any) None#

Dynamically add trait attributes to the HasTraits instance.

classmethod class_own_trait_events(name: str) dict[str, EventHandler]#

Get a dict of all event handlers defined on this class, not a parent.

Works like event_handlers, except for excluding traits from parents.

classmethod class_own_traits(**metadata: Any) dict[str, TraitType[Any, Any]]#

Get a dict of all the traitlets defined on this class, not a parent.

Works like class_traits, except for excluding traits from parents.

classmethod class_trait_names(**metadata: Any) list[str]#

Get a list of all the names of this class’ traits.

This method is just like the trait_names() method, but is unbound.

classmethod class_traits(**metadata: Any) dict[str, TraitType[Any, Any]]#

Get a dict of all the traits of this class. The dictionary is keyed on the name and the values are the TraitType objects.

This method is just like the traits() method, but is unbound.

The TraitTypes returned don’t know anything about the values that the various HasTrait’s instances are holding.

The metadata kwargs allow functions to be passed in which filter traits based on metadata values. The functions should take a single value as an argument and return a boolean. If any function returns False, then the trait is not included in the output. If a metadata key doesn’t exist, None will be passed to the function.

property cross_validation_lock: Any#

A contextmanager for running a block with our cross validation lock set to True.

At the end of the block, the lock’s value is restored to its value prior to entering the block.

from_hdf(hdf: ProjectHDFio, group_name: str = None)#

Read object to HDF.

If group_name is given descend into subgroup in hdf first.

Parameters:
  • hdf (ProjectHDFio) – HDF group to read from

  • group_name (str, optional) – name of subgroup

classmethod from_hdf_args(hdf: ProjectHDFio) dict#

Read arguments for instance creation from HDF5 file.

Parameters:

hdf (ProjectHDFio) – HDF5 group object

Returns:

arguments that can be **kwarg-passed to cls().

Return type:

dict

has_trait(name: str) bool#

Returns True if the object has a trait with the specified name.

hold_trait_notifications() Any#

Context manager for bundling trait change notifications and cross validation.

Use this when doing multiple trait assignments (init, config), to avoid race conditions in trait notifiers requesting other trait values. All trait notifications will fire after all values have been assigned.

lock() None[source]#

Recursively make all traits read-only.

notify_change(change: Bunch) None#

Notify observers of a change event

observe(handler: Callable[[...], Any], names: Sentinel | str | Iterable[Sentinel | str] = traitlets.All, type: Sentinel | str = 'change') None#

Setup a handler to be called when a trait changes.

This is used to setup dynamic notifications of trait changes.

Parameters:
  • handler (callable) – A callable that is called when a trait changes. Its signature should be handler(change), where change is a dictionary. The change dictionary at least holds a ‘type’ key. * type: the type of notification. Other keys may be passed depending on the value of ‘type’. In the case where type is ‘change’, we also have the following keys: * owner : the HasTraits instance * old : the old value of the modified trait attribute * new : the new value of the modified trait attribute * name : the name of the modified trait attribute.

  • names (list, str, All) – If names is All, the handler will apply to all traits. If a list of str, handler will apply to all names in the list. If a str, the handler will apply just to that name.

  • type (str, All (default: 'change')) – The type of notification to filter by. If equal to All, then all notifications are passed to the observe handler.

on_trait_change(handler: EventHandler | None = None, name: Sentinel | str | None = None, remove: bool = False) None#

DEPRECATED: Setup a handler to be called when a trait changes.

This is used to setup dynamic notifications of trait changes.

Static handlers can be created by creating methods on a HasTraits subclass with the naming convention ‘_[traitname]_changed’. Thus, to create static handler for the trait ‘a’, create the method _a_changed(self, name, old, new) (fewer arguments can be used, see below).

If remove is True and handler is not specified, all change handlers for the specified name are uninstalled.

Parameters:
  • handler (callable, None) – A callable that is called when a trait changes. Its signature can be handler(), handler(name), handler(name, new), handler(name, old, new), or handler(name, old, new, self).

  • name (list, str, None) – If None, the handler will apply to all traits. If a list of str, handler will apply to all names in the list. If a str, the handler will apply just to that name.

  • remove (bool) – If False (the default), then install the handler. If True then unintall it.

property read_only: bool#

Get the read-only status of the traits.

Returns:

True if the traits are read-only, False otherwise.

Return type:

bool

rewrite_hdf(hdf: ProjectHDFio, group_name: str = None)#

Update the HDF representation.

If an object is read from an older layout, this will remove the old data and rewrite it in the newest layout.

Parameters:
  • hdf (ProjectHDFio) – HDF group to read/write

  • group_name (str, optional) – name of subgroup

set_trait(name: str, value: Any) None#

Forcibly sets trait attribute, including read-only attributes.

setup_instance(**kwargs)[source]#

This is called before self.__init__ is called.

Overrides HasTraits.setup_instance, which gets called in HasTraits.__new__ and initializes instances of the traits on self. Since we override __setattr__ to depend on the attribute _read_only, we need to make sure this is the very first attribute that gets set!

to_hdf(hdf: ProjectHDFio, group_name: str = None)#

Write object to HDF.

If group_name is given create a subgroup in hdf first.

Parameters:
  • hdf (ProjectHDFio) – HDF group to write to

  • group_name (str, optional) – name of subgroup

trait_defaults(*names: str, **metadata: Any) dict[str, Any] | Sentinel#

Return a trait’s default value or a dictionary of them

Notes

Dynamically generated default values may depend on the current state of the object.

classmethod trait_events(name: str | None = None) dict[str, EventHandler]#

Get a dict of all the event handlers of this class.

Parameters:

name (str (default: None)) – The name of a trait of this class. If name is None then all the event handlers of this class will be returned instead.

Return type:

The event handlers associated with a trait name, or all event handlers.

trait_has_value(name: str) bool#

Returns True if the specified trait has a value.

This will return false even if getattr would return a dynamically generated default value. These default values will be recognized as existing only after they have been generated.

Example

class MyClass(HasTraits):
    i = Int()


mc = MyClass()
assert not mc.trait_has_value("i")
mc.i  # generates a default value
assert mc.trait_has_value("i")
trait_metadata(traitname: str, key: str, default: Any = None) Any#

Get metadata values for trait by key.

trait_names(**metadata: Any) list[str]#

Get a list of all the names of this class’ traits.

trait_values(**metadata: Any) dict[str, Any]#

A dict of trait names and their values.

The metadata kwargs allow functions to be passed in which filter traits based on metadata values. The functions should take a single value as an argument and return a boolean. If any function returns False, then the trait is not included in the output. If a metadata key doesn’t exist, None will be passed to the function.

Return type:

A dict of trait names and their values.

Notes

Trait values are retrieved via getattr, any exceptions raised by traits or the operations they may trigger will result in the absence of a trait value in the result dict.

traits(**metadata: Any) dict[str, TraitType[Any, Any]]#

Get a dict of all the traits of this class. The dictionary is keyed on the name and the values are the TraitType objects.

The TraitTypes returned don’t know anything about the values that the various HasTrait’s instances are holding.

The metadata kwargs allow functions to be passed in which filter traits based on metadata values. The functions should take a single value as an argument and return a boolean. If any function returns False, then the trait is not included in the output. If a metadata key doesn’t exist, None will be passed to the function.

unlock() None[source]#

Recursively make all traits both readable and writeable.

unobserve(handler: Callable[[...], Any], names: Sentinel | str | Iterable[Sentinel | str] = traitlets.All, type: Sentinel | str = 'change') None#

Remove a trait change handler.

This is used to unregister handlers to trait change notifications.

Parameters:
  • handler (callable) – The callable called when a trait attribute changes.

  • names (list, str, All (default: All)) – The names of the traits for which the specified handler should be uninstalled. If names is All, the specified handler is uninstalled from the list of notifiers corresponding to all changes.

  • type (str or All (default: 'change')) – The type of notification to filter by. If All, the specified handler is uninstalled from the list of notifiers corresponding to all types.

unobserve_all(name: str | Any = traitlets.All) None#

Remove trait change handlers of any type for the specified name. If name is not specified, removes all trait notifiers.