Skip to content

FoSpy.blocks.treatments

This site only contains documentation for the Block subclasses defined in this module. For a complete reference of all functions, classes, and variables, see the full documentation.

Block Types in this Module


AnnealProgram

Bases: ListBlock

Methods:

Name Description
Simple

Creates a simple subclass of ListBlock

__eq__

Check equality of two ListBlock objects.

__getitem__

Get an item from this ListBlock by index.

__hash__

Get the hash of this ListBlock object

__init__

Constructs a ListBlock from a list of objects or serialized dictionaries.

__iter__

Iterate over the items in this ListBlock

__len__

Get the number of items in this ListBlock

__setattr__

Only private attributes starting with "_" can be set.

__setitem__

Set an item to this ListBlock by index.

_subprocess
add_all_missing_parameters
add_comments

Attach comments to self in the parent block.

append
clear_all_comments
clear_comments

Remove comments attached to self in the parent block.

copy

Returns a deep copy by serializing and then reconstructing.

default_key_order
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_any
get_first
insert

Insert a SingleBlock-coercable object into this ListBlock.

list_avail_routines

Lists all methods decorated as calc routines.

refresh_attachments
remove_any

Remove any objects from self._objs with attributes matching kwargs

remove_idx

Remove a range of items from this ListBlock

serialize

Serialize this ListBlock as a list of dictionaries.

set_list_type

Set FOS list formatting (explicit or looped).

track_attachments
Source code in FoSpy/blocks/treatments.py
class AnnealProgram(ListBlock):
    _reqCls = AnnealSection
    def append(self, obj):
        super().append(obj)
        if hasattr(self, "_parent_block") and self._parent_block is not None:
            self._parent_block.build_profile()

    @_calc_routine()
    def add_all_missing_parameters(self):
        for section in self:
            if hasattr(section, "add_missing_parameter"):
                section.add_missing_parameter()

_objs = blockList instance-attribute

_reqCls = AnnealSection class-attribute instance-attribute

Simple(reqCls=SingleBlock) classmethod

Creates a simple subclass of ListBlock

Creates a subclass of ListBlock that only accepts objects of the specified SingleBlock subclass.

Simple ListBlocks are used when no specialized methods or attributes are needed.

Parameters:

Name Type Description Default
reqCls

The subclass of SingleBlock that this ListBlock subclass accepts.

SingleBlock
Source code in FoSpy/blocks/blocks.py
@classmethod
def Simple(cls, reqCls=SingleBlock):
    """
    Creates a simple subclass of `ListBlock`

    Creates a subclass of `ListBlock` that only accepts objects of the
    specified `SingleBlock` subclass.

    Simple ListBlocks are used when no specialized methods or attributes are
    needed.

    Args:
        reqCls:
            The subclass of `SingleBlock` that this `ListBlock` subclass
            accepts.
    """
    if not issubclass(reqCls, SingleBlock):
        raise TypeError("reqCls must be a subclass of SingleBlock")
    if cls._reqCls is not None:
        raise TypeError("You cannot create a simple subclass of another ListBlock subclass.")

    class SimpleSub(cls):
        _reqCls = reqCls

    name = f"{reqCls.__name__}List"
    qualname = f"{cls.__name__}.Simple.{name}"
    module = reqCls.__module__

    SimpleSub.__name__ = name
    SimpleSub.__qualname__ = qualname
    SimpleSub.__module__ = module

    return SimpleSub

__eq__(other, suppress_routine_paths=False)

Check equality of two ListBlock objects.

Equality is checked by a deep difference of their serialized lists.

Parameters:

Name Type Description Default
other

The other ListBlock object to check equality with

required
suppress_routine_paths

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths=False):
    """
    Check equality of two `ListBlock` objects.

    Equality is checked by a deep difference of their serialized lists.

    Args:
        other:
            The other `ListBlock` object to check equality with
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff
    try:
        return len(deep_diff(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths))==0
    except:
        return False

__getitem__(idx)

Get an item from this ListBlock by index.

Parameters:

Name Type Description Default
idx int

The index of the item

required
Source code in FoSpy/blocks/blocks.py
def __getitem__(self, idx:int):
    """
    Get an item from this `ListBlock` by index.

    Args:
        idx:
            The index of the item
    """
    return self._objs[idx]

__hash__()

Get the hash of this ListBlock object

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    """
    Get the hash of this `ListBlock` object
    """
    return id(self)

__init__(blockList)

Constructs a ListBlock from a list of objects or serialized dictionaries.

Each item in blockList is checked against the SingleBlock subclass specified for the ListBlock subclass. If the item is not the correct subclass, it is passed to the SingleBlock subclass's dispatch_subclass method for coersion.

Parameters:

Name Type Description Default
blockList list

A list containing either dicts or SingleBlock objects (Mixing is allowed).

required

Raises: TypeError: ListBlock instances can only be constructed from subclasses with an assigned _reqCls, not the parent ListBlock class.

Source code in FoSpy/blocks/blocks.py
def __init__(self, blockList:list):
    """
    Constructs a `ListBlock` from a list of objects or serialized dictionaries.

    Each item in blockList is checked against the `SingleBlock` subclass
    specified for the `ListBlock` subclass. If the item is not the correct
    subclass, it is passed to the `SingleBlock` subclass's
    `dispatch_subclass` method for coersion.

    Args:
        blockList:
            A list containing either `dicts` or `SingleBlock` objects (Mixing
            is allowed).
    Raises:
        TypeError:
            `ListBlock` instances can only be constructed from subclasses with an assigned _reqCls, not the parent `ListBlock` class.
    """
    #self._objs = []
    if not (isinstance(self._reqCls, type) and issubclass(self._reqCls, SingleBlock)):
        raise TypeError(f"ListBlock instances can only be constructed from subclasses with an assigned _reqCls. {self.__class__} has no _reqCls.")
    self.track_attachments(**cfg.track_attachments())
    if not isinstance(blockList, list):
        blockList = [blockList]
    self._objs = blockList

__iter__()

Iterate over the items in this ListBlock

Source code in FoSpy/blocks/blocks.py
def __iter__(self):
    """
    Iterate over the items in this `ListBlock`
    """
    return iter(self._objs)

__len__()

Get the number of items in this ListBlock

Source code in FoSpy/blocks/blocks.py
def __len__(self):
    """
    Get the number of items in this `ListBlock`
    """
    return len(self._objs)

__setattr__(name, value)

Only private attributes starting with "_" can be set.

Items in self._objs can be edited/replaced individually by indexing with self[i], or self._objs can be replaced with a new list, which is re-validated and coerced to the correct SingleBlock subclass specified by _reqCls

Parameters:

Name Type Description Default
name

The name of the attribute to set.

required
value

The value to set the attribute to.

required

Raises: AttributeError: Only private attributes starting with "_" can be set. TypeError: self._objs must be a list of objects which can be coerced to the correct SingleBlock subclass specified by _reqCls

Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name, value):
    """
    Only private attributes starting with "_" can be set.

    Items in self._objs can be edited/replaced individually by indexing with
    self[i], or self._objs can be replaced with a new list, which is
    re-validated and coerced to the correct `SingleBlock` subclass specified
    by _reqCls

    Args:
        name:
            The name of the attribute to set.
        value:
            The value to set the attribute to.
    Raises:
        AttributeError:
            Only private attributes starting with "_" can be set.
        TypeError:
            self._objs must be a list of objects which can be coerced to the
            correct `SingleBlock` subclass specified by _reqCls
    """
    from .attachments import Attachment

    if name == "_objs":
        if type(value) is not list:
            raise TypeError(f"{type(self).__name__}._objs must be a list of objects.")

        elif hasattr(self, "_reqCls"):
            typ = self._reqCls
            new_list = []
            for obj in value:
                if not isinstance(obj, typ):
                    try:
                        new_obj = typ.dispatch_subclass(obj.serialize() if hasattr(obj,"serialize") else obj)
                    except:
                        raise TypeError(f"{type(self).__name__}._objs must be an empty list or list of {typ.__name__} objects.")
                    if isinstance(obj, Attachment) and hasattr(obj, "_filepath"):
                        new_obj._filepath = obj._filepath
                    obj=new_obj
                obj._parent_block = self
                if hasattr(obj, "refresh") and isinstance(obj, Attachment):
                    obj.refresh(new_copy=self._att_new_copy, overwrite=self._att_overwrite)
                new_list.append(obj)
            return super().__setattr__(name, new_list)

    elif name.startswith("_") or name in self._reserved:
        return super().__setattr__(name,value)
    else:
        raise AttributeError(
            f"{type(self).__name__} does not allow setting attribute '{name}'. "
            f"Only private names starting with '_' can be used. "
            f"Each list item is an item in {type(self).__name__}._objs which can be edited individually, "
            f"Or you can replace {type(self).__name__}._objs with a new list of objects."
        )

__setitem__(idx, val)

Set an item to this ListBlock by index.

After setting, all items in this ListBlock's _objs list are passed back to __setattr__ for validation.

Parameters:

Name Type Description Default
idx

The index of the item

required
val

The new value for the item

required
Source code in FoSpy/blocks/blocks.py
def __setitem__(self, idx, val):
    """
    Set an item to this `ListBlock` by index.

    After setting, all items in this `ListBlock`'s `_objs` list are passed
    back to [`__setattr__`][FoSpy.blocks.blocks.ListBlock.__setattr__] for
    validation.

    Args:
        idx:
            The index of the item
        val:
            The new value for the item
    """
    new_objs = self._objs.copy()
    new_objs[idx] = val
    self._objs = new_objs

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_missing_parameters()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_all_missing_parameters(self):
    for section in self:
        if hasattr(section, "add_missing_parameter"):
            section.add_missing_parameter()

add_comments(*comments)

Attach comments to self in the parent block.

Mutates the parent block's comments metadata under the assigned attribute name.

Parameters:

Name Type Description Default
*comments str

Comments to attach to the assigned attribute name in the parent block's metadata. Comments are printed above their attached attribute when saving in the FOS format.

()
Example
>>> my_synthesis.materials.add_comments("A comment on the materials header")
>>> my_synthesis._meta.comments 
{"materials":
    [
        "an older comment", 
        "A comment on the materials header"
    ]
}
Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Attach comments to self in the parent block.

    Mutates the parent block's comments metadata under the assigned
    attribute name.

    Args:
        *comments (str):
            Comments to attach to the assigned attribute name in
            the parent block's metadata. Comments are printed above
            their attached attribute when saving in the FOS format.

    Example:
        ```
        >>> my_synthesis.materials.add_comments("A comment on the materials header")
        >>> my_synthesis._meta.comments 
        {"materials":
            [
                "an older comment", 
                "A comment on the materials header"
            ]
        }
        ```
    """
    # placeholder. Method is injected by _add_comments_to_parent when this
    # block is assigned as an attribute of another block
    return

append(obj)

Source code in FoSpy/blocks/treatments.py
def append(self, obj):
    super().append(obj)
    if hasattr(self, "_parent_block") and self._parent_block is not None:
        self._parent_block.build_profile()

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    for obj in self._objs:
        if hasattr(obj, "clear_all_comments"):
            obj.clear_all_comments()

clear_comments(self_attr)

Remove comments attached to self in the parent block.

Clears parent block's comments metadata under the assigned attribute name.

Example
>>> my_synthesis._meta.comments["materials"]
["This comment was read from a FOS file"]

>>> my_synthesis.materials.clear_comments()
>>> my_synthesis._meta.comments["materials"]
[]
Source code in FoSpy/blocks/blocks.py
def clear_comments(self_attr):
    """
    Remove comments attached to self in the parent block.

    Clears parent block's comments metadata under the assigned attribute name.

    Example:
        ```
        >>> my_synthesis._meta.comments["materials"]
        ["This comment was read from a FOS file"]

        >>> my_synthesis.materials.clear_comments()
        >>> my_synthesis._meta.comments["materials"]
        []
        ```
    """
    # placeholder. Method is injected by _clear_comments_from_parent when this
    # block is assigned as an attribute of another block
    return

copy()

Returns a deep copy by serializing and then reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """Returns a deep copy by serializing and then reconstructing."""
    cls = type(self)
    return cls(self.serialize(override_list_type=False))

default_key_order(deep=False)

Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep=False):
    for obj in self._objs:
        if hasattr(obj, "default_key_order"):
            obj.default_key_order(deep=deep)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_any(**kwargs)

Source code in FoSpy/blocks/blocks.py
def get_any(self, **kwargs):
    if len(kwargs) != 1:
        raise TypeError("Exactly one keyword argument is required")

    key, val = next(iter(kwargs.items()))
    found = []
    for obj in self._objs:
        if getattr(obj, key, None) == val:
            found.append(obj)
    return found

get_first(**kwargs)

Source code in FoSpy/blocks/blocks.py
def get_first(self, **kwargs):
    return self.get_any(**kwargs)[0]

insert(idx, obj)

Insert a SingleBlock-coercable object into this ListBlock.

Inserts the object into this ListBlock's _objs list, and passes the entire list back to __setattr__ for validation.

Parameters:

Name Type Description Default
idx

The index to insert the object at

required
obj SingleBlock

The object to insert

required
Source code in FoSpy/blocks/blocks.py
def insert(self, idx, obj:SingleBlock):
    """
    Insert a `SingleBlock`-coercable object into this `ListBlock`.

    Inserts the object into this `ListBlock`'s `_objs` list, and passes the
    entire list back to
    [`__setattr__`][FoSpy.blocks.blocks.ListBlock.__setattr__] for
    validation.

    Args:
        idx:
            The index to insert the object at
        obj:
            The object to insert
    """
    objs = self._objs.copy()
    objs.insert(idx,obj)
    self._objs = objs

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all methods decorated as calc routines.

Methods are resolved as path strings relative to self, including indexing for recursively searched methods within self._objs. When returned back to a parent ListBlock object's call, these strings produce paths that can be resolved back into function calls relative to the parent object. See SingleBlock.list_avail_routines().

This method is usually only used in a recursive call from a SingleBlock object where one of its attributes is a ListBlock.

Example:

mySyn.materals.list_avail_routines(recursive=True)
## returns [
##     'add_weight_pcts',
##     '[0].add_MW',
##     '[1].add_MW',
##     ... 6 total materials with the same calc_routine
##     '[5].add_MW'
## ]

