Plugin API Reference
This is the plugin API of spin. It contains functions and classes
that are necessary for plugins to register themselves with spin,
e.g. task(), and convenience APIs that aim to simplify plugin
implementation.
spin’s task management (aka subcommands) is just a thin wrapper on top of the venerable package click, so to create any slightly advanced command line interfaces for plugins you want to make yourself comfortable with click’s documentation.
Defining tasks
- csspin.task(*args: Any, **kwargs: Any) Callable[source]
Decorator that creates a task. This is a wrapper around Click’s
click.command()decorator, with some extras:a string keyword argument
whenadds the task to the list of commands to run usinginvoke()aliases is a list of aliases for the command (e.g. “tests” is an alias for “test”)
noenv=Trueregisters the command as a global command, that can run without a provisioned environment
task introspects the signature of the decorated function and handles certain argument names automatically:
ctxwill pass theClick context objectinto the task; this is rarely useful for spin taskscfgwill automatically pass the configuration tree; this is very useful most of the time, except for the simplest of tasksargswill simply pass through all command line arguments by using theignore_unknown_optionsandallow_extra_argsoptions of the Click context; this is often used for tasks that launch a specific command line tool to enable arbitrary arguments
All other arguments to the task must be annotated with either
option()orargument(). They both support the same arguments as the corresponding decoratorsclick.option()andclick.argument().A simple example:
1@task() 2def simple_task(cfg, args): 3 foo("do something")
This would make
simple_taskavailable as a new subcommand of spin.More elaborate examples can be found in the built-in plugins shipping with spin.
- csspin.group(*args: Any, **kwargs: Any) Callable[source]
Decorator for task groups, to create nested commands.
This works like
click.Group, but additionally supports subcommand aliases, that can be set via the aliases keyword argument totask(). Example:@group() def foo(): pass @foo.task() def bar(): pass
The above example creates a
spin foo barcommand.
- csspin.argument(**kwargs: Any) Callable[source]
Annotations task arguments.
This works just like
click.argument(), accepting all the same parameters. Example:1@task() 2def mytask(outfile: argument(type="...", help="...")): 3 foo("do something")
- csspin.option(*args: Any, **kwargs: Any) Callable[source]
Annotations for task options.
This works just like
click.option(), accepting the same parameters. Example:1@task() 2def mytask( 3 outfile: option( 4 "-o", 5 "outfile", 6 default="-", 7 type=click.File("w"), 8 help="... usage information ...", 9 ), 10): 11 foo("do something")
Interacting with spin
- csspin.config(*args: Any | None, **kwargs: Any) ConfigTree[source]
config creates a configuration subtree:
>>> config(a="alpha", b="beta) {"a": "alpha", "b": "beta}
Plugins use config to declare their
defaultstree.
- csspin.invoke(hook: str, *args: Any, **kwargs: Any) None[source]
invoke()invokes the tasks that have thewhenhook hook. As an example, here is the implementation of test:@task(aliases=["tests"]) def test(cfg, coverage: option("--coverage", "coverage", is_flag=True)): """Run all tests defined in this project""" invoke("test", coverage=coverage)
The way a task that uses invoke is invoking other tasks is part of the call interface contract: all tasks initialized like
@task(when="test")must support thecoverageargument as part of their Python function signature (albeit not necessarily the same command line flag--coverage).
- csspin.interpolate1(literal: str | Path, *extra_dicts: dict, interpolate_environ: bool = True) str | Path[source]
Interpolate a string or path against the configuration tree and the environment.
Example:
>>> interpolate1("{SHELL}") '/usr/bin/zsh'
If literal is not a string or path, it will be converted to a string prior interpolating.
To avoid interpolation for literals or specific parts of a literal, curly braces can be used to escape curly braces, like regular f-string interpolation.
Example:
>>> interpolate1( ... '{{"header": {{"language": "en", "data": "{SPIN_DATA}"}}}}' ... ) '{"header": {"language": "en", "data": "/home/developer/.local/share/spin"}}'
It may be necessary to omit the interpolation against the environment, in that case the parameter
interpolate_environcan be set toFalse.Example:
>>> interpolate1("{spin.version} and {PATH}", interpolate_environ=False) "1.0.2.dev5 and {PATH}"
Attention
Do not use
csspin.interpolate1()in a plugins’ top-level, as the one can’t rely on the configuration tree at import time of the module.Negative example: How not to usecsspin.interpolate1()1from csspin import config, interpolate1 2 3defaults = config(key=interpolate1("{some.property}"))
Attention
If the interpolated property is not set, not NoneType but “None” as a string is returned.
- csspin.interpolate(literals: Iterable, *extra_dicts: dict) list[source]
Interpolate an iterable of hashable items against the configuration tree.
- csspin.toporun(cfg: ConfigTree, *fn_names: Any, reverse: bool = False) None[source]
Run plugin functions named in ‘fn_names’ in topological order.
- csspin.EXPORTS: list[tuple[str, str]] = []
EXPORTS is a list that contains all (key, value) tuples of environment variables that got set or unset via
csspin.setenv()during the current spin execution.The
valueof a given element is already fully interpolated, except for parts that look like environment variables. So any plugin usingEXPORTSis able to lazily evaluate the value of a variable in cases, where it has been set multiple times.A case where that’s relevant can be seen in the following example:
Example:
>>> os.environ.getenv("PATH") "/usr/bin:/bin" >>> setenv(PATH="{spin.project_root}/bin:{PATH}") >>> setenv(PATH="{python.scriptdir}:{PATH}") >>> EXPORTS [("PATH", "/home/foo/project/bin:{PATH}"), ("PATH", "/home/foo/project/.spin/venv/bin:{PATH})]
As can be seen, the real value of
PATHshould be"/home/foo/project/.spin/venv/bin:/home/foo/project/bin:"/usr/bin:/bin", which any plugin could now generate.
Communication with the user
- class csspin.Verbosity(value)[source]
enum.IntEnumdefining four verbosity levels:QUIET: Outputs only warnings and errors viacsspin.warn()andcsspin.error().NORMAL: Outputs the normal amount of verbosity, extending the quiet level by enablingcsspin.echo().INFO: Extends normal verbosity to enablecsspin.info().DEBUG: Extends info verbosity to enable debug messages viacsspin.debug().
- csspin.echo(*msg: str, resolve: bool = False, **kwargs: Any) None[source]
Print a message to the console by joining the positional arguments msg with spaces.
echo is meant for messages that explain to the user what spin is doing (e.g. echoing commands launched). It will remain silent though when
spinis run with the--quietflag. If the parameterresolveis set toTrue, the arguments are interpolated against the configuration tree.echo supports the same keyword arguments as Click’s
click.echo().
- csspin.info(*msg: str, **kwargs: Any) None[source]
Print a message to the console by joining the positional arguments msg with spaces.
Arguments are interpolated against the configuration tree. info will remain silent unless
spinis run with the--verboseflag. info is meant for messages that provide additional details.info supports the same keyword arguments as Click’s
click.echo().
- csspin.debug(*msg: str, resolve: bool = False, **kwargs: Any) None[source]
Print a message to the console by joining the positional arguments msg with spaces.
Arguments are interpolated against the configuration tree if
resolveevaluates toTrue. debug will remain silent unlessspinis run with the-vvflag. debug is meant for messages that provide internal details.debug supports the same keyword arguments as Click’s
click.echo().
- csspin.warn(*msg: str, **kwargs: Any) None[source]
Print a warning message to the console by joining the positional arguments msg with spaces.
Arguments are interpolated against the configuration tree. The output is written to standard error.
warn supports the same keyword arguments as Click’s
click.echo().
- csspin.error(*msg: str, resolve: bool = True, **kwargs: Any) None[source]
Print an error message to the console by joining the positional arguments msg with spaces.
Arguments are interpolated against the configuration tree if resolve evaluates to True. The output is written to standard error.
error supports the same keyword arguments as Click’s
click.echo().
Handling Processes
- csspin.sh(*cmd: Any, **kwargs: Any) subprocess.CompletedProcess | None[source]
Run a program by building a command line from cmd.
When multiple positional arguments are given, each is treated as one element of the command. When just one positional argument is used, sh assumes it to be a single command and splits it into multiple arguments using
shlex.split(). The cmd arguments are interpolated against the configuration tree. When silent isFalse, the resulting command line will be echoed. When shell isTrue, the command line is passed to the system’s shell.Other keyword arguments are passed into
subprocess.run().All positional arguments are interpolated against the configuration tree.
>>> sh("ls", "{HOME}")
- csspin.setenv(*args: Any, **kwargs: Any) None[source]
Set or unset one or more environment variables. The values of keyword arguments are interpolated against the configuration tree.
Passing
Noneas a value removes the environment variable.Variables that have been set during or before
configure()will be patched into the activation scripts of the Python virtual environment.On Windows, all passed environment variable keys will be set in upper case.
>>> setenv(FOO="{foo.bar}", BAZ="some-value")
- class csspin.Command(*cmd: str)[source]
Create a function that is a shrink-wrapped shell command.
The callable returned behaves like
sh(), accepting additional arguments for the wrapper command as positional parameters. All positional arguments are interpolated against the configuration tree.Example:
>>> install = Command("pip", "install") >>> install("spin")
Handling state
- class csspin.Memoizer(fn: str | Path)[source]
Maintain a persistent base of simple facts.
Facts are loaded from file fn. The argument is interpolated against the configuration tree. If fn does not exist, there are no facts.
The Memoizer class stores and retrieves Python objects from the binary file named fn. The argument is interpolated against the configuration tree. Memoizer can be used to keep a simple “database”. Spin internally uses Memoizers for e.g. keeping track of packages installed in a virtual environment.
To ease the handling in spin scripts, there also is context manager called memoizer (note the lower case “m”). The context manager retrieves the database from the file and saves it back when the context is closed:
>>> with memoizer(fn) as m: ... if m.check("test"): ...
There are no precautions for simultaneous access from multiple processes, writes will likely silently become lost.
Files and Path handling
- csspin.cd(path: str | Path) DirectoryChanger[source]
Change directory.
The path argument is interpolated against the configuration tree.
cd can be used either as a function or as a context manager. When used as a context manager, the working directory is changed back to what it was before the
withblock.You can do this:
>>> cd("{spin.project_root}")
… or that:
>>> with cd("{spin.project_root}"): <do something in this directory>
- csspin.copy(source: str | Path, target: str | Path) None[source]
Copy a file or directory recursively from source to target in case the target exists.
- csspin.exists(path: str | Path) bool[source]
Check whether path exists. The argument is interpolated against the configuration tree.
- csspin.mkdir(path: str | Path) str[source]
Ensure that path exists.
If necessary, directories are recursively created to make path available. The argument is interpolated against the configuration tree.
- csspin.mv(source: str | Path, target: str | Path) None[source]
Move a file or directory recursively from source to target in case the target exists, otherwise rename source to target.
- csspin.rmtree(path: str | Path) None[source]
Recursively remove path and everything it contains. Can also remove single files. The argument is interpolated against the configuration tree.
Obviously, this should be used with care.
- csspin.download(url: str, location: str | Path, headers: dict | None = None) None[source]
Download data from
urltolocationusing optionalheaders.
- csspin.appendtext(fn: str | Path, data: str) int[source]
Append data, which is text (Unicode object of type str) to the file named fn.
The file name argument is interpolated against the configuration tree.
- csspin.getmtime(fn: str | Path) float[source]
Get the modification of file fn.
fn is interpolated against the configuration tree.
- csspin.persist(fn: str | Path, data: Type[object]) int[source]
Persist the Python object(s) in data using
pickle.
- csspin.readbytes(fn: str | Path) bytes[source]
readbytes reads binary data. The file name argument is interpolated against the configuration tree.
- csspin.readtext(fn: str | Path) str[source]
Read an UTF8 encoded text from the file ‘fn’.
The file name argument is interpolated against the configuration tree.
- csspin.unpersist(fn: str, default: Any | None = None) Any | None[source]
Load pickled Python object(s) from the file fn.