Mastering Contracts in Plugin Oriented Programming
Plugin Oriented Programming (POP) is a forward-thinking development paradigm designed to foster enhanced modularity, code reuse, and scalability in software creation. An integral element of POP is its unique contract system which permits subsystems to establish and enforce their interfaces. This post will delve into the advanced aspects of contracts in POP, with focus on contract signatures, pre/post/call wrappers, contracted context, use of the __contracts__ list, the potentials of future improvements, and more.
The Power of Contract Signatures
Contracts play an instrumental role in guaranteeing type safety and predictability within plugin interfaces in POP. By enforcing function signatures and Python's typing annotations, contracts help to uphold an order within the system. Each contract signature or 'sig' ensures that a plugin maintains the integrity of the defined function with its parameter names, types, and return types. They also enforce whether a function should be "async" or not.
In cases where a plugin fails to uphold a contract's signature, the system immediately identifies this mismatch. The POP application, during startup, throws a ContractSigException, thereby averting potential runtime errors that might have slipped unnoticed.
Harnessing Wrapper Functions: Pre, Post, and Call
One of the many capabilities that contracts afford in POP is the use of wrapper functions, allowing developers to manipulate data before, during, and after the execution of a function. These wrappers are named 'pre', 'call', and 'post'. A function can have multiple "pre" and "post" contracts, but only one "call" contract.
'Pre' wrappers are executed prior to the target function. This provides a fitting opportunity for tasks such as input parameter sanitization or pre-flight checks. The return of a pre contract currently doesn't affect the execution. However, there's potential for future iterations to utilize the return of pre contracts to determine whether to proceed with the function call, potentially skipping the function call and raising an error if the pre contract returns false.
'Call' wrappers actually replace the execution of the function. Here, developers can wrap the entire function execution and decide if the function should execute based on certain conditions. The function call within the 'call' wrapper must be invoked manually if needed.
'Post' wrappers, as the name suggests, are executed post the function. Developers can leverage this to manipulate or validate the return data from the function execution.
Leveraging the __contracts__ List
A plugin in POP can voluntarily decide the contracts it intends to adhere to. It does this by defining those contracts in the __contracts__ list. This effectively allows a plugin to subscribe to the rules defined in those contracts. The order of the contracts defined in __contracts__ determines the order in which their wrappers are enforced, thus providing predictable control over the flow of execution.
Subsystem-Wide Contracts for Uniformity
You can implement subsystem-wide contracts using an init.py contract file. This contract file acts as a general contract for the entire subsystem. When you place the init.py contract in the contracts directory, POP automatically applies it to all plugins within that subsystem.
This broad-scope contract is crucial in maintaining consistency and uniformity across a subsystem, particularly when it houses numerous plugins with varying functionalities. It's like a universal rule book that every plugin in that subsystem follows, ensuring each one complies with the specified guidelines and standards.
The enforcement of the init.py contract is implicit, and does not need to be individually specified it in a plugin's __contracts__ list, leading to cleaner code and better maintenance.
Recursive Contracts: Scope and Granularity
Unlike typical contracts, recursive contracts extend their applicability to all nested directories beneath a subsystem. This powerful feature allows for contract rules to be enforced across multiple levels of a project, guaranteeing uniformity and consistency across a broader scope.
Contracted Context - The Backbone of Contract Functions
Every contract function is bolstered by a ContractedContext object, referred to as 'ctx'. This object holds essential details of the call and allows manipulation of function parameters, fetching of the return value of a function, and more. The versatility of 'ctx' facilitates its adaptation based on the contract type and the specific needs of the contract.
Function-Specific and Global Contracts
POP allows the definition of both function-specific and global contracts. This flexibility provides granular control over which contracts apply to which functions within a plugin.
Function-specific contracts are contracts with names following the convention "<wrapper_type>_<function_name>". For example, a "pre" contract for a function named update would be named "pre_update". These contracts apply only to the function specified in their names. If you want to sanitize input parameters before the update function is called, you can define a "pre_update" contract for that. This granularity provides fine-tuned control over the order and conditions of function execution within a plugin.
On the other hand, global contracts are simply named "pre", "post", or "call". These contracts are not tied to a specific function and apply to every function within the plugin they govern. For example, a "post" contract would perform its duties after every function call within the plugin. Global contracts are particularly useful when you want to apply a uniform condition or operation to all functions within a plugin.
This system allows for an efficient and systematic way to enforce rules and manipulate data in a predictable manner, enhancing the reliability and maintainability of your code. The possibilities are endless when you combine these features with the inherent modularity and scalability of POP, creating a powerful and flexible framework for developing complex applications.
Contracts within Contracts: The Order of Execution
Contracts are also technically plugins themselves and we have the ability to make contracts for contracts -- implying a contract can have another contract ensuring its execution order. For instance, a contract such as 'pre_post' would be responsible for making sure that the pre contract is executed before the post contract, ensuring a specific order of execution.
Contracts in Plugin Oriented Programming are designed to foster a robust development framework, emphasizing consistency, predictability, and type safety in your plugins. They offer the flexibility to define function interfaces, manipulate function parameters and return values, maintain a consistent interface across an entire subsystem, and even extend their influence recursively across nested directories. Understanding and mastering these aspects of contracts can lead to a marked improvement in the reliability and maintainability of your code, paving the way for a higher level of software development excellence.