This is a draft of a future project, mostly for discussion. Do not use this in production. File an issue if you have thoughts!
specable is a very small python package that provides a simple interface to initialise objects from dicts, and in turn describe objects as dicts. While not particularly exciting in isolation, it's surprisingly useful when building research software, for example enabling simple plugin systems or non-code specification of objects with many parameters.
specable is intended to be used for building other packages and is not usually "user-facing". It's also geared towards research, and probably not suitable for large-scale production use. (Though I'd be happy to be proven wrong!)
In YAML, a specable-style dictionary looks like this:
(We'll use a fictional machine learning library called hyperdeep with a NN class called Minion as example.)
# example.yaml
hyperdeep/minion:
layers: 250
width: 1024
activation: "banana"The python equivalent is:
example = {
"hyperdeep/minion": {
"layers": 250,
"width": 1024,
"activation": "banana",
}
}If hyperdeep uses specable, we'll then be able to call hyperdeep.from_yaml("example.yaml") (or hyperdeep.from_dict(example)) and receive an instance of Minion, initialised with our parameters.
On the backend, specable simply looks up the module hyperdeep, finds the class corresponding to minion, and initialises it with the "inner" dictionary.
This means that the model can be specified separately from any executable code: it is cleanly encapsulated in a human-readable file and can be instantiated as required without boilerplate or imports. Since the handle (the first part of the dictionary) contains a module name, the class lookup can accomodate custom, one-off "plugins" for hyperdeep without any problems.
A spec is a combination of handle and payload. handle is a str of the form namespace/kind, where namespace corresponds to a module name and kind is a string (typically the lowercase version of a class name). The payload is a dict (or something similar) that can be used to initialise an object, most simply by calling Class(**payload). A spec dict is a dict of the form {handle: payload}.
- statelessness, i.e. parameters at initialisation fully determine the object
- small and sane payloads, i.e. objects aren't initialised with huge
numpyarrays or similar (this is particularly important ifyamlis used)
Basically, to use specable, you have to do the following:
- Decide on a reasonable name for the type of objects that you want to use with
specable(for example,modelsif you're building a ML library, orcomponentsfor a web library, ordrinksfor a bar); we call this acollection, - Make sure that these classes inherit from
specable.Specableand implement aget_dict()method, - In the
__init__.pyof your package, have a list with that name, containing the classes.
Then, specable.Interface("your_package_name", "your_collective_noun) will give you an interface with suitable from_dict, to_dict and related methods. It's convenient to "alias" these methods to module-level methods.
To instantiate an object from a spec dict, specable will then do the following:
- Parse the
dictintonamespace,kindandpayload(defaulting to your package name fornamespace) - Attempt to import
collectionfromnamespace - In that list, find a class where
class.get_kind()matcheskind - Return
class(**payload)(or something custom)
specable provides two points of contact:
Specableis a mixin class that provides the facilities forspecableto work. Your class needs to inherit from it, and implement theget_dict()method that returns thepayloaddict. By default, initialisation is simplyClass(**payload), if you need something custom, you need to also implement the_from_dictclass method. You can also specifykindif the lower-case classname is too long or unappealing, andnamespaceif the class is not located indefault_namespace.Interfaceprovides the facilities for (de)-serialisation. You need to import it, and instantiate it with adefault_namespace(corresponding to the name of your package) and acollection.
The tests/integration folder shows a toy example using specable, it probably explains how it works much better than I can do here!
In my work, I'm often building custom infrastructure for various experiments where I have to a) work in a "HPC" context (i.e. computations have to be run by a batch script), b) try many variations of parameters for a few classes (settings for machine learning models or computational methods). I've found it useful to separate "input files", i.e. the particular parameters to try, from implementation details, and it seems more reasonable to store such files as a non-executable format. With specable, it's easy to build simple command line interfaces of the form hyperdeep run example.yml without having to write lots of boilerplate experiment.py-type scripts.
specable is part of a broader effort to isolate the building blocks of my "one-off" tools, hoping that someone else will get some use out of them! It's essentially a part of cmlkit extracted into its own package for easier use and maintenance.