Mastering the Piece¶
This tutorial covers the examples/myapp.py
application to help you get
started with writing world-class masterpieces.
Prerequisites¶
Fetch the Masterpiece core framework and the plugin:
git clone https://gitlab.com/juham/masterpiece.git
git clone https://gitlab.com/juham/masterpiece_plugin.git
Navigate to the masterpiece/example folder and open the example application in your favorite editor (Emacs, anyone?):
emacs examples/myhome.py
MyHome Application¶
The main() function of masterpiece Application is as follows:
from myhome import MyHome
def main() -> None:
# configure classes
MyHome.init_app_id("myhome")
MyHome.load_plugins()
Application.load_configuration()
# instantiate
home = MyHome("home")
# run
home.run()
Importing Masterpiece Classes¶
The first lines of code import the classes used as building blocks in myapp.py.
from masterpiece import Application, MasterPiece, Composite, TreeVisualizer
Application Identifier¶
The main function initializes the application with an appropriate “application identifier,” a string that describes the software’s purpose.
def main() -> None:
MyHome.init_app_id("myhome")
This identifier determines where the application reads its configuration files and enables plugins to be written for applications.
Note: The application identifier is not the application name! It’s something shared by all related applications, for example, representing a software “family” or company if multiple applications share the same architecture.
Loading Plugins¶
If desired, load plugins with:
MyHome.load_plugins()
The plugin discovery uses Python’s importlib.metadata API. Every Masterpiece project can define project entry points in its pyproject.toml file:
[project.entry-points."masterpiece.plugins"]
Then, a plugin can specify the entry-points it can expand - in its entry points in pyproject.toml:
helloworld_plugin = "masterpiece_plugin:HelloWorld"
This example shows that the masterpiece_plugin was written for any Masterpiece application, relying only on core Masterpiece framework features.
Applications should (in fact, must) introduce application-specific entry points to allow plugins tailored to them.
Configuring Application¶
Application configuration involves setting class attributes, done either through class-specific configuration files or startup arguments, and loaded with:
Application.load_configuration()
Configuration files are found in:
~/.[app_id]/[configuration]/[classname].[ext]
where [app_id] is the application identifier. [configuration] is config by default but can be changed with the –config startup switch, allowing different configurations (e.g., production vs. test).
Each class has a configuration file ([classname]) with format-specific extension ([ext]), usually JSON. YAML is also supported, and plugins can introduce more formats. Select the desired one with:
python myapp.py --application_serialization_format 'YamlFormat'
This demonstrates the factory method pattern, where implementations are chosen through configuration.
If there are no configuration files, the application can generate default ones with:
python myapp.py --init
This creates a new set of configuration files at ~/.myapp/config/, using default values.
Creating the Application¶
Once classes have the desired properties, the main function can instantiate them:
home = MyHome("myhome")
This creates a MyHome application instance named “myhome”.
Running the Application¶
Applications perform operations in the run() method.
home.run()
For example, myapp.py prints out the instances in the application:
home
├─ grid
├─ downstairs
│ └─ kitchen
│ ├─ oven
│ └─ fridge
├─ garage
│ └─ EV charger
└─ Hello World - A Plugin
The Application Class¶
Masterpiece is object-oriented, meaning that components should be proper classes. For instance, MyApp:
class MyHome(Application):
# class attributes
solar: float = 0.0
color: str = "yellow"
def __init__(self, name: str = "myhome") -> None:
super().__init__(name)
# instance attributes
self.create_home()
self.install_plugins()
[snip]
MyHome inherits from Application, gaining features like plugin support, class attribute configuration, serialization, and startup argument handling, just to name a few.
Configure public class attributes (solar and color) via the ~/.myhome/config/MyHome.json file:
{
"solar": 10.0,
"color": "yellow"
}
or via startup arguments:
myhome --myapp_solar 20 --myapp_color "red"
Configuration priority:
Startup arguments, if defined
Configuration files, if present
Hard-coded values
Modeling Reality¶
Real-world objects are hierarchical. The Masterpiece framework models this with the Composite class, allowing MasterPiece or Composite objects to be added as children. Application classes can also be a Composite.
Methods like create_power_grid() demonstrate this:
def create_power_grid(self):
grid = MasterPiece("grid")
self.add(grid)
The method inserts ‘grid’ object into the application as a children.
This creates an “ownership tree,” where the application can robustly manage resources and serialize the hierarchy.
Visualizing the Hierarchy¶
The run() method has two parts:
def run(self) -> None:
super().run()
self.print()
def print(self):
visualizer = TreeVisualizer(self.color)
visualizer.print_tree(self)
Note that we could put the code in print() method into the run() method, after all, it is just two lines of code needed to print the hierarhcy. However, this would be bad programming practice! By putting printing specific code into a separate print() other applications sub-classed from ours, can easily override the print() method, if they choose to do so.
Note also that Masterpiece objects, including applications, can host “payload” objects. Therefore, always pass run() to the superclass.
Implementing Plugins¶
The framework encourages focusing on plugins rather than traditional applications. Applications should implement only the minimal infrastructure required, leaving features to be handled as plugins.
The next tutorial covers this topic in depth: Implementing Plugins