Source code for laser_core.propertyset

"""Implements a PropertySet class that can be used to store properties in a dictionary-like object."""

import json
from pathlib import Path


[docs] class PropertySet: """A class that can be used to store properties in a dictionary-like object with `.property` access to properties. Examples -------- Basic Initialization: >>> from laser_core import PropertySet >>> ps = PropertySet() >>> ps['infection_status'] = 'infected' >>> ps['age'] = 35 >>> print(ps.infection_status) # Outputs: 'infected' >>> print(ps['age']) # Outputs: 35 Combining Two PropertySets: >>> ps1 = PropertySet({'immunity': 'high', 'region': 'north'}) >>> ps2 = PropertySet({'infectivity': 0.7}) >>> combined_ps = ps1 + ps2 >>> print(combined_ps.to_dict()) {'immunity': 'high', 'region': 'north', 'infectivity': 0.7} Creating a PropertySet from a Dictionary: >>> ps = PropertySet({'mything': 0.4, 'that_other_thing': 42}) >>> print(ps.mything) # Outputs: 0.4 >>> print(ps.that_other_thing) # Outputs: 42 >>> print(ps.to_dict()) {'mything': 0.4, 'that_other_thing': 42} Save and Load: >>> ps.save('properties.json') >>> loaded_ps = PropertySet.load('properties.json') >>> print(loaded_ps.to_dict()) # Outputs the saved properties Property Access and Length: >>> ps['status'] = 'susceptible' >>> ps['exposure_timer'] = 5 >>> print(ps['status']) # Outputs: 'susceptible' >>> print(len(ps)) # Outputs: 4 In-Place Addition: >>> ps += {'new_timer': 10, 'infectivity': 0.8} >>> print(ps.to_dict()) {'mything': 0.4, 'that_other_thing': 42, 'status': 'susceptible', 'exposure_timer': 5, 'new_timer': 10, 'infectivity': 0.8} """ def __init__(self, *bags): """ Initialize a PropertySet to manage properties in a dictionary-like structure. Parameters ---------- *bags : iterable, optional A sequence of key-value pairs (e.g., lists, tuples, dictionaries) to initialize the PropertySet. Keys must be strings, and values can be any type. """ for bag in bags: assert isinstance(bag, (type(self), dict)) for key, value in (bag.__dict__ if isinstance(bag, type(self)) else bag).items(): setattr(self, key, value)
[docs] def to_dict(self): """Convert the PropertySet to a dictionary.""" result = {} for key, value in self.__dict__.items(): if isinstance(value, PropertySet): result[key] = value.to_dict() else: result[key] = value return result
[docs] def save(self, filename): """ Save the PropertySet to a specified file. Parameters: filename (str): The path to the file where the PropertySet will be saved. Returns: None """ file = Path(filename) with file.open("w") as file: file.write(str(self)) return
[docs] def __getitem__(self, key): """ Retrieve the attribute of the object with the given key (e.g., ``ps[key]``). Parameters: key (str): The name of the attribute to retrieve. Returns: Any: The value of the attribute with the specified key. Raises: AttributeError: If the attribute with the specified key does not exist. """ return getattr(self, key)
[docs] def __setitem__(self, key, value): """ Set the value of an attribute. This method allows setting an attribute of the instance using the dictionary-like syntax (e.g., ``ps[key] = value``). Parameters: key (str): The name of the attribute to set. value (any): The value to set for the attribute. Returns: None """ setattr(self, key, value)
[docs] def __add__(self, other): """ Add another PropertySet to this PropertySet. This method allows the use of the ``+`` operator to combine two PropertySet instances. Parameters: other (PropertySet): The other PropertySet instance to add. Returns: PropertySet: A new PropertySet instance that combines the properties of both instances. """ return PropertySet(self, other)
[docs] def __iadd__(self, other): """ Implements the in-place addition (``+=``) operator for the class. This method allows the instance to be updated with attributes from another instance of the same class or from a dictionary. If `other` is an instance of the same class, its attributes are copied to the current instance. If `other` is a dictionary, its key-value pairs are added as attributes to the current instance. Parameters: other (Union[type(self), dict]): The object or dictionary to add to the current instance. Returns: self: The updated instance with the new attributes. Raises: AssertionError: If `other` is neither an instance of the same class nor a dictionary. ValueError: If `other` contains keys already present in the PropertySet. """ assert isinstance(other, (type(self), dict)) for key, value in (other.__dict__ if isinstance(other, type(self)) else other).items(): if hasattr(self, key): raise ValueError(f"Cannot override existing value for '{key}'.") setattr(self, key, value) return self
[docs] def __lshift__(self, other): """ Implements the ``<<`` operator on PropertySet to override existing values with new values. Parameters: other (Union[type(self), dict]): The object or dictionary with overriding values. Returns: A new PropertySet with all the values of the first PropertySet with overrides from the second PropertySet. Raises: AssertionError: If `other` is neither an instance of the same class nor a dictionary. ValueError: If `other` contains keys not present in the PropertySet. """ result = PropertySet(self) result <<= other return result
[docs] def __ilshift__(self, other): """ Implements the ``<<=`` operator on PropertySet to override existing values with new values. Parameters: other (Union[type(self), dict]): The object or dictionary with overriding values. Returns: self: The updated instance with the overrides from other. Raises: AssertionError: If `other` is neither an instance of the same class nor a dictionary. ValueError: If `other` contains keys not present in the PropertySet. """ assert isinstance(other, (type(self), dict)) for key, value in (other.__dict__ if isinstance(other, type(self)) else other).items(): if not hasattr(self, key): raise ValueError(f"Cannot override missing key '{key}'.") setattr(self, key, value) return self
[docs] def __or__(self, other): """ Implements the ``|`` operator on PropertySet to add new or override existing values with new values. Parameters: other (Union[type(self), dict]): The object or dictionary with overriding values. Returns: A new PropertySet with all the values of the first PropertySet with new or overriding values from the second PropertySet. Raises: AssertionError: If `other` is neither an instance of the same class nor a dictionary. """ result = PropertySet(self) result |= other return result
[docs] def __ior__(self, other): """ Implements the ``|=`` operator on PropertySet to override existing values with new values. Parameters: other (Union[type(self), dict]): The object or dictionary with overriding values. Returns: self: The updated instance with all the values of self with new or overriding values from other. Raises: AssertionError: If `other` is neither an instance of the same class nor a dictionary. """ assert isinstance(other, (type(self), dict)) for key, value in (other.__dict__ if isinstance(other, type(self)) else other).items(): # no check on existence in self, all keys added or updated setattr(self, key, value) return self
[docs] def __len__(self): """ Return the number of attributes in the instance. This method returns the number of attributes stored in the instance's __dict__ attribute, which represents the instance's namespace. Returns: int: The number of attributes in the instance. """ return len(self.__dict__)
[docs] def __str__(self) -> str: """ Returns a string representation of the object's dictionary. This method is used to provide a human-readable string representation of the object, which includes all the attributes stored in the object's `__dict__`. Returns: str: A string representation of the object's dictionary. """ return json.dumps(self.to_dict(), indent=4)
[docs] def __repr__(self) -> str: """ Return a string representation of the PropertySet instance. The string representation includes the class name and the dictionary of the instance's attributes. Returns: str: A string representation of the PropertySet instance. """ return f"PropertySet({self.to_dict()!s})"
[docs] def __contains__(self, key): """ Check if a key is in the property set. Parameters: key (str): The key to check for existence in the property set. Returns: bool: True if the key exists in the property set, False otherwise. """ return key in self.__dict__
[docs] def __eq__(self, other): """ Check if two PropertySet instances are equal. Parameters: other (PropertySet): The other PropertySet instance to compare. Returns: bool: True if the two instances are equal, False otherwise. """ return self.to_dict() == other.to_dict()
[docs] @staticmethod def load(filename): """ Load a PropertySet from a specified file. Parameters: filename (str): The path to the file where the PropertySet is saved. Returns: PropertySet: The PropertySet instance loaded from the file. """ with Path(filename).open("r") as file: data = json.load(file) return PropertySet(data)