# Resursive call from `SingleBlock` mySyn object:
mySyn.list_avail_routines(recursive=True)
## returns [
##     'reaction.add_nom_MW',
##     'materials.add_weight_pcts',
##     'materials[0].add_MW',
##     'materials[1].add_MW',
##     ... 6 total materials with the same calc_routine
##     'materials[5].add_MW'
## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive=False, prefix="", abbreviated=False):
    """
    Lists all methods decorated as calc routines.

    Methods are resolved as path strings relative to self, including
    indexing for recursively searched methods within self._objs. When
    returned back to a parent `ListBlock` object's call, these strings
    produce paths that can be resolved back into function calls relative to
    the parent object. See `SingleBlock.list_avail_routines()`.

    This method is usually only used in a recursive call from a
    `SingleBlock` object where one of its attributes is a `ListBlock`.

    Example:
    ```
    mySyn.materals.list_avail_routines(recursive=True)
    ## returns [
    ##     'add_weight_pcts',
    ##     '[0].add_MW',
    ##     '[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     '[5].add_MW'
    ## ]

    # Resursive call from `SingleBlock` mySyn object:
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    ```
    """
    routines = []

    # Local routines on the ListBlock itself
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        if abbreviated:
            obj_routines = {}
            idx_str = "i"
            idx_num = 0
            while f"[{idx_str}{idx_num if idx_num > 0 else ''}]" in prefix:
                if idx_str == "z":
                    idx_str = "i"
                    idx_num += 1
                else:
                    idx_str = chr(ord(idx_str)+1)

            idx_str = f"{idx_str}{idx_num if idx_num > 0 else ''}"

            for i, obj in enumerate(self._objs):
                if hasattr(obj, "list_avail_routines"):
                    rtns = obj.list_avail_routines(True,f"{prefix[:-1]}[{idx_str}].",abbreviated=True)
                    for routine in rtns:
                        if routine not in obj_routines:
                            obj_routines[routine] = []
                        obj_routines[routine].append(i)
            for routine, i_list in obj_routines.items():
                routines.append(f"{routine}; {idx_str} = {i_list}")
        else:
            for i, obj in enumerate(self._objs):
                if hasattr(obj, "list_avail_routines"):
                    child_prefix = f"{prefix[:-1]}[{i}]."
                    routines.extend(obj.list_avail_routines(
                        recursive=True,
                        prefix=child_prefix,
                        abbreviated=False
                    ))

    return routines

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for val in self:
        if hasattr(val, "refresh_attachments"):
            val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
        if isinstance(val, Attachment) and hasattr(val, "refresh"):
            val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

remove_any(**kwargs)

Remove any objects from self._objs with attributes matching kwargs

Parameters:

Name Type Description Default
**kwargs

A single keyword argument can be passed. Any objects with attr:value matching kw:arg are removed.

{}

Raises:

Type Description
TypeError

Exactly one keyword argument is required.

Example:

mySyn.materials.remove_any(supplier="sigma")
## removes any obj from mySyn.materials._objs where
## obj.supplier == "sigma"

Source code in FoSpy/blocks/blocks.py
def remove_any(self, **kwargs):
    """
    Remove any objects from self._objs with attributes matching `kwargs`

    Args:
        **kwargs:
            A single keyword argument can be passed. Any objects with
            attr:value matching kw:arg are removed.

    Raises:
        TypeError: Exactly one keyword argument is required.

    Example:
    ```
    mySyn.materials.remove_any(supplier="sigma")
    ## removes any obj from mySyn.materials._objs where
    ## obj.supplier == "sigma"
    ```
    """
    if len(kwargs) != 1:
        raise TypeError("Exactly one keyword argument is required")

    key, val = next(iter(kwargs.items()))

    objs = self._objs.copy()
    removed = 0
    for obj in objs:
        if getattr(obj, key, None) == val:
            for i, existing in enumerate(self._objs):
                if existing is obj:
                    del self._objs[i]
                    removed += 1
                    break
    _debug.msg(f"Removed {removed} {self._reqCls.__name__} objects matching {key} = {val}.")

remove_idx(from_idx=None, to_idx=None)

Remove a range of items from this ListBlock

Removes a range of items from this ListBlock's _objs list, and passes the entire list back to __setattr__ for validation.

from_idx is inclusive, and to_idx is non-inclusive. i.e., if from_idx is 0 and to_idx is 1, then the first item in the list will be removed, but not the second.

If from_idx is None, then all items starting at and including to_idx will be removed. If to_idx is None, then all items up to and not including from_idx will be removed.

Parameters:

Name Type Description Default
from_idx int

The index of the first item to remove

None
to_idx int

The non-inclusive index to stop removing

None
Source code in FoSpy/blocks/blocks.py
def remove_idx(self, from_idx:int=None, to_idx:int=None):
    """
    Remove a range of items from this `ListBlock`

    Removes a range of items from this `ListBlock`'s `_objs` list, and
    passes the entire list back to
    [`__setattr__`][FoSpy.blocks.blocks.ListBlock.__setattr__] for
    validation.

    `from_idx` is inclusive, and `to_idx` is non-inclusive. i.e., if
    `from_idx` is 0 and `to_idx` is 1, then the first item in the list will
    be removed, but not the second.

    If `from_idx` is None, then all items starting at and including `to_idx`
    will be removed. If `to_idx` is None, then all items up to and **not**
    including `from_idx` will be removed.

    Args:
        from_idx:
            The index of the first item to remove
        to_idx:
            The non-inclusive index to stop removing
    """
    if from_idx is None and to_idx is None:
        self._objs = []

    objs = self._objs.copy()

    if from_idx is None:
        objs = objs[to_idx:]
    elif to_idx is None:
        objs = objs[:from_idx]
    else:
        objs = objs[:from_idx] + objs[to_idx:]

    self._objs = objs   

serialize(clean=False, shallow=False, override_list_type=None)

Serialize this ListBlock as a list of dictionaries.

Overriding list type is only skipped when all objects in the list have the same list type. To prevent mutation, list type override is performed by calling set_list_type on a copy of this ListBlock and returning the serialized copy.

ListBlocks of length one are always overridden to "explicit".

List Types
  • "explicit": Each object declares its own keys.
  • "looped": Common keys are declared once at the beginning of a list. Each object specifies values for those keys in the declared order. Anomalous keys are still printed as key:value pairs.

Parameters:

Name Type Description Default
clean

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False
shallow

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys for object dictionaries.

False
override_list_type str | bool
  • When None (default): Checks for mixed list types and recurses with override set to "explicit" if found.
  • When False: Does not override any list type. This should be avoided for FOS-formatted output unless you know that all objects in the list have the same list type.
  • When str: Copies this ListBlock and passes override to copy.set_list_type before returning the serialized copy
None
Source code in FoSpy/blocks/blocks.py
def serialize(self, clean=False, shallow=False, override_list_type:str|bool=None):
    """
    Serialize this `ListBlock` as a list of dictionaries.

    Overriding list type is only skipped when all objects in the list have
    the same list type. To prevent mutation, list type override is performed
    by calling
    [`set_list_type`][FoSpy.blocks.blocks.ListBlock.set_list_type] on a copy
    of this `ListBlock` and returning the serialized copy.

    `ListBlock`s of length one are always overridden to "explicit".

    List Types:
        - "explicit": Each object declares its own keys.
        - "looped": 
            Common keys are declared once at the beginning of a list. Each
            object specifies values for those keys in the declared order.
            Anomalous keys are still printed as key:value pairs.

    Args:
        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.
        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys for object
            dictionaries.
        override_list_type:
            - When `None` (default): Checks for mixed list types and
              recurses with override set to "explicit" if found.
            - When `False`: Does not override any list type. This should be
              avoided for FOS-formatted output unless you know that all
              objects in the list have the same list type.
            - When `str`: Copies this `ListBlock` and passes override to
              [`copy.set_list_type`][FoSpy.blocks.blocks.ListBlock.set_list_type]
              before returning the serialized copy
    """
    if override_list_type is None:
        for obj in self:
            if obj._meta.list_type == "explicit":
                return self.serialize(clean=clean, shallow=shallow, override_list_type="explicit")
        return self.serialize(clean=clean, shallow=shallow, override_list_type="looped")
    elif not override_list_type:
        keepListType = len(self)>1
        l = [obj.serialize(clean=clean, shallow=shallow, keepListType=keepListType) for obj in self]
        return l
    else:
        copy = self.copy()
        copy.set_list_type(override_list_type)
        return copy.serialize(clean=clean, shallow=shallow, override_list_type=False)

set_list_type(typ='explicit')

Set FOS list formatting (explicit or looped).

Sets metadata for all items in this ListBlock to the specified type.

List Types
  • "explicit": Each object declares its own keys.
  • "looped": Common keys are declared once at the beginning of a list. Each object specifies values for those keys in the declared order. Anomalous keys are still printed as key:value pairs.

Parameters:

Name Type Description Default
typ

The type to set

'explicit'
Source code in FoSpy/blocks/blocks.py
def set_list_type(self,typ="explicit"):
    """
    Set FOS list formatting (explicit or looped).

    Sets metadata for all items in this `ListBlock` to the specified type.

    List Types:
        - "explicit": Each object declares its own keys.
        - "looped": 
            Common keys are declared once at the beginning of a list. Each
            object specifies values for those keys in the declared order.
            Anomalous keys are still printed as key:value pairs.

    Args:
        typ:
            The type to set
    """
    if typ not in ("explicit", "looped"):
        raise ValueError("List type must be 'single' or 'looped'.")
    for obj in self:
        obj._meta.list_type = typ

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

AnnealSection

Bases: SingleBlock

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__

Constructs a SingleBlock object from a dictionary.

__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_req_validators

Overrides class validators with any renamed properties.

get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class AnnealSection(SingleBlock):
    dispatch = {}

    @classmethod
    def dispatch_subclass(cls, blockDict):
        from .blocks import _unwrap_block
        blockDict = _unwrap_block(blockDict)
        t = blockDict.get("type",None)
        subclass = cls.dispatch.get(t,cls)
        if subclass is cls:
            return cls(blockDict, _dispatched=True)
        return subclass.dispatch_subclass(blockDict)

    @_calc_routine()
    def add_missing_parameter(self):
        return

    def get_time(self, time_units="h"):
        from ..parsing.validators.units import FOSQuantity, FOSUnit
        if not hasattr(self, "time"):
            raise ValueError("This Anneal section does not have a 'time' attribute.")

        time = FOSQuantity(float(self.time.magnitude), self.time.units)
        value = time.to(FOSUnit(time_units))
        return value

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Constructs a SingleBlock object from a dictionary.

Avoid using this constructor for unfamiliar block classes, it may bypass subclass delegation. Use dispatch_subclass instead.

SingleBlocks are constructed recursively from an arbitrarily nested dictionary. All keys identified by SingleBlock.build_req_validators() must be present at the top level. Required keys at nested levels are handled by the recursed constructor.

Parameters:

Name Type Description Default
blockDict dict

An arbitrarily nested dictionary mapping attribute names to values.

Unexpected attributes will be assigned under self.ext instead (see SingleBlock.__setattr__).

It is possible to pass a blockDict already containing objects, but validation routines will fail if objects are not the correct type. Best practice is to serialize all nested objects into lists, dicts, and strings to allow full type coersion.

required
_dispatched

Flag passed by dispatch_subclass to signal that the safer construction method was used. Warning issued for False

False

Raises:

Type Description
ValueError

A key required by SingleBlock.build_req_validators() is not present.

TypeError

The value passed as blockDict was not able to be unwrapped into a dict, either by serialization of a passed Block object or by list index.

Source code in FoSpy/blocks/blocks.py
def __init__(self, blockDict:dict, _dispatched=False):
    """
    Constructs a SingleBlock object from a dictionary.

    Avoid using this constructor for unfamiliar block classes, it may bypass
    subclass delegation. Use
    [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
    instead.

    SingleBlocks are constructed recursively from an arbitrarily nested
    dictionary. All keys identified by `SingleBlock.build_req_validators()`
    must be present at the top level. Required keys at nested levels are
    handled by the recursed constructor.

    Args:
        blockDict:
            An arbitrarily nested dictionary mapping attribute names to
            values. 

            Unexpected attributes will be assigned under `self.ext` instead
            (see `SingleBlock.__setattr__`). 

            It is possible to pass a blockDict already containing objects,
            but validation routines will fail if objects are not the correct
            type. Best practice is to serialize all nested objects into
            lists, dicts, and strings to allow full type coersion.
        _dispatched:
            Flag passed by
            [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
            to signal that the safer construction method was used. Warning
            issued for False


    Raises:
        ValueError:
            A key required by `SingleBlock.build_req_validators()` is not
            present.
        TypeError:
            The value passed as `blockDict` was not able to be unwrapped
            into a dict, either by serialization of a passed `Block` object
            or by list index.

    """

    if not _dispatched:
        from warnings import warn
        warn(f"You should avoid directly constructing a {type(self).__name__} object. Use the dispatch_subclass() "
             "method instead to allow for subclass delegation when constructing.", stacklevel=2)

    self.track_attachments(**cfg.track_attachments())

    from ..parsing.validation import aliases as als
    new_als = als.copy()
    new_als.update(self._aliases or {})
    self._aliases = new_als
    self._reserved = ['ext']

    blockDict = _unwrap_block(blockDict)
    self._sourceDict = blockDict.copy()

    if not isinstance(blockDict, dict):
        raise TypeError("A SingleBlock must be constructed from either a dictionary or another SingleBlock. "
                        "The passed source can optionally be wrapped in lists of length == 1.")

    blockDict = blockDict.copy()

    rename = blockDict.pop("rename",None)
    if rename:
        setattr(self, "rename", _unwrap_block(rename))

    req = self.get_req_validators()
    req.pop("ext",None)
    for key, validator in req.items():
        if key not in blockDict:
            from .template import TemplateBlock, TemplateList, TemplateField, FlexTemplate
            is_type = isinstance(validator, type)
            if is_type and issubclass(validator, TemplateField):
                blockDict[key] = ""
            elif is_type and issubclass(validator, FlexTemplate):
                blockDict[key] = {"template_name": f"Empty {self._baseReq.__name__} Template"}
            elif is_type and issubclass(validator, TemplateBlock):
                blockDict[key] = validator.reflex()
            elif is_type and issubclass(validator, TemplateList):
                blockDict[key] = []
            else:
                raise ValueError(f"Missing required property: '{key}' for '{type(self).__name__}' object.")

    self._meta = SubContainer()
    self._calc_comments = {}
    self._calc_routines = []
    self._key_overrides = {}

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        setattr(self._meta, attr, blockDict.pop(key,k))

    self._key_order = []
    self.ext = SubContainer()

    for key, val in blockDict.items():
        self._key_order.append(key)
        setattr(self, key, val)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    return

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    from .blocks import _unwrap_block
    blockDict = _unwrap_block(blockDict)
    t = blockDict.get("type",None)
    subclass = cls.dispatch.get(t,cls)
    if subclass is cls:
        return cls(blockDict, _dispatched=True)
    return subclass.dispatch_subclass(blockDict)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import FOSQuantity, FOSUnit
    if not hasattr(self, "time"):
        raise ValueError("This Anneal section does not have a 'time' attribute.")

    time = FOSQuantity(float(self.time.magnitude), self.time.units)
    value = time.to(FOSUnit(time_units))
    return value

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

