.. _task-namespaces:

=======================
Constructing namespaces
=======================

The :doc:`base case </getting-started>` of loading a single module of tasks
works fine initially, but advanced users typically need more organization, such
as separating tasks into a tree of nested namespaces.

The `.Collection` class provides an API for organizing tasks (and :ref:`their
configuration <collection-configuration>`) into a tree-like structure. When
referenced by strings (e.g. on the CLI or in pre/post hooks) tasks in nested
namespaces use a dot-separated syntax, e.g. ``docs.build``.

In this section, we show how building namespaces with this API is flexible but
also allows following Python package layouts with minimal boilerplate.


Starting out
============

One unnamed ``Collection`` is always the namespace root; in the implicit base
case, Invoke creates one for you from the tasks in your tasks module.  Create
your own, named ``namespace`` or ``ns``, to set up an explicit namespace (i.e.
to skip the default "pull in all Task objects" behavior)::

    from invoke import Collection

    ns = Collection()
    # or: namespace = Collection()

Add tasks with `.Collection.add_task`. `~.Collection.add_task` can take an
`.Task` object, such as those generated by the `.task` decorator::

    from invoke import Collection, task

    @task
    def release(c):
        c.run("python setup.py sdist register upload")

    ns = Collection()
    ns.add_task(release)

Our available tasks list now looks like this::

    $ invoke --list
    Available tasks:

        release


Naming your tasks
=================

By default, a task's function name is used as its namespace identifier, but you
may override this by giving a ``name`` argument to either `@task <.task>` (i.e.
at definition time) or `.Collection.add_task` (i.e. at binding/attachment
time).

For example, say you have a variable name collision in your tasks module --
perhaps you want to expose a ``dir`` task, which shadows a Python builtin.
Naming your function itself ``dir`` is a bad idea, but you can name the
function something like ``dir_`` and then tell ``@task`` the "real" name::

    @task(name='dir')
    def dir_(c):
        # ...

On the other side, you might have obtained a task object that doesn't fit with
the names you want in your namespace, and can rename it at attachment time.
Maybe we want to rename our ``release`` task to be called ``deploy`` instead::

    ns = Collection()
    ns.add_task(release, name='deploy')

The result::

    $ invoke --list
    Available tasks:

        deploy

.. note::
    The ``name`` kwarg is the 2nd argument to `~.Collection.add_task`, so those
    in a hurry can phrase it as::

        ns.add_task(release, 'deploy')

Aliases
-------

Tasks may have additional names or aliases, given as the ``aliases`` keyword
argument; these are appended to, instead of replacing, any implicit or explicit
``name`` value::

    ns.add_task(release, aliases=('deploy', 'pypi'))

Result, with three names for the same task::

    $ invoke --list
    Available tasks:

        release
        deploy
        pypi

