Rules¶
Use the class decorator¶
Decorate an attrs-based class with @xattree instead of @define. Stop here and you won’t notice a difference. But there is acrimony under your feet. Rays of sun and hostile glances filter through your tree’s bare branches.
Use the field decorators¶
Replace attrs.field() with xattree.dim(), coord(), and array(), and field() as appropriate.
use
coord()if your variable is a dimension coordinate arrayuse
dim()if your variable is an integer indicating the size of a dimensionuse
array()for array variables, ideally specifying their shape by reference todimsdefined in the same or anotherxattree-decorated classuse
field()for arbitrary attributes or child components — the former go intoDataTree.attrs, the latter are described in more detail belowUse
attrs.field()for attributes you wantxattreeto ignore — these will not be moved from your object’s__dict__to the tree
Claws grapple for place. A hierarchy forms, seemingly of its own accord. Soon there is peace.
Array shape resolution¶
When you initialize an instance with an array field, xattree will try to look up the array’s shape by its dimension names, if you have specified any. The size of each dimension may be a field in another component somewhere above the current class in your object hierarchy. If you provide an array value, the shape will be checked. If you don’t provide a value at init time, and the array has a default (scalar) value, it will be expanded with np.full to the proper shape.
Conversion and validation¶
Like attrs, xattree supports automatic conversion of field values using the converter parameter, and field validation using the validator parameter. This can be useful for mapping values from a format convenient for user input to a more canonical type, e.g. converting “sparse” list input into an array.
Note: array conversion and validation runs after the attrs initialization procedure is complete. All other conversions/validations are piped through the attrs mechanisms. Provided you use an attrs.Converter with takes_self=True, this gives your array conversion functions access to the instance __dict__ and everything sent to it through __init__ method arguments, including explicit dimensions and/or parent components whose dimensions the given component may inherit.
Define your object model¶
Besides applying the decorators as described above, develop your attrs-based object model as you otherwise would.
At import time, xattree walks your domain to discover its structure. Where it discovers a field() whose type is either another xattree node or an Optional, Mapping or Iterable of such, it inspects it recursively.
Children¶
When a field is another xattree-decorated class, or an Optional, Mapping or Iterable of such, we refer to it as a child. In the former two cases we have an only child, in the latter two we have child collections. xattree will “flatten” child collections — each element of the child collection is attached as a child datatree node instead of attaching the collection itself.
Use a Mapping or Iterable if you don’t want to name your children up front (i.e. when you define your object model) but at their moment of birth, which is perhaps understandable. Using an Iterable is effectively to declare that you don’t want to have to name them, which, while deplorable among our kind, is standard feline conduct, therefore xattree grudgingly accepts but insists on naming anonymous children behind your back, appending an auto-incrementing integer to the name of their field.
Note: While xattree will raise an error at runtime if a user-specified or auto-generated name collides with another fields, it’s best to name fields such that collisions are impossible.
Note: You may not reassign a child to a different parent if it already has one.
Note: Children may be associated with parents via the parent parameter to the __init__ method only if xattree can unambiguously identify which of the parent’s fields the child corresponds to. This is not possible if the parent has multiple fields of the same child type (or Optional, Iterable or Mapping of such).
Bring your own conventions¶
While the family Felidae are social creatures of hierarchy and routine, none can fathom — nor would abide, if they could — such a thing as convention. The same goes for xattree.
If you want your objects to be easily consumed by discretization-aware libraries e.g. xugrid or uxarray, you must either arrange your xattree-decorated class in such a way as to conform to the relevant convention(s) after translation into an xarray.Dataset, or write your own translation layer between them.
No rearranging the furniture¶
By default, xattree names the datatree attribute data. To name it something else, use xattree(where="...").
Unlike typical appointments of wood and fabric, wherever you put the tree, that’s where it stays. If you try to move it (at runtime), things will break. If you regret your choice, tough luck — find a new apartment (i.e. kill the program and set where to something else).
Note: some names are reserved, namely the core xattree-managed fields (name, parent, children, dims, and strict).