Annealing

Bases: Treatment

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__
__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

build_profile
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
example_calc
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_req_validators

Overrides class validators with any renamed properties.

get_validators

Overrides class validators with any renamed properties.

interactive_plot
key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

show_plot
to_json

Converts self into a JSON-formatted string or file.

track_attachments
update_profile
Source code in FoSpy/blocks/treatments.py
class Annealing(Treatment):
    dispatch = {}
    def __init__(self, blockDict, _dispatched=False):
        super().__init__(blockDict, _dispatched=_dispatched)
        self.build_profile()

    def build_profile(self, **kwargs):
        from .template import TemplateBlock
        if isinstance(self, TemplateBlock):
            from warnings import warn
            warn("Cannot build profile for a template annealing block. Skipping profile build.")
            return None

        from cif2xrd.furnace import Profile #type: ignore
        import matplotlib.pyplot as plt

        furnace = Profile(**kwargs)

        for section in self.program:
            if section.type == "ramp":
                temp = section.get_temp("C").magnitude
                furnace.ramp(temp, f"{section.get_time('h').magnitude} h")
            elif section.type == "dwell":
                furnace.dwell(f"{section.get_time('h').magnitude} h")
            elif section.type == "quench":
                furnace.quench(section.medium)

        self._profile = furnace

    def update_profile(self, **kwargs):
        return self._profile.update_params(**kwargs)

    def show_plot(self, **kwargs):
        self.update_profile(**kwargs)
        return self._profile.plot(show=True)

    def interactive_plot(self, **kwargs):
        self.update_profile(**kwargs)
        return self._profile.interactive()

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Source code in FoSpy/blocks/treatments.py
def __init__(self, blockDict, _dispatched=False):
    super().__init__(blockDict, _dispatched=_dispatched)
    self.build_profile()

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

build_profile(**kwargs)

Source code in FoSpy/blocks/treatments.py
def build_profile(self, **kwargs):
    from .template import TemplateBlock
    if isinstance(self, TemplateBlock):
        from warnings import warn
        warn("Cannot build profile for a template annealing block. Skipping profile build.")
        return None

    from cif2xrd.furnace import Profile #type: ignore
    import matplotlib.pyplot as plt

    furnace = Profile(**kwargs)

    for section in self.program:
        if section.type == "ramp":
            temp = section.get_temp("C").magnitude
            furnace.ramp(temp, f"{section.get_time('h').magnitude} h")
        elif section.type == "dwell":
            furnace.dwell(f"{section.get_time('h').magnitude} h")
        elif section.type == "quench":
            furnace.quench(section.medium)

    self._profile = furnace

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    from .blocks import _unwrap_block
    blockDict = _unwrap_block(blockDict)
    t = blockDict.get("type", None)
    subclass = cls.dispatch.get(t,cls)
    return subclass(blockDict, _dispatched=True)

example_calc()

Source code in FoSpy/blocks/treatments.py
@_calc_routine(attach=False)
def example_calc(self):
    _debug.msg(f"Running example routine for {self.type} treatment")
    return None

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

interactive_plot(**kwargs)

Source code in FoSpy/blocks/treatments.py
def interactive_plot(self, **kwargs):
    self.update_profile(**kwargs)
    return self._profile.interactive()

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

show_plot(**kwargs)

Source code in FoSpy/blocks/treatments.py
def show_plot(self, **kwargs):
    self.update_profile(**kwargs)
    return self._profile.plot(show=True)

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

update_profile(**kwargs)

Source code in FoSpy/blocks/treatments.py
def update_profile(self, **kwargs):
    return self._profile.update_params(**kwargs)

Dwell

Bases: AnnealSection

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__

Constructs a SingleBlock object from a dictionary.

__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_req_validators

Overrides class validators with any renamed properties.

get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class Dwell(AnnealSection):
    dispatch = {}
    @classmethod
    def dispatch_subclass(cls, blockDict):
        return cls(blockDict, _dispatched=True)

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Constructs a SingleBlock object from a dictionary.

Avoid using this constructor for unfamiliar block classes, it may bypass subclass delegation. Use dispatch_subclass instead.

SingleBlocks are constructed recursively from an arbitrarily nested dictionary. All keys identified by SingleBlock.build_req_validators() must be present at the top level. Required keys at nested levels are handled by the recursed constructor.

Parameters:

Name Type Description Default
blockDict dict

An arbitrarily nested dictionary mapping attribute names to values.

Unexpected attributes will be assigned under self.ext instead (see SingleBlock.__setattr__).

It is possible to pass a blockDict already containing objects, but validation routines will fail if objects are not the correct type. Best practice is to serialize all nested objects into lists, dicts, and strings to allow full type coersion.

required
_dispatched

Flag passed by dispatch_subclass to signal that the safer construction method was used. Warning issued for False

False

Raises:

Type Description
ValueError

A key required by SingleBlock.build_req_validators() is not present.

TypeError

The value passed as blockDict was not able to be unwrapped into a dict, either by serialization of a passed Block object or by list index.

Source code in FoSpy/blocks/blocks.py
def __init__(self, blockDict:dict, _dispatched=False):
    """
    Constructs a SingleBlock object from a dictionary.

    Avoid using this constructor for unfamiliar block classes, it may bypass
    subclass delegation. Use
    [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
    instead.

    SingleBlocks are constructed recursively from an arbitrarily nested
    dictionary. All keys identified by `SingleBlock.build_req_validators()`
    must be present at the top level. Required keys at nested levels are
    handled by the recursed constructor.

    Args:
        blockDict:
            An arbitrarily nested dictionary mapping attribute names to
            values. 

            Unexpected attributes will be assigned under `self.ext` instead
            (see `SingleBlock.__setattr__`). 

            It is possible to pass a blockDict already containing objects,
            but validation routines will fail if objects are not the correct
            type. Best practice is to serialize all nested objects into
            lists, dicts, and strings to allow full type coersion.
        _dispatched:
            Flag passed by
            [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
            to signal that the safer construction method was used. Warning
            issued for False


    Raises:
        ValueError:
            A key required by `SingleBlock.build_req_validators()` is not
            present.
        TypeError:
            The value passed as `blockDict` was not able to be unwrapped
            into a dict, either by serialization of a passed `Block` object
            or by list index.

    """

    if not _dispatched:
        from warnings import warn
        warn(f"You should avoid directly constructing a {type(self).__name__} object. Use the dispatch_subclass() "
             "method instead to allow for subclass delegation when constructing.", stacklevel=2)

    self.track_attachments(**cfg.track_attachments())

    from ..parsing.validation import aliases as als
    new_als = als.copy()
    new_als.update(self._aliases or {})
    self._aliases = new_als
    self._reserved = ['ext']

    blockDict = _unwrap_block(blockDict)
    self._sourceDict = blockDict.copy()

    if not isinstance(blockDict, dict):
        raise TypeError("A SingleBlock must be constructed from either a dictionary or another SingleBlock. "
                        "The passed source can optionally be wrapped in lists of length == 1.")

    blockDict = blockDict.copy()

    rename = blockDict.pop("rename",None)
    if rename:
        setattr(self, "rename", _unwrap_block(rename))

    req = self.get_req_validators()
    req.pop("ext",None)
    for key, validator in req.items():
        if key not in blockDict:
            from .template import TemplateBlock, TemplateList, TemplateField, FlexTemplate
            is_type = isinstance(validator, type)
            if is_type and issubclass(validator, TemplateField):
                blockDict[key] = ""
            elif is_type and issubclass(validator, FlexTemplate):
                blockDict[key] = {"template_name": f"Empty {self._baseReq.__name__} Template"}
            elif is_type and issubclass(validator, TemplateBlock):
                blockDict[key] = validator.reflex()
            elif is_type and issubclass(validator, TemplateList):
                blockDict[key] = []
            else:
                raise ValueError(f"Missing required property: '{key}' for '{type(self).__name__}' object.")

    self._meta = SubContainer()
    self._calc_comments = {}
    self._calc_routines = []
    self._key_overrides = {}

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        setattr(self._meta, attr, blockDict.pop(key,k))

    self._key_order = []
    self.ext = SubContainer()

    for key, val in blockDict.items():
        self._key_order.append(key)
        setattr(self, key, val)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    return

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    return cls(blockDict, _dispatched=True)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import FOSQuantity, FOSUnit
    if not hasattr(self, "time"):
        raise ValueError("This Anneal section does not have a 'time' attribute.")

    time = FOSQuantity(float(self.time.magnitude), self.time.units)
    value = time.to(FOSUnit(time_units))
    return value

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

FlowList


GasFlow

Bases: SingleBlock

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__

Constructs a SingleBlock object from a dictionary.

__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass

Recommended dispatcher to allow subclass delegation when constructing.

find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_req_validators

Overrides class validators with any renamed properties.

get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class GasFlow(SingleBlock):
    pass

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Constructs a SingleBlock object from a dictionary.

Avoid using this constructor for unfamiliar block classes, it may bypass subclass delegation. Use dispatch_subclass instead.

SingleBlocks are constructed recursively from an arbitrarily nested dictionary. All keys identified by SingleBlock.build_req_validators() must be present at the top level. Required keys at nested levels are handled by the recursed constructor.

Parameters:

Name Type Description Default
blockDict dict

An arbitrarily nested dictionary mapping attribute names to values.

Unexpected attributes will be assigned under self.ext instead (see SingleBlock.__setattr__).

It is possible to pass a blockDict already containing objects, but validation routines will fail if objects are not the correct type. Best practice is to serialize all nested objects into lists, dicts, and strings to allow full type coersion.

required
_dispatched

Flag passed by dispatch_subclass to signal that the safer construction method was used. Warning issued for False

False

Raises:

Type Description
ValueError

A key required by SingleBlock.build_req_validators() is not present.

TypeError

The value passed as blockDict was not able to be unwrapped into a dict, either by serialization of a passed Block object or by list index.

Source code in FoSpy/blocks/blocks.py
def __init__(self, blockDict:dict, _dispatched=False):
    """
    Constructs a SingleBlock object from a dictionary.

    Avoid using this constructor for unfamiliar block classes, it may bypass
    subclass delegation. Use
    [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
    instead.

    SingleBlocks are constructed recursively from an arbitrarily nested
    dictionary. All keys identified by `SingleBlock.build_req_validators()`
    must be present at the top level. Required keys at nested levels are
    handled by the recursed constructor.

    Args:
        blockDict:
            An arbitrarily nested dictionary mapping attribute names to
            values. 

            Unexpected attributes will be assigned under `self.ext` instead
            (see `SingleBlock.__setattr__`). 

            It is possible to pass a blockDict already containing objects,
            but validation routines will fail if objects are not the correct
            type. Best practice is to serialize all nested objects into
            lists, dicts, and strings to allow full type coersion.
        _dispatched:
            Flag passed by
            [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
            to signal that the safer construction method was used. Warning
            issued for False


    Raises:
        ValueError:
            A key required by `SingleBlock.build_req_validators()` is not
            present.
        TypeError:
            The value passed as `blockDict` was not able to be unwrapped
            into a dict, either by serialization of a passed `Block` object
            or by list index.

    """

    if not _dispatched:
        from warnings import warn
        warn(f"You should avoid directly constructing a {type(self).__name__} object. Use the dispatch_subclass() "
             "method instead to allow for subclass delegation when constructing.", stacklevel=2)

    self.track_attachments(**cfg.track_attachments())

    from ..parsing.validation import aliases as als
    new_als = als.copy()
    new_als.update(self._aliases or {})
    self._aliases = new_als
    self._reserved = ['ext']

    blockDict = _unwrap_block(blockDict)
    self._sourceDict = blockDict.copy()

    if not isinstance(blockDict, dict):
        raise TypeError("A SingleBlock must be constructed from either a dictionary or another SingleBlock. "
                        "The passed source can optionally be wrapped in lists of length == 1.")

    blockDict = blockDict.copy()

    rename = blockDict.pop("rename",None)
    if rename:
        setattr(self, "rename", _unwrap_block(rename))

    req = self.get_req_validators()
    req.pop("ext",None)
    for key, validator in req.items():
        if key not in blockDict:
            from .template import TemplateBlock, TemplateList, TemplateField, FlexTemplate
            is_type = isinstance(validator, type)
            if is_type and issubclass(validator, TemplateField):
                blockDict[key] = ""
            elif is_type and issubclass(validator, FlexTemplate):
                blockDict[key] = {"template_name": f"Empty {self._baseReq.__name__} Template"}
            elif is_type and issubclass(validator, TemplateBlock):
                blockDict[key] = validator.reflex()
            elif is_type and issubclass(validator, TemplateList):
                blockDict[key] = []
            else:
                raise ValueError(f"Missing required property: '{key}' for '{type(self).__name__}' object.")

    self._meta = SubContainer()
    self._calc_comments = {}
    self._calc_routines = []
    self._key_overrides = {}

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        setattr(self._meta, attr, blockDict.pop(key,k))

    self._key_order = []
    self.ext = SubContainer()

    for key, val in blockDict.items():
        self._key_order.append(key)
        setattr(self, key, val)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict, **kwargs) classmethod

Recommended dispatcher to allow subclass delegation when constructing.

Overridden in some subclasses, usually to assign subclass based on the value of one or more properties.

Default behavior passes blockDict and **kwargs to __init__ constructor.

Source code in FoSpy/blocks/blocks.py
@classmethod
def dispatch_subclass(cls, blockDict:dict, **kwargs:any):
    """
    Recommended dispatcher to allow subclass delegation when constructing.

    Overridden in some subclasses, usually to assign subclass based on the
    value of one or more properties.

    Default behavior passes `blockDict` and `**kwargs` to `__init__`
    constructor.
    """
    # k: construct normally.
    return cls(blockDict,_dispatched=True, **kwargs)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

