Writing Extensions

Writing Sending Methods

A sending method is implemented as a callable (usually a class) that accepts the fields of a configuration structure as keyword arguments and returns a sender object. The keyword arguments include the method field and also include a configpath key specifying a pathlib.Path pointing to the configuration file (or None if from_dict() was called without setting a configpath). Callables should accept any keyword argument and ignore any that they do not recognize.

For example, given the following configuration:

[outgoing]
method = "foobar"
server = "www.example.nil"
password = { env = "SECRET_TOKEN" }
comment = "I like e-mail!"

the callable registered for the “foobar” method will be called with the following keyword arguments:

**{
    "method": "foobar",
    "server": "www.example.nil",
    "password": {"env": "SECRET_TOKEN"},
    "comment": "I like e-mail!",
    "configpath": Path("path/to/configfile"),
}

If the configuration passed to a callable is invalid, the callable should raise an InvalidConfigError.

Callables can resolve password fields by passing them to resolve_password() or by using pydantic and the Password type. Callables should resolve paths relative to the directory containing configpath by using resolve_path() or by using pydantic and the Path, FilePath, and/or DirectoryPath types.

The last step of writing a sending method is to package it in a Python project and declare the callable as an entry point in the outgoing.senders entry point group so that users can install & access it. For example, if your project is built using setuptools, and the callable is a FooSender class in the foobar.senders module, and you want it to be usable by setting method = "foo", add the following to your setup.py:

setup(
    ...
    entry_points={
        "outgoing.senders": [
            "foo = foobar.senders:FooSender",
        ],
    },
    ...
)

Writing Password Schemes

A password scheme is implemented as a function that takes the value part of a password = { scheme = value } entry as an argument and returns the corresponding password as a str. If the function additionally accepts arguments named host, username, and/or configpath (either explicitly or via **kwargs), the corresponding values passed to resolve_password() will be forwarded to the scheme function.

If the value structure is invalid, or if no password can be found, the function should raise an InvalidPasswordError.

The last step of writing a password scheme is to package it in a Python project and declare the function as an entry point in the outgoing.password_schemes entry point group so that users can install & access it. For example, if your project is built using setuptools, and the function is foo_scheme() in the foobar.passwords module, and you want it to be usable by writing password = { foo = some-value }, add the following to your setup.py:

setup(
    ...
    entry_points={
        "outgoing.password_schemes": [
            "foo = foobar.passwords:foo_scheme",
        ],
    },
    ...
)