.. note::
    The convenience decorator `@task <.task>` is another method of
    setting aliases (e.g. ``@task(aliases=('foo', 'bar'))``, and is useful for
    ensuring a given task always has some aliases set no matter how it's added
    to a namespace.

.. _dashes-vs-underscores:

Dashes vs underscores
---------------------

In the common case of functions-as-tasks, you'll often find yourself writing
task names that contain underscores::

    @task
    def my_awesome_task(c):
        print("Awesome!")

Similar to how task arguments are processed to turn their underscores into
dashes (since that's a common command-line convention) all underscores in task
or collection names are interpreted to be dashes instead, by default::

    $ inv --list
    Available tasks:

      my-awesome-task

    $ inv my-awesome-task
    Awesome!

If you'd prefer the underscores to remain instead, you can update your
configuration to set ``tasks.auto_dash_names`` to ``False`` in one of the
non-runtime :ref:`config files <config-files>` (system, user, or project.) For
example, in ``~/.invoke.yml``::

    tasks:
        auto_dash_names: false

.. note::
    In the interests of avoiding confusion, this setting is "exclusive" in
    nature - underscored version of task names *are not valid* on the CLI
    unless ``auto_dash_names`` is disabled. (However, at the pure function
    level within Python, they must continue to be referenced with underscores,
    as dashed names are not valid Python syntax!)


Nesting collections
===================

The point of namespacing is to have sub-namespaces; to do this in Invoke,
create additional `.Collection` instances and add them to their parent
collection via `.Collection.add_collection`. For example, let's say we have a
couple of documentation tasks::

    @task
    def build_docs(c):
        c.run("sphinx-build docs docs/_build")

    @task
    def clean_docs(c):
        c.run("rm -rf docs/_build")

We can bundle them up into a new, named collection like so::

    docs = Collection('docs')
    docs.add_task(build_docs, 'build')
    docs.add_task(clean_docs, 'clean')

And then add this new collection under the root namespace with
``add_collection``::

    ns.add_collection(docs)

The result (assuming for now that ``ns`` currently just contains the original
``release`` task)::

    $ invoke --list
    Available tasks:

        release
        docs.build
        docs.clean

As with tasks, collections may be explicitly bound to their parents with a
different name than they were originally given (if any) via a ``name`` kwarg
(also, as with ``add_task``, the 2nd regular arg)::

    ns.add_collection(docs, 'sphinx')

Result::

    $ invoke --list
    Available tasks:

        release
        sphinx.build
        sphinx.clean


Importing modules as collections
================================

A simple tactic which Invoke itself uses in the trivial, single-module
case is to use `.Collection.from_module` -- a classmethod
serving as an alternate ``Collection`` constructor which takes a Python module
object as its first argument.

Modules given to this method are scanned for ``Task`` instances, which are
added to a new ``Collection``. By default, this collection's name is taken from
the module name (the ``__name__`` attribute), though it can also be supplied
explicitly.

.. note::
    As with the default task module, you can override this default loading
    behavior by declaring a ``ns`` or ``namespace`` `.Collection` object at top
    level in the loaded module.

For example, let's reorganize our earlier single-file example into a Python
package with several submodules. First, ``tasks/release.py``::

    from invoke import task

    @task
    def release(c):
        c.run("python setup.py sdist register upload")

And ``tasks/docs.py``::

    from invoke import task

    @task
    def build(c):
        c.run("sphinx-build docs docs/_build")

    @task
    def clean(c):
        c.run("rm -rf docs/_build")

Tying them together is ``tasks/__init__.py``::

    from invoke import Collection

    import release, docs

    ns = Collection()
    ns.add_collection(Collection.from_module(release))
    ns.add_collection(Collection.from_module(docs))

This form of the API is a little unwieldy in practice. Thankfully there's a
shortcut: ``add_collection`` will notice when handed a module object as its
first argument and call ``Collection.from_module`` for you internally::

    ns = Collection()
    ns.add_collection(release)
    ns.add_collection(docs)

Either way, the result::

    $ invoke --list
    Available tasks:

        release.release
        docs.build
        docs.clean


Default tasks
=============

Tasks may be declared as the default task to invoke for the collection they
belong to, e.g. by giving ``default=True`` to `@task <.task>` (or to
`.Collection.add_task`.) This is useful when you have a bunch of related tasks
in a namespace but one of them is the most commonly used, and maps well to the
namespace as a whole.

For example, in the documentation submodule we've been experimenting with so
far, the ``build`` task makes sense as a default, so we can say things like
``invoke docs`` as a shortcut to ``invoke docs.build``. This is easy to do::

    @task(default=True)
    def build(c):
        # ...

When imported into the root namespace (as shown above) this alters the output
of ``--list``, highlighting the fact that ``docs.build`` can be invoked as
``docs`` if desired::

    $ invoke --list
    Available tasks:

        release.release
        docs.build (docs)
        docs.clean

Default subcollections
----------------------

As of version 1.5, this functionality is also extended to subcollections: a
subcollection can be specified as the default when being added to its parent
collection, and that subcollection's own default task (or sub-subcollection!)
will be invoked as the default for the parent.

An example probably makes that clearer. Here's a tiny inline task tree with two
subcollections, each with their own default task::

    from invoke import Collection, task

    @task(default=True)
    def build_all(c):
        print("build ALL THE THINGS!")

    @task
    def build_wheel(c):
        print("Just the wheel")

    build = Collection(all=build_all, wheel=build_wheel)

    @task(default=True)
    def build_docs(c):
        print("Code without docs is no code at all")

    docs = Collection(build_docs)

Then we tie those into one top level collection, setting the ``build``
subcollection as the overall default::

    ns = Collection()
    ns.add_collection(build, default=True)
    ns.add_collection(docs)

The result is that ``build.all`` becomes the absolute default task::

    $ invoke
    build ALL THE THINGS!

Mix and match
=============

You're not limited to the specific tactics shown above -- now that you know
the basic tools of ``add_task`` and ``add_collection``, use whatever approach
best fits your needs.

For example, let's say you wanted to keep things organized into submodules, but
wanted to "promote" ``release.release`` back to the top level for convenience's
sake. Just because it's stored in a module doesn't mean we must use
``add_collection`` -- we could instead import the task itself and use
``add_task`` directly::

    from invoke import Collection

    import docs
    from release import release

    ns = Collection()
    ns.add_collection(docs)
    ns.add_task(release)

Result::

    $ invoke --list
    Available tasks:

        release
        docs.build
        docs.clean


More shortcuts
==============

Finally, you can even skip ``add_collection`` and ``add_task`` if your needs
are simple enough -- `.Collection`'s constructor will take
unknown arguments and build the namespace from their values as
appropriate::

    from invoke import Collection

    import docs, release

    ns = Collection(release.release, docs)

Notice how we gave both a task object (``release.release``) and a module
containing tasks (``docs``). The result is identical to the above::

    $ invoke --list
    Available tasks:

        release
        docs.build
        docs.clean

If given as keyword arguments, the keywords act like the ``name`` arguments do
in the ``add_*`` methods. Naturally, both can be mixed together as well::

    ns = Collection(docs, deploy=release.release)

Result::

    $ invoke --list
    Available tasks:

        deploy
        docs.build
        docs.clean

.. note::
    You can still name these ``Collection`` objects with a leading string
    argument if desired, which can be handy when building sub-collections.