Quench

Bases: AnnealSection

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__

Constructs a SingleBlock object from a dictionary.

__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_req_validators

Overrides class validators with any renamed properties.

get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class Quench(AnnealSection):
    dispatch = {}
    @classmethod
    def dispatch_subclass(cls, blockDict):
        return cls(blockDict, _dispatched=True)

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Constructs a SingleBlock object from a dictionary.

Avoid using this constructor for unfamiliar block classes, it may bypass subclass delegation. Use dispatch_subclass instead.

SingleBlocks are constructed recursively from an arbitrarily nested dictionary. All keys identified by SingleBlock.build_req_validators() must be present at the top level. Required keys at nested levels are handled by the recursed constructor.

Parameters:

Name Type Description Default
blockDict dict

An arbitrarily nested dictionary mapping attribute names to values.

Unexpected attributes will be assigned under self.ext instead (see SingleBlock.__setattr__).

It is possible to pass a blockDict already containing objects, but validation routines will fail if objects are not the correct type. Best practice is to serialize all nested objects into lists, dicts, and strings to allow full type coersion.

required
_dispatched

Flag passed by dispatch_subclass to signal that the safer construction method was used. Warning issued for False

False

Raises:

Type Description
ValueError

A key required by SingleBlock.build_req_validators() is not present.

TypeError

The value passed as blockDict was not able to be unwrapped into a dict, either by serialization of a passed Block object or by list index.

Source code in FoSpy/blocks/blocks.py
def __init__(self, blockDict:dict, _dispatched=False):
    """
    Constructs a SingleBlock object from a dictionary.

    Avoid using this constructor for unfamiliar block classes, it may bypass
    subclass delegation. Use
    [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
    instead.

    SingleBlocks are constructed recursively from an arbitrarily nested
    dictionary. All keys identified by `SingleBlock.build_req_validators()`
    must be present at the top level. Required keys at nested levels are
    handled by the recursed constructor.

    Args:
        blockDict:
            An arbitrarily nested dictionary mapping attribute names to
            values. 

            Unexpected attributes will be assigned under `self.ext` instead
            (see `SingleBlock.__setattr__`). 

            It is possible to pass a blockDict already containing objects,
            but validation routines will fail if objects are not the correct
            type. Best practice is to serialize all nested objects into
            lists, dicts, and strings to allow full type coersion.
        _dispatched:
            Flag passed by
            [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
            to signal that the safer construction method was used. Warning
            issued for False


    Raises:
        ValueError:
            A key required by `SingleBlock.build_req_validators()` is not
            present.
        TypeError:
            The value passed as `blockDict` was not able to be unwrapped
            into a dict, either by serialization of a passed `Block` object
            or by list index.

    """

    if not _dispatched:
        from warnings import warn
        warn(f"You should avoid directly constructing a {type(self).__name__} object. Use the dispatch_subclass() "
             "method instead to allow for subclass delegation when constructing.", stacklevel=2)

    self.track_attachments(**cfg.track_attachments())

    from ..parsing.validation import aliases as als
    new_als = als.copy()
    new_als.update(self._aliases or {})
    self._aliases = new_als
    self._reserved = ['ext']

    blockDict = _unwrap_block(blockDict)
    self._sourceDict = blockDict.copy()

    if not isinstance(blockDict, dict):
        raise TypeError("A SingleBlock must be constructed from either a dictionary or another SingleBlock. "
                        "The passed source can optionally be wrapped in lists of length == 1.")

    blockDict = blockDict.copy()

    rename = blockDict.pop("rename",None)
    if rename:
        setattr(self, "rename", _unwrap_block(rename))

    req = self.get_req_validators()
    req.pop("ext",None)
    for key, validator in req.items():
        if key not in blockDict:
            from .template import TemplateBlock, TemplateList, TemplateField, FlexTemplate
            is_type = isinstance(validator, type)
            if is_type and issubclass(validator, TemplateField):
                blockDict[key] = ""
            elif is_type and issubclass(validator, FlexTemplate):
                blockDict[key] = {"template_name": f"Empty {self._baseReq.__name__} Template"}
            elif is_type and issubclass(validator, TemplateBlock):
                blockDict[key] = validator.reflex()
            elif is_type and issubclass(validator, TemplateList):
                blockDict[key] = []
            else:
                raise ValueError(f"Missing required property: '{key}' for '{type(self).__name__}' object.")

    self._meta = SubContainer()
    self._calc_comments = {}
    self._calc_routines = []
    self._key_overrides = {}

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        setattr(self._meta, attr, blockDict.pop(key,k))

    self._key_order = []
    self.ext = SubContainer()

    for key, val in blockDict.items():
        self._key_order.append(key)
        setattr(self, key, val)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    return

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    return cls(blockDict, _dispatched=True)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import FOSQuantity, FOSUnit
    if not hasattr(self, "time"):
        raise ValueError("This Anneal section does not have a 'time' attribute.")

    time = FOSQuantity(float(self.time.magnitude), self.time.units)
    value = time.to(FOSUnit(time_units))
    return value

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

Ramp

Bases: AnnealSection

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__
__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_rate
get_req_validators

Overrides class validators with any renamed properties.

