============================================= Enforcing Patterns and Interfaces - Contracts ============================================= Contracts exist to enforce how an interface is being used or extended. Having contracts allows for the integrity of the interface to be maintained despite being extended by third parties. Having contracts allows for the explicit exposure of specific functions that can then be enforced. Contracts also allows for pre and post processing of function calls. This makes it easy to add extensive input and return validation for extensions of any given `sub` or `plugin`. Contract Structure ================== When a sub is created, an additional directory called `contracts` can also be created inside of the same directory as the `sub`. The `contracts` directory loads plugins just like a regular `sub`, but they do not appear on the `hub`. When functions are loaded from plugins onto the `hub` they are wrapped in an object called `Contracted`. This `Contracted` object, when called, executes the needed contracts, if any are present. The `Contracted` object also calls the wrapped function when called. Finally, the `Contracted` object presents a pass-through interface to the variables or properties that are present on the underlying function. This means that you can use the `Contracted` function entirely as if it were the real function. This also means that decorators will work for the underlying function as well. The contract files are laid out in a simple way. If the contracts directory has an `init.py` file, then those contract functions will apply to all of the plugins in the contracted `sub`. Otherwise a file with the same virtual name as a `plugin` likely to be found in the `sub` will present contracts that will apply to just that `plugin`. Contract Signature Enforcement ============================== Contract signatures allow for enforcement of a function's arguments. To create a contract signature, just make the function in the contract `plugin`. To contract a function called `foo`, name the signature function `sig_foo`, preceding the name of the contracted function, with `sig_`. Then give the function `sig_foo` a set of arguments that would be compatible with the intended function. This means that if the function needs a very specific set of options, then they can be defined, with type annotations. If the function can accept arbitrary options, then `*args` and `**kwargs` can also be used. Therefore a contract found in contracts/test.py: .. code-block:: python def sig_foo(hub, a, b:str, **kwargs): pass Means that a plugin found in test.py can have this function: .. code-block:: python def foo(hub, a, b:str, bar="77", quo=55): return a Notice how the signature does nothing. It just has a pass statement. The signature function is never called and needs no code. The `foo` function follows the `sig_foo` function but is able to use the leeway presented by the `**kwargs` option, allowing multiple keyword arguments to be used. Contract Function Wrappers ========================== Contract function wrappers allow for execution before, instead of, and after functions are called. This can be useful when performing input validation or modifying output of functions, or dynamically adding decorator-like functionality without making developers that extend your framework apply decorators themselves. These functions follow the same rules as the signature functions, except they use the keywords, `pre`, `call`, and `post`. These functions also receive an added argument called `ctx` which contains the call context. This call context has different components based on whether the contract wrapper is `pre`, `post`, or `call`. The `pre` call has `*args` and `**kwargs` as passed to the function. The `call` function receives `func` which is the underlying function. The `post` function receives `ret` which is the return from calling the function and the return from `post` becomes the overall function call return.