Core Module Documentation¶
Description¶
Base classes for MasterPiece - a light-weight and general purpose object-oriented toolkit for implementing modular plugin-aware applications.
- class masterpiece.core.Application(name, payload=None)[source]¶
Masterpiece application base class. Implements startup argument parsing, plugin management and initialization of class attributes through class specific JSON configuration files.
Todo
Abstract the serialization API to support multiple formats (e.g., JSON, XML, etc.).
- deserialize()[source]¶
Deserialize instances from the startup file specified by ‘serialization_file’ class attribute, or ‘–file’ startup argument.
TODO: or by optional method parameter
- Return type:
None
- install_plugins()[source]¶
Installs plugins into the application by invoking the install() method of each loaded plugin module. Note: This method is intended for testing and debugging purposes only. In a typical use case, the application should handle the instantiation of classes and manage their attributes as needed.
- Return type:
None
- instantiate_plugin_by_name(name)[source]¶
Installs the plugin by name, that is, instantiates the plugin class and inserts the instance as child to the application. :type name:
str:param name: name of the plugin class :type name: str- Return type:
Optional[MasterPiece]
- classmethod load_plugins()[source]¶
Loads and initializes all plugins for instantiation. This method corresponds to importing Python modules with import clauses.
- Return type:
None
- classmethod register_args(parser)[source]¶
Register startup arguments to be parsed.
- Parameters:
parser (argparse.ArgumentParser) – parser to add the startup arguments.
- classmethod register_plugin_group(name)[source]¶
Registers a new plugin group within the application. Only plugins that match the registered groups will be loaded. By default, all ‘masterpiece’ plugins are included. Frameworks built on the MasterPiece framework can define framework-specific group names, enabling plugins to be developed for any application built on those frameworks. Additionally, individual applications can introduce application-specific groups (typically named after the application), allowing plugins to be developed exclusively for that specific application.
- Parameters:
name (str) – The name of the plugin group to be registered
- Return type:
None
- class masterpiece.core.Args[source]¶
For typing ArgumentParser args. Sub-classes of MasterPiece can sub-class their startup arguments from this base class to define any additional args.
TODO: change the implementation due to Liskov’s warning
- class masterpiece.core.Composite(name='group', payload=None)[source]¶
Class implementing hierarchy. Objects of this class can consist of children.
This class can be used for grouping masterpieces into larger entities.
Example:
motion_sensors = Composite("motionsensors") motion_sensors.add(ShellyMotionSensor("downstairs")) motion_sensors.add(ShellyMotionSensor("upstairs"))
- add(h)[source]¶
Add new automation object as children. The object to be inserted must be derived from MasterPiece base class.
- Parameters:
h (T) – object to be inserted.
- Return type:
None
- from_dict(data)[source]¶
Recursively deserialize the group from a dictionary, including its children.
- Parameters:
data (dict) – data to deserialize from.
- print(prefix='', is_last=True)[source]¶
Print the hierarchy of the node using ├─, └─, and │.
Example:
parent = Composite("parent") child = MasterPiece("child") parent.add(child) parent.print() # expected output: # parent # └─ child
- Parameters:
prefix (str, optional) – Defaults to “”.
is_last (bool, optional) – Defaults to True.
- class masterpiece.core.Log(name, level=10)[source]¶
Default logger for logging events to application log and/or to console.
- class masterpiece.core.MasterPiece(name='noname', payload=None)[source]¶
An object with a name. Base class of everything. Serves as the foundational class offering key features needed by any robust object-oriented software.
Logging¶
All objects have logging methods e.g. info() and error() at their fingertips, for centralized logging.
if (err := self.do_good()) < 0: self.error(f"Damn, did bad {err}")
Factory Method Pattern¶
Instantiation via class identifiers, adhering to the factory method pattern. This allows for the dynamic creation of instances based on class identifiers, promoting decoupled and extensible design required by plugin architecture.
# instead of fixed implementation car = Ferrari() car = Object.instantiate(car_class_id)
Serialization¶
Serialization of both class and instance attributes serves as a means of configuration.
Class attributes should follow a consistent naming convention where an underscore prefix (‘_’ or ‘__’) implies the attribute is private and transient, meaning it is not serialized. Class attributes without an underscore prefix are initialized from configuration files named ‘~/.masterpiece/[appname]/[classname].json’, if present. If the class-specific configuration files do not already exist, they are automatically created upon the first run.
Instance attributes can be serialized and deserialized using the serialize() and deserialize() methods:
# serialize to json file with open("foo.json", "w") as f: foo.serialize(f) # deserialize foo = F() with open("foo.json", "r") as f: foo.deserialize(f)
Deserialization must restore the object’s state to what it was when it was serialized. As Python does not have ‘transient’ keyword to tag attributes that should be serialized, all classes must explicitely describe information for the serialization. This is done with to_dict() and from_dict() methods:
def to_dict(self): data = super().to_dict() data["_foo"] = { "topic": self.topic, "temperature": self.temperature, } return data def from_dict(self, data): super().from_dict(data) for key, value in data["_foo"].items(): setattr(self, key, value)
Copying Objects¶
Any object can be copied using the copy() method. This feature is based on serialization, so typically, subclasses don’t need to implement the copy() method; everything is taken care of by the base class.
foo2 = foo.copy()
- classmethod configure_args(args)[source]¶
Register startup arguments to be parsed.
- Parameters:
parser (argparse.ArgumentParser) – parser to add the startup arguments.
- Return type:
None
- copy()[source]¶
Create and return a copy of the current object.
This method serializes the current object to a dictionary using the to_dict method, creates a new instance of the object’s class, and populates it with the serialized data using the from_dict method.
This method uses class identifier based instantiation (see factory method pattern) to create a new instance of the object, and ‘to_dict’ and ‘from_dict’ methods to initialize object’s state.
- Return type:
- Returns:
A new instance of the object’s class with the same state as the original object.
Example:
clone_of_john = john.copy()
- debug(msg, details='')[source]¶
Logs the given debug message to the application log.
- Parameters:
msg (str) – The information message to be logged.
details (str) – Additional detailed information for the message to be logged
- Return type:
None
- elapsed_seconds_in_day(ts_utc)[source]¶
Fetch the elapsed seconds since the be given time stamp ‘ts_utc’.
- Returns:
elapsed second today
- Return type:
float
- elapsed_seconds_in_hour(ts_utc)[source]¶
Given timestamp in UTC, Compute elapsed seconds within an hour
- Parameters:
ts (float) – seconds since UTC epoch
- Returns:
_description_
- Return type:
float
- epoc2utc(epoch)[source]¶
Converts the given epoch time to UTC time string. All time coordinates are represented in UTC time. This allows the time coordinate to be mapped to any local time representation without ambiguity.
- Parameters:
epoch (float) – timestamp in UTC time
rc (str) – time string describing date, time and time zone e.g 2024-07-08T12:10:22Z
- Returns:
UTC time
- error(msg, details='')[source]¶
Logs the given error message to the application log.
- Parameters:
msg (str) – The message to be logged.
details (str) – Additional detailed information for the message to be logged
- Return type:
None
- classmethod find_class(class_id)[source]¶
Given class identifier find the registered class. If no class with the give identifier exists return None.
- Parameters:
class_id (int) – class identifier
- Returns:
class or null if not registered
- Return type:
obj (obj)
- classmethod get_app_name()[source]¶
Get application name
- Returns:
application name
- Return type:
name (str)
- classmethod get_class_id()[source]¶
Return the class id of the class. Each class has an unique identifier that can be used for instantiating the class via
Object.instantiate()method.- Parameters:
cls (class) – class
- Return type:
str- Returns:
id (int) unique class identifier through which the class can be instantiated by factory method pattern.
- classmethod get_json_file()[source]¶
Generate the JSON file name based on the class name.
The file is created into users home folder.
- classmethod get_registered_classes()[source]¶
Get the dictionary holding the registered class identifiers and the corresponding classes.
- Returns:
dictionary of class identifier - class pairs
- Return type:
dict
- classmethod has_class_method_directly(method_name)[source]¶
Check if the method is in the class’s own dictionary
- Return type:
bool
- info(msg, details='')[source]¶
Logs the given information message to the application log.
- Parameters:
msg (str) – The information message to be logged.
details (str) – Additional detailed information for the message to be logged
- Return type:
None
- classmethod initialize_class(load_class_attrs=True)[source]¶
Initialize the class for instantiation. Initializes the class identifier. Initialize public class attributes from the class-specific configuration files, unless disabled with the load_class_attrs parameter. Call the register() method of the class to let each class do custom initializations. Set up atexit callback to generate class-specific initialization files if they don’t exist already.
- Parameters:
load_class_attrs (bool, optional) – If true, then attempts to initialize class attributes from the class-specific configuration files. Defaults to True.
- Returns:
- Returns true if the class was initialized. False implies the class is already initialized,
in which case the method call has no effect.
- Return type:
bool
- classmethod instantiate(class_id)[source]¶
Create an instance of the class corresponding to the given class identifier. This method implements the factory method pattern, which is essential for a plugin architecture.
- Parameters:
class_id (int) – Identifier of the class to instantiate.
- Returns:
An instance of the class corresponding to the given class identifier.
- Return type:
obj
- classmethod instantiate_with_param(class_id, param)[source]¶
Given class identifier and one constructor argument create the corresponding object.
- Parameters:
class_id (
str) – class identifierparam (
Any) – class specific constructor parameter
- Returns:
instance of the given class.
- Return type:
obj
- classmethod is_abstract()[source]¶
Check whether the class is abstract or real. Override in the derived sub-classes. The default is False.
- Return type:
bool- Returns:
True (bool) if abstract
- is_time_between(begin_time, end_time, check_time=None)[source]¶
Check if the given time is within the given time line. All timestamps must be in UTC time.
- Parameters:
begin_time (timestamp) – beginning of the timeline
end_time (timestamp) – end of the timeline
check_time (timestamp) – time to be checked
- Returns:
True if within the timeline
- Return type:
rc (bool)
- classmethod parse_args()[source]¶
Parse registered startup arguments.
- Parameters:
parser (argparse.ArgumentParser) – parser to add the startup arguments.
- Return type:
None
- classmethod parse_initial_args()[source]¶
Parse application name and configuration name arguments. Note that argument parsing is two stage process. This method must be called at very early on to know where to load class initialization files. See register_args() method.
- print(prefix='', is_last=True)[source]¶
Print the name of the object, decorated with prefix consisting of ├─, └─, and │.
- quantize(quanta, value)[source]¶
Quantize the given value.
- Parameters:
quanta (float) – resolution for quantization
value (float) – value to be quantized
- Returns:
quantized value
- Return type:
(float)
Example:
hour_of_a_day = self.quantize(3600, epoch_seconds)
- classmethod register()[source]¶
General purpose class method for initializing classes for instantiation. Up to the sub clases to implement. This method is called right before the class attributes are loaded from the class specific configuration files.
- Return type:
None
- classmethod register_args(parser)[source]¶
Register startup arguments to be parsed.
- Parameters:
parser (argparse.ArgumentParser) – parser to add the startup arguments.
- Return type:
None
- classmethod register_class(class_id, ctor)[source]¶
Register the given class identifier for identifier based instantiation . This, factory method pattern, as it is called, decouples the actual implementation from the interface. For more information see
instantiate()method.- Parameters:
class_id (str) – class identifier
ctor (function) – constructor
- run()[source]¶
Run the masterpiece. Dispatches the call to payload object and returns the control to the caller.
- Return type:
None
- run_forever()[source]¶
Run the masterpiece forever. This method will return only when violently terminated.
- Return type:
None
- classmethod save_to_json()[source]¶
Create class configuration file, if the file does not exist yet.
- classmethod set_log(l)[source]¶
Set logger.
- Parameters:
l (logger) – logger object
- Return type:
None
- shutdown()[source]¶
Shutdown the masterpiece. It is up to the sub classes to implement this method. Dispatches the call to payload object.
- Return type:
None
- timestamp()[source]¶
Returns the current date-time in UTC.
- Returns:
datetime in UTC.
- Return type:
rc (datetime)
- timestamp_hour(ts)[source]¶
Returns the hour in 24h format in UTC.
- Parameters:
ts (float) – timestamp
- Returns:
current hour in UTC 0 …23
- Return type:
rc (int)
- class masterpiece.core.MetaMasterPiece(name, bases, dct)[source]¶
Meta class that implements per-class initialization procedure for the MasterPiece classes. Typically the application code should not need to to be aware of this class.
- class masterpiece.core.PlugMaster(name)[source]¶
The Plugmaster class is responsible for managing and loading plugins into an application.
The Plugmaster is designed to work with plugins that are Masterpiece objects or subclasses thereof. Plugins can optionally be derived from the Plugin class.
If a plugin implements the Plugin interface, it is responsible for determining what objects should be added to the application.
If a plugin is not a Plugin class, it is simply loaded, and it is the responsibility of the application configuration file or the application code to determine how to utilize the plugin.
- find_class_by_name(name)[source]¶
Find and return a plugin class by its name.
- Return type:
Optional[Type[MasterPiece]]- Returns:
plugin class (MasterPiece) or none if not found
- get()[source]¶
Fetch the directionary of plugins classes.
- Returns:
List of plugins
- Return type:
List[Type[MasterPiece]]
- install(app)[source]¶
Instantiate and add all the registered plugins to the application.
Typically is up to the application or the configuration to define the instances to be added. This method is provided for testing purposes only.
- Parameters:
app (Composite) – parent object (application), for hosting the instances
plugin. (created by the)
- Return type:
None
- instantiate_class_by_name(app, name)[source]¶
Instantiate and add the plugin into the application.
- Parameters:
app (Composite) – parent object, for hosting the instances createdby the plugin.
name (str) – name of the plugin to be instantiated
- Return type:
Optional[MasterPiece]
- load(name)[source]¶
Fetch the entry points associated with the ‘name’, call their ‘load()’ methods and insert to the list of plugins. Note: Python’s ‘importlib.metadata’ API has been redesigned a couple of times in the past. The current implementation has been tested with Python 3.8, 3.9 and 3.12.
- Parameters:
name (str) – Name determining the plugins to be loaded.
- Return type:
None
- class masterpiece.core.Plugin(name='noname', payload=None)[source]¶
Base class for plugins. TODO: make formally abstract
- install(app)[source]¶
Instantiates and installs the classes in the plugin module into the application. This is an abstract method that the plugin classes must implement. Plugins may choose not to do anything here and instead leave it up to the higher level software layers.
- Parameters:
app (Composite) – application to plug into