get_temp
get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class Ramp(AnnealSection):
    dispatch = {}

    def __init__(self, blockDict, _dispatched=False):
        super().__init__(blockDict, _dispatched=_dispatched)

    @classmethod
    def dispatch_subclass(cls, blockDict):
        from ._blockUtils import _unwrap_block
        blockDict = _unwrap_block(blockDict)
        seeking = ["temp","time","rate"]
        found = []
        for key in blockDict:
            if key in seeking:
                found.append(key)
            if len(found) == 2:
                break
        if len(found) != 2:
            raise ValueError(f"Ramp section must have at least two of the following keys: {seeking}. Found: {found}")

        for key in seeking:
            if key not in found:
                blockDict.pop(key,None)
                subclass = cls.dispatch.get(key,None)
                if subclass is None:
                    raise ValueError(f"Ramp section missing required key '{key}' and no subclass found to handle this case.")
                return subclass(blockDict, _dispatched=True)

    def get_rate(self, temp_units="C", time_units="h"):
        from ..parsing.validators.units import FOSTempUnit, FOSUnit, FOSQuantity
        if not hasattr(self, "rate"):
            raise ValueError("Ramp section does not have a 'rate' attribute. Conisder reclassifying this section as a RampNoRate section.")
        new_unit = FOSUnit(f"{FOSTempUnit(temp_units)}/{FOSUnit(time_units,"[time]")}")
        rate = FOSQuantity(float(self.rate.magnitude),self.rate.units)
        value = rate.to(new_unit)
        return value

    def get_temp(self, temp_units="C"):
        from ..parsing.validators.units import FOSTempUnit, FOSQuantity
        if not hasattr(self, "temp"):
            raise ValueError("Ramp section does not have a 'temp' attribute. Conisder reclassifying this section as a RampNoTemp section.")
        temp = FOSQuantity(float(self.temp.magnitude),self.temp.units)
        value = temp.to(FOSTempUnit(temp_units))
        return value

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Source code in FoSpy/blocks/treatments.py
def __init__(self, blockDict, _dispatched=False):
    super().__init__(blockDict, _dispatched=_dispatched)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    return

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    from ._blockUtils import _unwrap_block
    blockDict = _unwrap_block(blockDict)
    seeking = ["temp","time","rate"]
    found = []
    for key in blockDict:
        if key in seeking:
            found.append(key)
        if len(found) == 2:
            break
    if len(found) != 2:
        raise ValueError(f"Ramp section must have at least two of the following keys: {seeking}. Found: {found}")

    for key in seeking:
        if key not in found:
            blockDict.pop(key,None)
            subclass = cls.dispatch.get(key,None)
            if subclass is None:
                raise ValueError(f"Ramp section missing required key '{key}' and no subclass found to handle this case.")
            return subclass(blockDict, _dispatched=True)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_rate(temp_units='C', time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_rate(self, temp_units="C", time_units="h"):
    from ..parsing.validators.units import FOSTempUnit, FOSUnit, FOSQuantity
    if not hasattr(self, "rate"):
        raise ValueError("Ramp section does not have a 'rate' attribute. Conisder reclassifying this section as a RampNoRate section.")
    new_unit = FOSUnit(f"{FOSTempUnit(temp_units)}/{FOSUnit(time_units,"[time]")}")
    rate = FOSQuantity(float(self.rate.magnitude),self.rate.units)
    value = rate.to(new_unit)
    return value

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_temp(temp_units='C')

Source code in FoSpy/blocks/treatments.py
def get_temp(self, temp_units="C"):
    from ..parsing.validators.units import FOSTempUnit, FOSQuantity
    if not hasattr(self, "temp"):
        raise ValueError("Ramp section does not have a 'temp' attribute. Conisder reclassifying this section as a RampNoTemp section.")
    temp = FOSQuantity(float(self.temp.magnitude),self.temp.units)
    value = temp.to(FOSTempUnit(temp_units))
    return value

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import FOSQuantity, FOSUnit
    if not hasattr(self, "time"):
        raise ValueError("This Anneal section does not have a 'time' attribute.")

    time = FOSQuantity(float(self.time.magnitude), self.time.units)
    value = time.to(FOSUnit(time_units))
    return value

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

RampNoRate

Bases: Ramp

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__
__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_rate
get_req_validators

Overrides class validators with any renamed properties.

get_temp
get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class RampNoRate(Ramp):
    dispatch = {}
    def get_rate(self, temp_units="C", time_units="h"):
        from ..parsing.validators.units import FOSQuantity, temp_rate_unit, FOSTempUnit
        try:
            ramp_set = self._parent_block.get_any(type="ramp")
            self_idx = ramp_set.index(self)
            if self_idx == 0:
                last_temp = self._parent_block._parent_block.start_temp
            else:
                last_temp = ramp_set[self_idx-1].get_temp(temp_units)
        except:
            last_temp = FOSQuantity(25,FOSTempUnit("C"))

        last_temp = FOSQuantity(float(last_temp.magnitude),last_temp.units)

        delta_temp = self.get_temp(temp_units)-last_temp
        time = self.get_time(time_units)
        rate = delta_temp / time
        return rate

    @classmethod
    def dispatch_subclass(cls, blockDict):
        return cls(blockDict, _dispatched=True)

    @_calc_routine()
    def add_missing_parameter(self):
        rate = self.get_rate()
        self.add_calc_comment("temp", f"Rate for ramp: {rate}", "missing rate")

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Source code in FoSpy/blocks/treatments.py
def __init__(self, blockDict, _dispatched=False):
    super().__init__(blockDict, _dispatched=_dispatched)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    rate = self.get_rate()
    self.add_calc_comment("temp", f"Rate for ramp: {rate}", "missing rate")

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    return cls(blockDict, _dispatched=True)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_rate(temp_units='C', time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_rate(self, temp_units="C", time_units="h"):
    from ..parsing.validators.units import FOSQuantity, temp_rate_unit, FOSTempUnit
    try:
        ramp_set = self._parent_block.get_any(type="ramp")
        self_idx = ramp_set.index(self)
        if self_idx == 0:
            last_temp = self._parent_block._parent_block.start_temp
        else:
            last_temp = ramp_set[self_idx-1].get_temp(temp_units)
    except:
        last_temp = FOSQuantity(25,FOSTempUnit("C"))

    last_temp = FOSQuantity(float(last_temp.magnitude),last_temp.units)

    delta_temp = self.get_temp(temp_units)-last_temp
    time = self.get_time(time_units)
    rate = delta_temp / time
    return rate

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_temp(temp_units='C')

Source code in FoSpy/blocks/treatments.py
def get_temp(self, temp_units="C"):
    from ..parsing.validators.units import FOSTempUnit, FOSQuantity
    if not hasattr(self, "temp"):
        raise ValueError("Ramp section does not have a 'temp' attribute. Conisder reclassifying this section as a RampNoTemp section.")
    temp = FOSQuantity(float(self.temp.magnitude),self.temp.units)
    value = temp.to(FOSTempUnit(temp_units))
    return value

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import FOSQuantity, FOSUnit
    if not hasattr(self, "time"):
        raise ValueError("This Anneal section does not have a 'time' attribute.")

    time = FOSQuantity(float(self.time.magnitude), self.time.units)
    value = time.to(FOSUnit(time_units))
    return value

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

RampNoTemp

Bases: Ramp

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__
__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_rate
get_req_validators

Overrides class validators with any renamed properties.

get_temp
get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class RampNoTemp(Ramp):
    dispatch = {}
    def get_temp(self, temp_units="C"):
        from ..parsing.validators.units import convert_temp
        current_temp, current_time = [v.strip() for v in self.rate_units.split("/")]
        rate = self.get_rate(current_temp, current_time)
        time = self.get_time(current_time)
        temp = rate * time
        temp = convert_temp(temp, current_temp, temp_units)
        return temp
    @classmethod
    def dispatch_subclass(cls, blockDict):
        return cls(blockDict, _dispatched=True)

    @_calc_routine()
    def add_missing_parameter(self):
        temp_unit = [v.strip() for v in self.rate_units.split("/")][0]
        temp = self.get_temp(temp_unit)
        self.add_calc_comment("time", f"Temperature after ramp: {temp} {temp_unit}", "missing temp")

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Source code in FoSpy/blocks/treatments.py
def __init__(self, blockDict, _dispatched=False):
    super().__init__(blockDict, _dispatched=_dispatched)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    temp_unit = [v.strip() for v in self.rate_units.split("/")][0]
    temp = self.get_temp(temp_unit)
    self.add_calc_comment("time", f"Temperature after ramp: {temp} {temp_unit}", "missing temp")

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    return cls(blockDict, _dispatched=True)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_rate(temp_units='C', time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_rate(self, temp_units="C", time_units="h"):
    from ..parsing.validators.units import FOSTempUnit, FOSUnit, FOSQuantity
    if not hasattr(self, "rate"):
        raise ValueError("Ramp section does not have a 'rate' attribute. Conisder reclassifying this section as a RampNoRate section.")
    new_unit = FOSUnit(f"{FOSTempUnit(temp_units)}/{FOSUnit(time_units,"[time]")}")
    rate = FOSQuantity(float(self.rate.magnitude),self.rate.units)
    value = rate.to(new_unit)
    return value

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_temp(temp_units='C')

Source code in FoSpy/blocks/treatments.py
def get_temp(self, temp_units="C"):
    from ..parsing.validators.units import convert_temp
    current_temp, current_time = [v.strip() for v in self.rate_units.split("/")]
    rate = self.get_rate(current_temp, current_time)
    time = self.get_time(current_time)
    temp = rate * time
    temp = convert_temp(temp, current_temp, temp_units)
    return temp

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import FOSQuantity, FOSUnit
    if not hasattr(self, "time"):
        raise ValueError("This Anneal section does not have a 'time' attribute.")

    time = FOSQuantity(float(self.time.magnitude), self.time.units)
    value = time.to(FOSUnit(time_units))
    return value

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

RampNoTime

Bases: Ramp

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__
__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

add_missing_parameter
build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_rate
get_req_validators

Overrides class validators with any renamed properties.

get_temp
get_time
get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class RampNoTime(Ramp):
    dispatch = {}
    def get_time(self, time_units="h"):
        from ..parsing.validators.units import convert_time
        current_temp, current_time = [v.strip() for v in self.rate_units.split("/")]
        rate = self.get_rate(current_temp, current_time)
        temp = self.get_temp(current_temp)
        time = temp / rate
        time = convert_time(time, current_time, time_units)
        return time
    @classmethod
    def dispatch_subclass(cls, blockDict):
        return cls(blockDict, _dispatched=True)

    @_calc_routine()
    def add_missing_parameter(self):
        time_unit = [v.strip() for v in self.rate_units.split("/")][1]
        time = self.get_time(time_unit)
        self.add_calc_comment("temp", f"Time for ramp: {time} {time_unit}", "missing time")

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Source code in FoSpy/blocks/treatments.py
def __init__(self, blockDict, _dispatched=False):
    super().__init__(blockDict, _dispatched=_dispatched)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

add_missing_parameter()

Source code in FoSpy/blocks/treatments.py
@_calc_routine()
def add_missing_parameter(self):
    time_unit = [v.strip() for v in self.rate_units.split("/")][1]
    time = self.get_time(time_unit)
    self.add_calc_comment("temp", f"Time for ramp: {time} {time_unit}", "missing time")

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    return cls(blockDict, _dispatched=True)

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_rate(temp_units='C', time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_rate(self, temp_units="C", time_units="h"):
    from ..parsing.validators.units import FOSTempUnit, FOSUnit, FOSQuantity
    if not hasattr(self, "rate"):
        raise ValueError("Ramp section does not have a 'rate' attribute. Conisder reclassifying this section as a RampNoRate section.")
    new_unit = FOSUnit(f"{FOSTempUnit(temp_units)}/{FOSUnit(time_units,"[time]")}")
    rate = FOSQuantity(float(self.rate.magnitude),self.rate.units)
    value = rate.to(new_unit)
    return value

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_temp(temp_units='C')

Source code in FoSpy/blocks/treatments.py
def get_temp(self, temp_units="C"):
    from ..parsing.validators.units import FOSTempUnit, FOSQuantity
    if not hasattr(self, "temp"):
        raise ValueError("Ramp section does not have a 'temp' attribute. Conisder reclassifying this section as a RampNoTemp section.")
    temp = FOSQuantity(float(self.temp.magnitude),self.temp.units)
    value = temp.to(FOSTempUnit(temp_units))
    return value

get_time(time_units='h')

Source code in FoSpy/blocks/treatments.py
def get_time(self, time_units="h"):
    from ..parsing.validators.units import convert_time
    current_temp, current_time = [v.strip() for v in self.rate_units.split("/")]
    rate = self.get_rate(current_temp, current_time)
    temp = self.get_temp(current_temp)
    time = temp / rate
    time = convert_time(time, current_time, time_units)
    return time

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

Treatment

Bases: SingleBlock

Methods:

Name Description
TemplateClass

Create a template for a subclass of SingleBlock.

__delattr__
__eq__

Check equality of two SingleBlock objects.

__getattr__

Check both self and self.ext for attribute before returning.

__hash__
__init__

Constructs a SingleBlock object from a dictionary.

__setattr__

Assign an attribute with validation and controlled namespace behavior.

_assign_and_inject

Attaches attributes and methods to any value before assigning it as an

_meta_to_front

Moves metadata to the front of _key_order. Metadata will always be

_rename_validators

Realigns any renamed

_resolve_relative_path

Resolves a relative object path string into an object or function.

_subprocess
add_all_calc_routines

Schedule all available calculation routines.

add_block

Adds an unexpected attribute with a validator mapped by type_alias.

add_calc_comment

Add a calculated comment to be injected during serialization.

add_calc_routine

Schedules a calculated comment.

add_comments

Default behavior to be overwritten when attached to a parent block.

build_req_validators

Builds required keys and validators mapped to subclass.

build_validators

Builds expected keys and validators mapped to subclass.

clear_all_comments
clear_comments

Clear comments attached to top-level attributes only.

copy

Returns a deep-copy of self by serializing and reconstructing.

default_key_order

Set to default attribute order for serialization.

dispatch_subclass
example_calc
find_fileblock

Finds the parent file object.

find_tempdir

Find the parent file object's temporary directory.

find_temppath

Find the parent file object's temporary directory path.

get_req_validators

Overrides class validators with any renamed properties.

get_validators

Overrides class validators with any renamed properties.

key_to_idx

Reorder attributes for serialization.

keys_to_end

Reorder attributes for serialization.

keys_to_front

Reorder attributes for serialization.

list_avail_routines

Lists all calc routines available to be added to self._calc_routines.

make_template

Converts self into a template of its original subclass.

reflex

Generate a flexible template for the current class.

refresh_attachments
rename_block
serialize

Return a recursively serialized dict representation of self.

to_json

Converts self into a JSON-formatted string or file.

track_attachments
Source code in FoSpy/blocks/treatments.py
class Treatment(SingleBlock):
    # Maps type strings to subclass constructors.
    # Populated after each subclass definition.
    dispatch = {}

    @classmethod
    def dispatch_subclass(cls, blockDict):
        from .blocks import _unwrap_block
        blockDict = _unwrap_block(blockDict)
        t = blockDict.get("type", None)
        subclass = cls.dispatch.get(t,cls)
        return subclass(blockDict, _dispatched=True)

    @_calc_routine(attach=False)
    def example_calc(self):
        _debug.msg(f"Running example routine for {self.type} treatment")
        return None

_aliases = new_als class-attribute instance-attribute

_calc_comments = {} instance-attribute

_calc_routines = [] instance-attribute

_key_order = [] instance-attribute

_key_overrides = {} instance-attribute

_meta = SubContainer() instance-attribute

_reserved = ['ext'] instance-attribute

_sourceDict = blockDict.copy() instance-attribute

dispatch = {} class-attribute instance-attribute

ext = SubContainer() instance-attribute

TemplateClass(*args) classmethod

Create a template for a subclass of SingleBlock.

Generates a hybridized subclass of the current block class and TemplateBlock. Template subclasses override original expected validators with either a TemplateField, TemplateBlock, or TemplateList depending on the type of the original validator.

Parameters:

Name Type Description Default
*args str

A list of properties to override as template types.

()
Source code in FoSpy/blocks/blocks.py
@classmethod
def TemplateClass(cls,*args:str):
    """
    Create a template for a subclass of `SingleBlock`.

    Generates a hybridized subclass of the current block class and
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock]. Template
    subclasses override original expected validators with either a
    [`TemplateField`][FoSpy.blocks.template.TemplateField],
    [`TemplateBlock`][FoSpy.blocks.template.TemplateBlock], or
    [`TemplateList`][FoSpy.blocks.template.TemplateList] depending on the
    type of the original validator.

    Args:
        *args: A list of properties to override as template types.
    """
    from .template import TemplateBlock, TemplateField, TemplateList
    from ..parsing.validation import required_keys, optional_keys
    if issubclass(cls, TemplateBlock):
        class ExtendedTemplate(cls):
            pass
        SubTemplate = ExtendedTemplate
    else:
        class NewTemplate(TemplateBlock, cls):
            dispatch = {}
            def __init__(self, blockDict, _dispatched=False):
                super().__init__(blockDict, _dispatched=_dispatched)
                self._full_class = cls
        SubTemplate = NewTemplate
    required_keys[SubTemplate] = {}
    optional_keys[SubTemplate] = {}
    required_validators = cls.build_req_validators()
    all_validators = cls.build_validators()
    for key in args:
        req_val = required_validators.get(key,None)
        val = all_validators.get(key,None) or req_val

        if isinstance(val,type) and issubclass(val,SingleBlock):
            all_fields = list(val.build_req_validators().keys())
            field = val.TemplateClass(*all_fields)

        elif isinstance(val,type) and issubclass(val, ListBlock):
            field = TemplateList.Simple(val._reqCls)
        else:
            field = TemplateField

        if req_val:
            required_keys[SubTemplate][key] = field
        else:
            optional_keys[SubTemplate][key] = field

    finished_reqs = required_keys[SubTemplate]
    finished_opts = optional_keys[SubTemplate]
    for typ, sub in cls.dispatch.items():
        dispatched_sub = sub.TemplateClass(*args)
        SubTemplate.dispatch[typ] = dispatched_sub

        required_keys[dispatched_sub] = finished_reqs
        optional_keys[dispatched_sub] = finished_opts

    SubTemplate.__name__ = f"{cls.__name__}Template"
    SubTemplate.__qualname__ = f"{cls.__name__}.Template"
    SubTemplate.__module__ = cls.__module__

    return SubTemplate

__delattr__(attr)

Source code in FoSpy/blocks/blocks.py
def __delattr__(self, attr):
    if attr in self.get_req_validators():
        raise AttributeError(f"Cannot delete property: '{attr}'. It is registered as a required property for this object.")
    return super().__delattr__(attr)

__eq__(other, suppress_routine_paths=False)

Check equality of two SingleBlock objects.

Equality is checked by a deep difference of their serialized dictionaries.

Parameters:

Name Type Description Default
suppress_routine_paths bool

Optional flag to still return true if the only differences found are in calculation routine metadata. Calculation routines are for user information only and may not be relevant for equality.

False
Source code in FoSpy/blocks/blocks.py
def __eq__(self, other, suppress_routine_paths:bool=False):
    """
    Check equality of two `SingleBlock` objects.

    Equality is checked by a deep difference of their
    [serialized][FoSpy.blocks.blocks.SingleBlock.serialize] dictionaries.

    Args:
        suppress_routine_paths:
            Optional flag to still return true if the only differences found
            are in [calculation
            routine][FoSpy.blocks.blocks.SingleBlock.add_calc_routine]
            metadata. Calculation routines are for user information only and
            may not be relevant for equality.
    """
    from .._debug import deep_diff as dd, _debug as db
    try:
        db.msg("Serializing Blocks to check equality:", module = "SingleBlock.__eq__()")
        diffs = dd(self.serialize(), other.serialize(), suppress_routine_paths=suppress_routine_paths)
        passed = len(diffs) == 0
        if not passed:
            db.pmsg(diffs,module = "SingleBlock.__eq__()")
        return passed
    except Exception as e:
        db.msg(f"Equality failed by exception: {e}",module = "SingleBlock.__eq__()")
        return False

__getattr__(name)

Check both self and self.ext for attribute before returning.

A matching attribute of self will be returned first, but if self has no matching attribute, a matching attribute of self.ext can be returned instead.

Source code in FoSpy/blocks/blocks.py
def __getattr__(self, name:str):
    """
    Check both `self` and `self.ext` for attribute before returning.

    A matching attribute of `self` will be returned first, but if `self` has
    no matching attribute, a matching attribute of `self.ext` can be
    returned instead.
    """
    try:
        if name != 'ext':
            return getattr(self.ext, name)
        raise AttributeError()
    except AttributeError:
        raise AttributeError(
            f"{type(self).__name__} object "
            f"has no attribute {name!r}."
        )

__hash__()

Source code in FoSpy/blocks/blocks.py
def __hash__(self):
    return id(self)

__init__(blockDict, _dispatched=False)

Constructs a SingleBlock object from a dictionary.

Avoid using this constructor for unfamiliar block classes, it may bypass subclass delegation. Use dispatch_subclass instead.

SingleBlocks are constructed recursively from an arbitrarily nested dictionary. All keys identified by SingleBlock.build_req_validators() must be present at the top level. Required keys at nested levels are handled by the recursed constructor.

Parameters:

Name Type Description Default
blockDict dict

An arbitrarily nested dictionary mapping attribute names to values.

Unexpected attributes will be assigned under self.ext instead (see SingleBlock.__setattr__).

It is possible to pass a blockDict already containing objects, but validation routines will fail if objects are not the correct type. Best practice is to serialize all nested objects into lists, dicts, and strings to allow full type coersion.

required
_dispatched

Flag passed by dispatch_subclass to signal that the safer construction method was used. Warning issued for False

False

Raises:

Type Description
ValueError

A key required by SingleBlock.build_req_validators() is not present.

TypeError

The value passed as blockDict was not able to be unwrapped into a dict, either by serialization of a passed Block object or by list index.

Source code in FoSpy/blocks/blocks.py
def __init__(self, blockDict:dict, _dispatched=False):
    """
    Constructs a SingleBlock object from a dictionary.

    Avoid using this constructor for unfamiliar block classes, it may bypass
    subclass delegation. Use
    [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
    instead.

    SingleBlocks are constructed recursively from an arbitrarily nested
    dictionary. All keys identified by `SingleBlock.build_req_validators()`
    must be present at the top level. Required keys at nested levels are
    handled by the recursed constructor.

    Args:
        blockDict:
            An arbitrarily nested dictionary mapping attribute names to
            values. 

            Unexpected attributes will be assigned under `self.ext` instead
            (see `SingleBlock.__setattr__`). 

            It is possible to pass a blockDict already containing objects,
            but validation routines will fail if objects are not the correct
            type. Best practice is to serialize all nested objects into
            lists, dicts, and strings to allow full type coersion.
        _dispatched:
            Flag passed by
            [`dispatch_subclass`][FoSpy.blocks.blocks.SingleBlock.dispatch_subclass]
            to signal that the safer construction method was used. Warning
            issued for False


    Raises:
        ValueError:
            A key required by `SingleBlock.build_req_validators()` is not
            present.
        TypeError:
            The value passed as `blockDict` was not able to be unwrapped
            into a dict, either by serialization of a passed `Block` object
            or by list index.

    """

    if not _dispatched:
        from warnings import warn
        warn(f"You should avoid directly constructing a {type(self).__name__} object. Use the dispatch_subclass() "
             "method instead to allow for subclass delegation when constructing.", stacklevel=2)

    self.track_attachments(**cfg.track_attachments())

    from ..parsing.validation import aliases as als
    new_als = als.copy()
    new_als.update(self._aliases or {})
    self._aliases = new_als
    self._reserved = ['ext']

    blockDict = _unwrap_block(blockDict)
    self._sourceDict = blockDict.copy()

    if not isinstance(blockDict, dict):
        raise TypeError("A SingleBlock must be constructed from either a dictionary or another SingleBlock. "
                        "The passed source can optionally be wrapped in lists of length == 1.")

    blockDict = blockDict.copy()

    rename = blockDict.pop("rename",None)
    if rename:
        setattr(self, "rename", _unwrap_block(rename))

    req = self.get_req_validators()
    req.pop("ext",None)
    for key, validator in req.items():
        if key not in blockDict:
            from .template import TemplateBlock, TemplateList, TemplateField, FlexTemplate
            is_type = isinstance(validator, type)
            if is_type and issubclass(validator, TemplateField):
                blockDict[key] = ""
            elif is_type and issubclass(validator, FlexTemplate):
                blockDict[key] = {"template_name": f"Empty {self._baseReq.__name__} Template"}
            elif is_type and issubclass(validator, TemplateBlock):
                blockDict[key] = validator.reflex()
            elif is_type and issubclass(validator, TemplateList):
                blockDict[key] = []
            else:
                raise ValueError(f"Missing required property: '{key}' for '{type(self).__name__}' object.")

    self._meta = SubContainer()
    self._calc_comments = {}
    self._calc_routines = []
    self._key_overrides = {}

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        setattr(self._meta, attr, blockDict.pop(key,k))

    self._key_order = []
    self.ext = SubContainer()

    for key, val in blockDict.items():
        self._key_order.append(key)
        setattr(self, key, val)

__setattr__(name, value)

Assign an attribute with validation and controlled namespace behavior.

Contract

SingleBlock enforces type correctness and validator execution for all public attributes defined for its subclass.

Required types and validators are mapped by subclass in parsing.validation

Comment-mutating methods are attached to every object assigned to an attribute. (See add_comments)

Rules
  1. Private attributes (_-prefixed) bypass validation.
  2. Attributes with registered validators are processed through the validator before assignment.
  3. Attributes with required types are coerced by calling the type constructor when necessary.
  4. Unrecognized attributes can be assigned to a validator mapped to an alias in parsing.validation, using the syntax name="name$alias".
  5. Unrecognized attribute names are redirected as attributes of self.ext.

Raises:

Type Description
ValueError
  • If a required SingleBlock is passed as a list with length > 1.
  • If a block alias cannot be parsed from a key containing $.
  • If a block alias is unrecognized.
  • If a template field is found when constructing a non-template subclass.
Source code in FoSpy/blocks/blocks.py
def __setattr__(self, name:str, value):
    """
    Assign an attribute with validation and controlled namespace behavior.

    Contract:
        `SingleBlock` enforces type correctness and validator execution for
        all public attributes defined for its subclass.

        Required types and validators are mapped by subclass in
        [`parsing.validation`][FoSpy.parsing.validation]

        Comment-mutating methods are attached to every object assigned to an
        attribute. (See
        [`add_comments`][FoSpy.blocks.blocks._add_comments_to_parent])

    Rules:
        1. Private attributes (`_`-prefixed) bypass validation.
        2. Attributes with registered validators are processed through the
        validator before assignment.
        3. Attributes with required types are coerced by calling the type
        constructor when necessary.
        4. Unrecognized attributes can be assigned to a validator mapped to
        an alias in [`parsing.validation`][FoSpy.parsing.validation], using
        the syntax `name="name$alias"`.
        5. Unrecognized attribute names are redirected as attributes of
        `self.ext`.

    Raises:
        ValueError:
            - If a required `SingleBlock` is passed as a list with length > 1.
            - If a block alias cannot be parsed from a key containing `$`.
            - If a block alias is unrecognized.
            - If a template field is found when constructing a non-template
              subclass.
    """
    from ..parsing.format_fos import format_field
    from .template import TemplateField, TemplateBlock

    from inspect import signature as sign

    if name.startswith("_") or name in self._reserved:
        return super().__setattr__(name, value)

    validators = self.get_validators()

    if "$" in name:
        try:
            name, alias = name.split("$")
        except:
            raise ValueError(f"Unable to parse a block alias from key: '{name}'.")

        try:
            val = self._aliases[alias]
        except KeyError:
            raise ValueError(f"Unrecognized block alias: '{alias}'")

        if name in validators and val != validators[name]:
            raise ValueError(f"Key: '{name}' is already reserved for '{validators[name].__name__}' validator, "
                             f"it cannot be overwritten to '{val.__name__}'.")
        if name not in validators:
            validators[name] = val
            self._key_overrides[name] = val

    if name in validators:
        validator = validators[name]
        val_kwargs = {}
        for kw, arg in (("sourceDict", self._sourceDict),("cls", type(self))):
            try:
                if kw in sign(validator).parameters:
                    val_kwargs[kw] = arg
            except:
                pass


        if isinstance(validator, type):
            if issubclass(validator, SingleBlock):
                if not isinstance(value, validator):
                    validator = validator.dispatch_subclass
                    if isinstance(value, list):
                        if len(value) > 1:
                            raise ValueError(f"Block '{name}' must be a single block. It can only be constructed from a list of length 1.")
                        value = value[0]
                    elif isinstance(value,SingleBlock):
                        value = value.serialize(keepListType=True)

            elif issubclass(validator, ListBlock):
                try: 
                    value = [block.serialize(keepListType=True) for block in value]
                except Exception as e:
                    if isinstance(value, ListBlock):
                        value = value.serialize()
            elif value == format_field("template") and not issubclass(validator, TemplateField):
                if isinstance(self,TemplateBlock):
                    validator = TemplateField
                else:       
                    raise ValueError(f"You cannot create a '{type(self).__name__}' object with an un-filled '{name}' template field.")
            if isinstance(validator, type) and isinstance(value, validator):
                return self._assign_and_inject(name, value)




        return self._assign_and_inject(name,
                                        validator(value,**val_kwargs)
                                        if val_kwargs != {}
                                        else validator(value))
    else:
        return self._assign_and_inject(name, value, extended=True)

_assign_and_inject(name, value, extended=False)

Attaches attributes and methods to any value before assigning it as an attribute of self or self.ext.

Attributes Attached to Object

_parent_block: refers to self

Methods Attached to Object

add_comments_to_parent clear_comments_from_parent

Source code in FoSpy/blocks/blocks.py
def _assign_and_inject(self, name, value, extended=False):
    """
    Attaches attributes and methods to any value before assigning it as an
    attribute of `self` or `self.ext`.

    Attributes Attached to Object:
        `_parent_block`: refers to `self`

    Methods Attached to Object:
        [`add_comments_to_parent`][FoSpy.blocks.blocks._add_comments_to_parent]
        [`clear_comments_from_parent`][FoSpy.blocks.blocks._clear_comments_from_parent]
    """
    from .attachments import Attachment

    if name == 'ext':
        return super().__setattr__('ext', value)
    if not hasattr(value, "__dict__"):
        value = SimpleWrapper(value)

    if extended:
        setattr(self.ext, name, value)
    else:
        super().__setattr__(name, value)

    attr_obj = getattr(self.ext if extended else self, name)

    setattr(attr_obj, "_parent_block", self)

    if isinstance(attr_obj, Attachment):
        attr_obj._get_filepath()
    elif hasattr(attr_obj, "refresh_attachments"):
        attr_obj.refresh_attachments()

    methods = ((_add_comments_to_parent(name), "add_comments"),
            (_clear_comments_from_parent(name), "clear_comments"))

    attr_obj._reserved = ['ext'] if not hasattr(attr_obj,"_reserved") else attr_obj._reserved
    for method, method_name in methods:
        attr_obj._reserved.append(method_name)
        bound = method.__get__(attr_obj, type(attr_obj))
        setattr(attr_obj, method_name, bound)

_meta_to_front()

Moves metadata to the front of _key_order. Metadata will always be serialized first, but being elsewhere in the order leads to unexpected results when moving other keys to desired indices.

Source code in FoSpy/blocks/blocks.py
def _meta_to_front(self):
    """
    Moves metadata to the front of `_key_order`. Metadata will always be
    serialized first, but being elsewhere in the order leads to unexpected
    results when moving other keys to desired indices.
    """
    try:
        meta_idx =self._key_order.index("metadata")
        self._key_order.pop(meta_idx)
    except:
        pass
    self._key_order.insert(0,"metadata")

_rename_validators(validators)

Realigns any renamed attributes with their expected validator.

Parameters:

Name Type Description Default
validators dict

A dictionary mapping attribute names to validators, returned by either build_validators or build_req_validators

required
Source code in FoSpy/blocks/blocks.py
def _rename_validators(self, validators:dict):
    """
    Realigns any [renamed][FoSpy.blocks.blocks.SingleBlock.rename_block]
    attributes with their expected validator.

    Args:
        validators:
            A dictionary mapping attribute names to validators, returned by
            either
            [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]
            or
            [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    if hasattr(self, "rename"):
        for name, rename in self.rename.serialize(shallow=True).items():
            if name in validators and rename not in validators:
                val = validators.pop(name)
                validators[rename] = val
    return validators

_resolve_relative_path(path)

Resolves a relative object path string into an object or function.

Example:

    mySyn._resolve_relative_path("materials[1].ratio")
    ## returns mySyn.materials[1].ratio

Source code in FoSpy/blocks/blocks.py
def _resolve_relative_path(self, path: str):
    """
    Resolves a relative object path string into an object or function.

    Example:
    ```
        mySyn._resolve_relative_path("materials[1].ratio")
        ## returns mySyn.materials[1].ratio
    ```
    """
    import re

    _index_re = re.compile(r"^([A-Za-z_]\w*)\[(\d+)\]$")
    obj = self

    for part in path.split("."):

        # Case: attr[index]
        m = _index_re.match(part)
        if m:
            attr_name, idx_str = m.groups()
            idx = int(idx_str)

            # Get the ListBlock
            obj = getattr(obj, attr_name)

            # Index into its _objs
            obj = obj._objs[idx]
            continue

        # Case: simple attribute
        obj = getattr(obj, part)

    return obj

_subprocess(target, args=(), **kwargs)

Source code in FoSpy/blocks/blocks.py
def _subprocess(self, target, args=(), **kwargs):
    from multiprocessing import Process

    if kwargs is None:
        kwargs={}

    p = Process(target=target, args=args, kwargs=kwargs)
    p.start()

add_all_calc_routines(recursive=False)

Schedule all available calculation routines.

Adds all available calc_routines to self._calc_routines using list_avail_routines() and add_calc_routine().

Parameters:

Name Type Description Default
recursive bool

Optional recursion. See SingleBlock.list_avail_routines()

False
Source code in FoSpy/blocks/blocks.py
def add_all_calc_routines(self, recursive:bool=False):
    """
    Schedule all available calculation routines.

    Adds all available calc_routines to `self._calc_routines` using
    [`list_avail_routines()`][FoSpy.blocks.blocks.SingleBlock.list_avail_routines]
    and
    [`add_calc_routine()`][FoSpy.blocks.blocks.SingleBlock.add_calc_routine].

    Args:
        recursive:
            Optional recursion. See `SingleBlock.list_avail_routines()`
    """
    for path in self.list_avail_routines(recursive=recursive, abbreviated=False):
        self.add_calc_routine(path)

add_block(block_name, type_alias, value=[])

Adds an unexpected attribute with a validator mapped by type_alias. Unexpected attributes not requiring a validator can be set directly without using this method.

Parameters:

Name Type Description Default
block_name str

new unexpected attribute name

required
type_alias str

Alias mapped to the desired validator in parsing.validation.aliases. For more information on how aliases are used, see __setattr__.

required
Source code in FoSpy/blocks/blocks.py
def add_block(self, block_name:str, type_alias:str, value=[]):
    """
    Adds an unexpected attribute with a validator mapped by `type_alias`.
    Unexpected attributes not requiring a validator can be set directly
    without using this method.

    Args:
        block_name: new unexpected attribute name
        type_alias:
            Alias mapped to the desired validator in
            [`parsing.validation.aliases`][FoSpy.parsing.validation.aliases].
            For more information on how aliases are used, see
            [`__setattr__`][FoSpy.blocks.blocks.SingleBlock.__setattr__].
    """
    if hasattr(self,block_name):
        raise ValueError(f"This object already has attribute: '{block_name}'.")
    return setattr(self, f"{block_name}${type_alias}", value)

add_calc_comment(key, comment, calc_id)

Add a calculated comment to be injected during serialization.

WARNING: This function can leave outdated calculations in comments after serialization. Recommended to use add_calc_routine() instead.

Calculated comments are for user information and will be formatted to be skipped by the parser when reading the file. This is useful for comments that should be recalculated and refreshed during saving/serialization, like weight percentages or summaries.

Parameters:

Name Type Description Default
key str

attribute to attach the calculated comment to. Comments appear above their attached attributes in FOS format.

required
comment str

comment text without comment formatting (don't include // or !)

required
calc_id str

unique identifier for the calculated comment. If it matches an existing comment (like when refreshing a value), the comment is overwritten

required
Source code in FoSpy/blocks/blocks.py
def add_calc_comment(self, key:str, comment:str, calc_id:str):
    """
    Add a calculated comment to be injected during serialization.

    WARNING: This function can leave outdated calculations in comments after
    serialization. Recommended to use `add_calc_routine()` instead.

    Calculated comments are for user information and will be formatted to be
    skipped by the parser when reading the file. This is useful for comments
    that should be recalculated and refreshed during saving/serialization,
    like weight percentages or summaries.

    Args:
        key:
            attribute to attach the calculated comment to. Comments appear
            above their attached attributes in FOS format.
        comment:
            comment text without comment formatting (don't include // or !)
        calc_id:
            unique identifier for the calculated comment. If it matches an
            existing comment (like when refreshing a value), the comment is
            overwritten

    """
    calc_comments = self._calc_comments.get(key, {})
    self._calc_comments[key] = calc_comments
    self._calc_comments[key][calc_id]=comment

add_calc_routine(path, **kwargs)

Schedules a calculated comment.

Appends a _calc_routine()-decorated function to self._calc_routines to be run at serialization.

Used to add calculated comments that should be refreshed during serialization.

Parameters:

Name Type Description Default
path str

a relative path string that can be resolved into a _calc_routine()-decorated function

required
**kwargs any

optional key word arguments to be passed to the function at path.

{}

Raises:

Type Description
TypeError

the attr or method at path is not registered as a _calc_routine

Example:

    mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
    ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
    ## to run at serialization

Source code in FoSpy/blocks/blocks.py
def add_calc_routine(self, path:str, **kwargs):
    """
    Schedules a calculated comment.

    Appends a
    [`_calc_routine()`][FoSpy.blocks._blockUtils._calc_routine]-decorated
    function to `self._calc_routines` to be run at
    [serialization][FoSpy.blocks.blocks.SingleBlock.serialize].

    Used to add calculated comments that should be refreshed during
    serialization.

    Args:
        path:
            a relative path string that can be resolved into a
            `_calc_routine()`-decorated function
        **kwargs (any):
            optional key word arguments to be passed to the function at
            path.

    Raises:
        TypeError:
            the attr or method at path is not registered as a
            _calc_routine

    Example:
    ```
        mySyn.add_calc_routine("materials.add_weight_pcts", typ="reagent")
        ## mySyn.materials.add_weight_pcts(typ="reagent") is now scheduled
        ## to run at serialization
    ```
    """
    from functools import wraps

    func = self._resolve_relative_path(path)
    if not getattr(func, "_is_calc_routine", False):
        raise TypeError(f"'{path}' is not a registered calc routine.")

    self._meta.routine_paths.append(path)

    @wraps(func)
    def wrapped():
        __name__ = func.__name__
        return func(**kwargs)

    self._calc_routines.append(wrapped)

add_comments(*comments)

Default behavior to be overwritten when attached to a parent block.

If a SingleBlock is stored as an attribute of another SingleBlock, this method will be overwritten by the parent's __setattr__.

Source code in FoSpy/blocks/blocks.py
def add_comments(self, *comments):
    """
    Default behavior to be overwritten when attached to a parent block.

    If a `SingleBlock` is stored as an attribute of another `SingleBlock`,
    this method will be overwritten by the parent's `__setattr__`.
    """
    keys = list(self.get_req_validators())

    keys = [k for k in keys if k != "metadata"]
    fallback = [k for k in self._key_order if k != "metadata"]
    if not (keys or fallback):
        raise ValueError("This object has not been correctly attached to a parent block "
                         "and could not identify a required key to attach to.")

    first = keys[0] if keys else fallback[0]

    self._meta.comments.setdefault(first, [])
    for comment in comments:
        self._meta.comments[first].append(comment)

build_req_validators() classmethod

Builds required keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are required during __init__, and their respective validation routines. Subclasses are mapped to expected keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

Returns:

Name Type Description
merged dict

Maps required keys to validation routines. Routines may be a class constructor or a func taking one arg.

Example:

>>> SingleBlock.build_req_validators()
{
    "name": str,
    "type": str,
    "formula": ChemFormula, # class constructor
    "supplier": str,
    "cas": str,
    "form": str,
    "env": str,
    "ratio": validators.material.ratio # validator function
}

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_req_validators(cls):
    """
    Builds required keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are required
    during `__init__`, and their respective validation routines. Subclasses
    are mapped to expected keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    Returns:
        merged (dict):
            Maps required keys to validation routines. Routines may be
            a class constructor or a func taking one arg.
    Example:
        ``` 
        >>> SingleBlock.build_req_validators()
        {
            "name": str,
            "type": str,
            "formula": ChemFormula, # class constructor
            "supplier": str,
            "cas": str,
            "form": str,
            "env": str,
            "ratio": validators.material.ratio # validator function
        }
        ```
    """
    from ..parsing.validation import required_keys
    merged = {}
    for base in reversed(cls.__mro__):
        base_reqs = required_keys.get(base,{})
        for key, validator in base_reqs.items():
            # allow subclasses to remove parent requirements.
            if validator is False:
                merged.pop(key, None)
            else:
                merged[key] = validator
    return merged

build_validators() classmethod

Builds expected keys and validators mapped to subclass.

Walks all parent classes and builds a map of all keys that are expected (required or optional), and their respective validation routines. Subclasses are mapped to keys and validations in parsing.validation. Subclass validations override parent classes when applicable.

See build_req_validators

Source code in FoSpy/blocks/blocks.py
@classmethod
def build_validators(cls):
    """
    Builds expected keys and validators mapped to subclass.

    Walks all parent classes and builds a map of all keys that are expected
    (required or optional), and their respective validation routines.
    Subclasses are mapped to keys and validations in
    [`parsing.validation`][FoSpy.parsing.validation]. Subclass validations
    override parent classes when applicable.

    See
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators]
    """
    from ..parsing.validation import required_keys, optional_keys
    merged = {}
    for base in reversed(cls.__mro__):
        for key_set in (required_keys, optional_keys):
            base_reqs = key_set.get(base,{})
            for key, validator in base_reqs.items():
                # allow subclasses to remove parent requirements.
                if validator is False:
                    merged.pop(key, None)
                else:
                    merged[key] = validator

    return merged

clear_all_comments()

Source code in FoSpy/blocks/blocks.py
def clear_all_comments(self):
    self._meta.comments = {}
    for attr, val in self.__dict__.items():
        if attr.startswith("_") or attr in self._reserved:
            continue
        if hasattr(val, "clear_all_comments"):
            val.clear_all_comments()

clear_comments()

Clear comments attached to top-level attributes only.

Source code in FoSpy/blocks/blocks.py
def clear_comments(self):
    """
    Clear comments attached to top-level attributes only.
    """
    self._meta.comments = {}

copy()

Returns a deep-copy of self by serializing and reconstructing.

_calc_comments are not preserved during copy, but _calc_routines are. This prevents mutation of the comments when reconstructing.

Source code in FoSpy/blocks/blocks.py
def copy(self):
    """
    Returns a deep-copy of `self` by serializing and reconstructing.

    _calc_comments are not preserved during copy, but _calc_routines are.
    This prevents mutation of the comments when reconstructing.
    """
    cls = type(self)
    c_cmts = self._calc_comments.copy()
    self._calc_comments = {}

    new_obj =  cls.dispatch_subclass(self.serialize(keepListType=True))
    self._calc_comments = c_cmts

    return new_obj

default_key_order(deep=False)

Set to default attribute order for serialization.

Rearrange attribute order to the default order assigned by build_validators

Parameters:

Name Type Description Default
deep bool

When true, recursively calls default_key_order on any other SingleBlock objects stored in attributes.

False
Source code in FoSpy/blocks/blocks.py
def default_key_order(self, deep:bool=False):
    """
    Set to default attribute order for serialization.

    Rearrange attribute order to the default order assigned by
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators]

    Args:
        deep:
            When true, recursively calls `default_key_order` on any other
            `SingleBlock` objects stored in attributes.
    """
    new_order = []
    for key in self.get_validators():
        if key != "ext" and key in self.serialize(shallow=True):
            new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

    if deep:
        for name, obj in self.__dict__.items():
            if not name.startswith("_") and hasattr(obj, "default_key_order"):
                obj.default_key_order(deep=True)

dispatch_subclass(blockDict) classmethod

Source code in FoSpy/blocks/treatments.py
@classmethod
def dispatch_subclass(cls, blockDict):
    from .blocks import _unwrap_block
    blockDict = _unwrap_block(blockDict)
    t = blockDict.get("type", None)
    subclass = cls.dispatch.get(t,cls)
    return subclass(blockDict, _dispatched=True)

example_calc()

Source code in FoSpy/blocks/treatments.py
@_calc_routine(attach=False)
def example_calc(self):
    _debug.msg(f"Running example routine for {self.type} treatment")
    return None

find_fileblock()

Finds the parent file object.

Walks upward through _parent_block attributes until a FileBlock instance is found and returns that instance.

Source code in FoSpy/blocks/blocks.py
def find_fileblock(self):
    """
    Finds the parent file object.

    Walks upward through `_parent_block` attributes until a
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance is found and
    returns that instance.
    """
    from .files import FileBlock
    from .._errors import FileBlockNotFoundError

    blk = self
    while blk is not None:
        if isinstance(blk, FileBlock):
            return blk
        if hasattr(blk,"_parent_block"):
            blk = blk._parent_block
        else:
            blk = None
    raise FileBlockNotFoundError("Could not find a FileBlock containing the current object")

find_tempdir()

Find the parent file object's temporary directory.

Finds the temporary directory created by the FileBlock instance containing this block as one of its attributes.

Returns:

Name Type Description
tempdir tempfile.TemporaryDirectory

The temporary directory created by the parent file object

Source code in FoSpy/blocks/blocks.py
def find_tempdir(self):
    """
    Find the parent file object's temporary directory.

    Finds the temporary directory created by the
    [`FileBlock`][FoSpy.blocks.files.FileBlock] instance containing this
    block as one of its attributes. 

    Returns:
        tempdir (tempfile.TemporaryDirectory):
            The temporary directory created by the parent file object
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_tempdir"):
        return fileblock._tempdir
    else:
        raise AttributeError("Could not find a temporary directory attached to this object's FileBlock")

find_temppath()

Find the parent file object's temporary directory path.

Similar to find_tempdir but returns the corresponding pathlib.Path object instead.

Source code in FoSpy/blocks/blocks.py
def find_temppath(self):
    """
    Find the parent file object's temporary directory path.

    Similar to [`find_tempdir`][FoSpy.blocks.blocks.Block.find_tempdir] but
    returns the corresponding `pathlib.Path` object instead.
    """
    fileblock = self.find_fileblock()
    if hasattr(fileblock, "_temppath"):
        return fileblock._temppath
    if hasattr(fileblock, "_temppdir"):
        raise AttributeError("This object's FileBlock has a temporary directory but no path mapped to it. "
                             "Use obj.find_tempdir() instead")
    raise AttributeError("Could not find a temporary directory object or path "
                         "attached to this object's FileBlock.")

get_req_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_req_validators, but uses _rename_validators to align any renamed properties with their original validators.

Source code in FoSpy/blocks/blocks.py
def get_req_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_req_validators`][FoSpy.blocks.blocks.SingleBlock.build_req_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators.
    """
    return self._rename_validators(self.build_req_validators())

get_validators()

Overrides class validators with any renamed properties.

Similar to class method: build_validators, but uses _rename_validators to align any renamed properties with their original validators. Also adds any optional key overrides added by key$alias syntax.

Returns:

Name Type Description
vals dict

maps expected keys to validation routines.

Source code in FoSpy/blocks/blocks.py
def get_validators(self):
    """
    Overrides class validators with any renamed properties.

    Similar to class method:
    [`build_validators`][FoSpy.blocks.blocks.SingleBlock.build_validators],
    but uses
    [`_rename_validators`][FoSpy.blocks.blocks.SingleBlock._rename_validators]
    to align any renamed properties with their original validators. Also
    adds any optional key overrides added by key$alias syntax.

    Returns:
        vals (dict): maps expected keys to validation routines.
    """
    vals = self._rename_validators(self.build_validators())
    if hasattr(self, "_key_overrides"):
        for key, val in self._key_overrides.items():
            vals[key] = val
    return vals

key_to_idx(key, idx)

Reorder attributes for serialization.

Move any attribute name to a specific index in _key_order for serialization order. The invisible "metadata" key is always refreshed to the front of the list, so indices are effectively 1-based.

Parameters:

Name Type Description Default
key str

name of attribute to reorder

required
idx int

new index in _key_order

required
Source code in FoSpy/blocks/blocks.py
def key_to_idx(self, key:str, idx:int):
    """
    Reorder attributes for serialization.

    Move any attribute name to a specific index in `_key_order` for
    serialization order. The invisible `"metadata"` key is always refreshed
    to the front of the list, so indices are effectively 1-based.

    Args:
        key: name of attribute to reorder
        idx: new index in _key_order
    """
    self._meta_to_front()
    try:
        old_idx = self._key_order.index(key)
        self._key_order.pop(old_idx)
    except:
        pass
    self._key_order.insert(idx, key)

keys_to_end(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the end of _key_order to be serialized last. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_end(self, *args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the end of _key_order to be
    serialized last. Order within `*args` is maintained in result.
    """
    def remove_alias(key):
        return key.split("$")[0] if "$" in key else key
    for key in self.serialize(shallow=True):
        if not key.startswith("_") and remove_alias(key) not in self._key_order:
            self._key_order.append(remove_alias(key))
    for key in args:
        try:
            idx = self._key_order.index(key)
            self._key_order.pop(idx)
        except:
            pass
        self._key_order.append(key)
    self._meta_to_front()

keys_to_front(*args)

Reorder attributes for serialization.

Move any attribute names in *args to the front of _key_order to be serialized first. Order within *args is maintained in result.

Source code in FoSpy/blocks/blocks.py
def keys_to_front(self,*args):
    """
    Reorder attributes for serialization.

    Move any attribute names in `*args` to the front of _key_order to be
    serialized first. Order within `*args` is maintained in result.
    """
    try:
        meta_idx = args.index("metadata")
        args.pop(meta_idx)
    except:
        pass

    new_order = []
    for key in args:
        new_order.append(key)
    for key in self._key_order:
        if key not in new_order:
            new_order.append(key)
    self._key_order = new_order
    self._meta_to_front()

list_avail_routines(recursive=False, prefix='', abbreviated=False)

Lists all calc routines available to be added to self._calc_routines.

Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Parameters:

Name Type Description Default
recursive bool

If True, recursively walks all attributes and appends results from self.attr.list_avail_routines() to result. Otherwise only identifies methods of self.

False
prefix str

Used during recursion to build relative paths

''
abbreviated bool

optionally abbreviate recursively repeated routines for similar objects into one line. This line cannot be passed to self.add_calc_routine()

False

Returns:

Name Type Description
routines list

list of strings describing _calc_routine-decorated methods. Non-abbreviated calc routine strings can be passed directly to self.add_calc_routine()

Example:

    mySyn.list_avail_routines()
    ## returns []
    mySyn.list_avail_routines(recursive=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[0].add_MW',
    ##     'materials[1].add_MW',
    ##     ... 6 total materials with the same calc_routine
    ##     'materials[5].add_MW'
    ## ]
    mySyn.list_avail_routines(recursive=True, abbreviated=True)
    ## returns [
    ##     'reaction.add_nom_MW',
    ##     'materials.add_weight_pcts',
    ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
    ## ]

Source code in FoSpy/blocks/blocks.py
def list_avail_routines(self, recursive:bool=False, prefix:str="", abbreviated:bool=False):
    """
    Lists all calc routines available to be added to `self._calc_routines`.

    Non-abbreviated calc routine strings can be passed directly to
    `self.add_calc_routine()`

    Args:
        recursive:
            If True, recursively walks all attributes and appends results
            from `self.attr.list_avail_routines()` to result. Otherwise only
            identifies methods of `self`.

        prefix: Used during recursion to build relative paths
        abbreviated:
            optionally abbreviate recursively repeated routines for similar
            objects into one line. This line cannot be passed to
            `self.add_calc_routine()`

    Returns:
        routines (list): 
            list of strings describing _calc_routine-decorated methods.
            Non-abbreviated calc routine strings can be passed directly to
            `self.add_calc_routine()`

    Example:
    ```
        mySyn.list_avail_routines()
        ## returns []
        mySyn.list_avail_routines(recursive=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[0].add_MW',
        ##     'materials[1].add_MW',
        ##     ... 6 total materials with the same calc_routine
        ##     'materials[5].add_MW'
        ## ]
        mySyn.list_avail_routines(recursive=True, abbreviated=True)
        ## returns [
        ##     'reaction.add_nom_MW',
        ##     'materials.add_weight_pcts',
        ##     'materials[i].add_MW; i = [0, 1, 2, 3, 4, 5]'
        ## ]
    ```
    """
    routines = []

    # Local routines
    for name in dir(self):
        attr = getattr(self, name)
        if callable(attr) and getattr(attr, "_is_calc_routine", False):
            routines.append(prefix + name)

    if recursive:
        for attr, val in self.__dict__.items():
            if attr.startswith("_"):
                continue

            # Recurse into child blocks
            if hasattr(val, "list_avail_routines"):
                child_prefix = f"{prefix}{attr}."
                routines.extend(val.list_avail_routines(True, child_prefix, abbreviated))

    return routines

make_template(template_name, *args)

Converts self into a template of its original subclass.

Returns a copy of self as a template of its original subclass, with specified fields replaced with template types. See TemplateClass for more information on template generation.

Parameters:

Name Type Description Default
template_name str

All templates require an identifying name.

required
*args str

properties to clear and replace with template types.

()
Source code in FoSpy/blocks/blocks.py
def make_template(self,template_name:str,*args:str):
    """
    Converts `self` into a template of its original subclass.

    Returns a copy of `self` as a template of its original subclass, with
    specified fields replaced with template types. See
    [`TemplateClass`][FoSpy.blocks.blocks.SingleBlock.TemplateClass] for
    more information on template generation.

    Args:
        template_name: All templates require an identifying name.
        *args: properties to clear and replace with template types.
    """

    from ..parsing.format_fos import format_field

    serial = self.serialize(keepListType=True)
    validators = self.get_validators()
    for key in args:
        val = validators.get(key, None)
        if isinstance(val,type) and (issubclass(val, SingleBlock) or issubclass(val, ListBlock)):
            serial[key] = []
        else:
            serial[key] = format_field("template")
    serial["template_name"] = template_name
    return type(self).TemplateClass(*args).dispatch_subclass(serial)

reflex(serialize=True, **kwargs) classmethod

Generate a flexible template for the current class.

Flexibly generates a template for the current class where any required properties missing from kwargs are automatically converted to template types (See FlexTemplate). Returns an instance of the flexible template constructed from kwargs, or a serial dictionary of that instance.

Parameters:

Name Type Description Default
serialize bool

Whether to return the serialized dictionary of the reflexed template, or the object itself.

True
**kwargs str

Known properties to pass to the template constructor.

{}
Source code in FoSpy/blocks/blocks.py
@classmethod
def reflex(cls, serialize=True, **kwargs:dict):
    """
    Generate a flexible template for the current class.

    Flexibly generates a template for the current class where any required
    properties missing from `kwargs` are automatically converted to template
    types (See
    [`FlexTemplate`][FoSpy.blocks.template.FlexTemplate]).
    Returns an instance of the flexible template constructed from `kwargs`,
    or a serial dictionary of that instance.

    Args:
        serialize (bool):
            Whether to return the serialized dictionary of the reflexed
            template, or the object itself.
        **kwargs (str): Known properties to pass to the template constructor.
    """
    from .template import FlexTemplate
    class Flex(FlexTemplate, cls):
        _baseReq = cls

    kwargs.setdefault("template_name", f"Reflexed {cls.__name__}")

    empty = Flex.dispatch_subclass(kwargs)
    if serialize:
        return empty.serialize()
    return empty

refresh_attachments(new_copy=None, overwrite=None, **kwargs)

Source code in FoSpy/blocks/blocks.py
def refresh_attachments(self, new_copy=None, overwrite=None, **kwargs):
    from .attachments import Attachment

    if new_copy is None:
        new_copy = self._att_new_copy
    if overwrite is None:
        overwrite = self._att_overwrite

    for propDict in self.__dict__, self.ext.__dict__:
        for key, val in propDict.items():
            if key.startswith("_") or key in self._reserved:
                continue
            if hasattr(val, "refresh_attachments"):
                val.refresh_attachments(new_copy=new_copy, overwrite=overwrite, **kwargs)
            elif isinstance(val, Attachment) and hasattr(val, "refresh"):
                val.refresh(new_copy=new_copy, overwrite=overwrite, **kwargs)

rename_block(old, new)

Source code in FoSpy/blocks/blocks.py
def rename_block(self, old, new):
    validators = self.get_validators()
    req = self.get_req_validators()
    if True in [name.startswith("_") for name in (old, new)]:
        raise ValueError(f"You cannot set private attributes (starting with '_') using obj.rename_block()")

    if old in req and new in validators:
        raise ValueError(f"You cannot rename '{old}' to '{new}'. '{old}' is a required property that "
                            f"can only be renamed to an unregistered key; '{new}' is already registered "
                            "as an expected property.")

    if hasattr(self, new):
        raise ValueError(f"'{new}' is already a property for this object, you cannot overwrite it with "
                         "obj.rename_block()")

    if "rename" in (old, new):
        raise ValueError("obj.rename property cannot be set or changed by obj.rename_block()")

    if old in self._key_overrides:
        val = self._key_overrides.pop(old)
        self._key_overrides[new] = val
    else:
        _debug.msg(f"Registering '{old}':'{new}' into rename block")
        if not hasattr(self,"rename"):
            self.rename = {}
        setattr(self.rename, old, new)
    _debug.msg(f"Moving '{old}' over to '{new}'.")
    setattr(self,new,getattr(self, old))
    delattr(self,old)

    try:
        idx = self._key_order.index(old)
        self._key_order[idx] = new
    except:
        self._key_order.append(new)

serialize(keepListType=False, shallow=False, clean=False)

Return a recursively serialized dict representation of self.

Fully serialized SingleBlocks are a single dict that can be passed to another constructor or emitted into lines for a FOS file. Serialized values at any nest level are either dicts, lists, or strings to allow full type-coersion when reconstructing or simplified emission when writing files.

Serialized dict is deep copied to prevent object mutation.

Parameters:

Name Type Description Default
keepListType bool

When True, maintains its current FOS printing mode (looped keys or explicit key:value lines), instead of explicit default

False
shallow bool

When True, no recursive serialization occurs. Recommended when serialization is used only to inspect top-level keys.

False
clean bool

When True, no FOS format read/write metadata is included in the serial. Recommended for sending output to other formats like JSON.

False

Private attributes starting with "_" are either skipped or unpacked in special cases:

  • _key_order: attributes are added to the serialized dict in the order they appear in this list.

  • _calc_comments: calculated comments are attached to their mapped attribute after serialization to avoid mutation of object comments

  • _calc_routines: A list of functions scheduled to be called right before serialization to update _calc_comments. Scheduling calc routines ensures that their calculated values are up-to-date.

  • _meta: attributes of this container are given their own private _keys mapped by FoSpy.parsing.syntax.meta_keys in the serialized dict.

  • _key_overrides: per-instance override mapping that tracks which unexpected attributes require $alias suffixes.

  • _aliases: maps attribute names to alias tags used to emit $alias suffixed keys.

  • _reserved: attribute names in reserved are non-private attributes which should not be serialized. This usually applies to the ext attribute or methods attached after construction.

Source code in FoSpy/blocks/blocks.py
def serialize(self, keepListType:bool=False, shallow:bool=False, clean:bool=False):
    """
    Return a recursively serialized `dict` representation of `self`.

    Fully serialized `SingleBlock`s are a single dict that can be passed to
    another constructor or emitted into lines for a FOS file. Serialized
    values at any nest level are either dicts, lists, or strings to allow
    full type-coersion when reconstructing or simplified emission when
    writing files.

    Serialized dict is deep copied to prevent object mutation.

    Args:
        keepListType:
            When True, maintains its current FOS printing mode (looped keys
            or explicit key:value lines), instead of explicit default

        shallow:
            When True, no recursive serialization occurs. Recommended when
            serialization is used only to inspect top-level keys.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. Recommended for sending output to other formats like
            JSON.

    Private attributes starting with "_" are either skipped or unpacked in
    special cases:

    * `_key_order`:
        attributes are added to the serialized dict in the order they
        appear in this list.

    * `_calc_comments`:
        calculated comments are attached to their mapped attribute after
        serialization to avoid mutation of object comments

    * `_calc_routines`:
        A list of functions scheduled to be called right before
        serialization to update _calc_comments. Scheduling calc routines
        ensures that their calculated values are up-to-date.

    * `_meta`:
        attributes of this container are given their own private `_key`s
        mapped by `FoSpy.parsing.syntax.meta_keys` in the serialized
        dict.

    * `_key_overrides`:
        per-instance override mapping that tracks which unexpected
        attributes require $alias suffixes.

    * `_aliases`:
        maps attribute names to alias tags used to emit $alias suffixed
        keys.

    * `_reserved`:
        attribute names in reserved are non-private attributes which
        should *not* be serialized. This usually applies to the `ext`
        attribute or methods attached after construction.
    """
    from copy import deepcopy
    from ..parsing.format_fos import format_calc_comment
    from .template import TemplateBlock

    val_to_alias = {v:k for k,v in self._aliases.items()}

    all_attrs = {}
    out = {}

    for routine in self._calc_routines:
        routine()

    def add_alias(key):
        if key in self._key_overrides:
            alias = val_to_alias[self._key_overrides[key]]
            return f"{key}${alias}"
        return key


    def try_serial(obj):
        serialize = getattr(obj, "serialize", None)
        if isinstance(obj, SimpleWrapper):
            obj = obj()
        if callable(serialize) and not shallow:
            return obj.serialize(clean=clean)
        if isinstance(obj, list):
            return [try_serial(item) for item in obj]
        if isinstance(obj, dict):
            return {k:try_serial(v) for k,v in obj.items()}
        return str(obj)

    for attr,val in self.__dict__.items():
        if attr == "ext" and val is not None:
            for ext_attr, ext_val in val.__dict__.items():
                all_attrs[ext_attr] = ext_val
        elif not (attr.startswith("_") or attr in self._reserved):
            all_attrs[attr] = val


    for key in self._key_order:
        if key in all_attrs:
            val = all_attrs.pop(key)
            out[add_alias(key)] = try_serial(val)

    for key, val in all_attrs.items():
        out[add_alias(key)] = try_serial(val)

    for attr, key in mk.items():
        try:
            k = md[key].copy()
        except:
            k = md[key]
        val = getattr(self._meta,attr,k)
        out[key] = val

    comments = {}
    for key, comment_list in out[mk["comments"]].items():
        comments[add_alias(key)] = comment_list
    out[mk["comments"]] = comments

    out = deepcopy(out)

    # _debug.pmsg(self._calc_comments)
    for key, comments in self._calc_comments.items():
        for comment in comments.values():
            out[mk["comments"]].setdefault(add_alias(key),[])
            out[mk["comments"]][add_alias(key)].append(format_calc_comment(comment))

    if not keepListType:
        out[mk["list_type"]] = "explicit"

    if "template_name" in out and not isinstance(self, TemplateBlock):
        out.pop("template_name")

    if clean:
        scan = out.copy()
        for key, val in scan.items():
            if key.startswith("_") or val is None:
                out.pop(key)

    return out

to_json(filepath=None, clean=True, indent=4, **kwargs)

Converts self into a JSON-formatted string or file.

Serializes and either returns as a JSON-formatted string or saves to a JSON file.

Parameters:

Name Type Description Default
filepath pathlike

JSON file save destination. If None, returns JSON-formatted string instead.

None
clean bool

When True, no FOS format read/write metadata is included in the serial. FOS metadata has no impact on JSON format but may be useful to view in JSON for troubleshooting.

True
indent int

indent value passed to json.dump for file saving.

4
**kwargs any

other arguments passed to json.dump for file saving.

{}
Source code in FoSpy/blocks/blocks.py
def to_json(self, filepath=None, clean:bool=True, indent:int=4, **kwargs):
    """
    Converts `self` into a JSON-formatted string or file.

    [Serializes][FoSpy.blocks.blocks.SingleBlock.serialize] and either
    returns as a JSON-formatted string or saves to a JSON file.

    Args:
        filepath (pathlike):
            JSON file save destination. If `None`, returns JSON-formatted
            string instead.

        clean:
            When True, no FOS format read/write metadata is included in the
            serial. FOS metadata has no impact on JSON format but may be
            useful to view in JSON for troubleshooting.

        indent:
            `indent` value passed to `json.dump` for file saving.

        **kwargs (any):
            other arguments passed to `json.dump` for file saving.
    """
    import json
    serial = self.serialize(clean=clean)

    if filepath is None:
        return json.dumps(serial)

    with open(filepath, "w") as f:
        json.dump(serial, f, indent=indent, **kwargs)

track_attachments(new_copy='prompt', overwrite='prompt', **kwargs)

Source code in FoSpy/blocks/blocks.py
def track_attachments(self, new_copy="prompt",overwrite="prompt", **kwargs):
    self._att_new_copy = new_copy
    self._att_overwrite = overwrite

TreatmentList