==========================
Using @public and @private
==========================

This library provies two very simple decorators that document the *publicness*
of the names in your module.  They keep your module's ``__all__`` in sync so
you don't have to.


Background
==========

``__all__`` is great.  It has both functional and documentation purposes.

The functional purpose is that it `directly controls`_ which module names are
imported by the ``from <module> import *`` statement.  In the absence of an
``__all__``, when this statement is executed, every name in ``<module>`` that
does not start with an underscore will be imported.  This often leads to
importing too many names into the module.  That's a good enough reason not to
use ``from <module> import *`` with modules that don't have an ``__all__``.

In the presence of an ``__all__``, only the names specified in this list are
imported by the ``from <module> import *`` statement.  This in essence gives
the ``<module>`` author a way to explicitly state which names are for public
consumption.

And that's the second purpose of ``__all__``; it serves as module
documentation, explicitly naming the public objects it wants to export.  You
can print a module's ``__all__`` and get an explicit declaration of its public
API.


The problem with __all__
========================

``__all__`` has two problems.

First, it separates the declaration of a name's public export semantics from
the implementation of that name.  Usually the ``__all__`` is put at the top of
the module, although this isn't required, and in some cases it's `actively
prohibited`_.  So when you're looking at the definition of a function or class
in a module, you have to search for the ``__all__`` definition to know whether
the function or class is intended for public consumption.

This leads to the second problem, which is that it's too easy for the
``__all__`` to get `out of sync`_ with the module's contents.  Often a
function or class is renamed, removed, or added without the ``__all__`` being
updated.  Then it's difficult to know what the module author's intent was, and
it can lead to an exception when a string appearing in ``__all__`` doesn't
match an existing name in the module.  Some tools like Sphinx_ will complain
when names appear in ``__all__`` don't appear in the module.  All of this
points to the root problem; it should be easy to keep ``__all__`` in sync!


The solution
============

This package provides a way to declare a name's *publicness* right at the
point of its declaration, and to infer the name to export from that
definition.  In this way, a module's author never explicitly sets the
``__all__`` so there's no way for it to get out of sync.

This package, and Python `issue 26632`_, propose just such a solution, in the
form of a ``public`` builtin that can be used as either a decorator, or a
callable.

    >>> from public import public

You'll usually use this as a decorator, for example::

    >>> @public
    ... def foo():
    ...    pass

or::

    >>> @public
    ... class Bar:
    ...     pass

The ``__all__`` after both of those code snippets has both names in it::

    >>> print(__all__)
    ['foo', 'Bar']

Note that you do not need to initialize ``__all__`` in the module, since
``public`` will do it for you.  Of course, if your module *already* has an
``__all__``, it will add any new names to the existing list.


Function call form
==================

The requirements to use the ``@public`` decorator are simple: the decorated
thing must have a ``__name__`` attribute.  Since you'll overwhelmingly use it
to decorate functions and classes, this will always be the case.  If the
object has a ``__module__`` attribute, that string is used to look up the
module object in ``sys.modules``, otherwise the module is extracted from the
globals where the decorator is called.

There's one other common use case that isn't covered by the ``@public``
decorator.  Sometimes you want to declare simple constants or instances as
publicly available.  You can't use the ``@public`` decorator for two reasons:
constants don't have a ``__name__`` and Python's syntax doesn't allow you to
decorate such constructs.

To solve this use case, ``public`` is also a callable function accepting
keyword arguments.  An example makes this obvious.  We'll start by resetting
the ``__all__``.

    >>> reset()
    >>> public(SEVEN=7)
    7
    >>> public(a_bar=Bar())
    <...Bar object ...>

The module's ``__all__`` now contains both of the keys::

    >>> print(__all__)
    ['SEVEN', 'a_bar']

and as should be obvious, the module contains name bindings for these
constants::

    >>> print(SEVEN)
    7
    >>> print(a_bar)
    <....Bar object at ...>

Multiple keyword arguments are allowed::

    >>> public(ONE=1, TWO=2)
    (1, 2)
    >>> print(__all__)
    ['SEVEN', 'a_bar', 'ONE', 'TWO']

    >>> print(ONE)
    1
    >>> print(TWO)
    2

You'll notice that the functional form of ``public()`` returns the values in
its keyword arguments in order.  This is to help with a use case where some
linters complain bcause they can't see that ``public()`` binds the names in
the global namespace.  In the above example they might report erroneously that
``ONE`` and ``TWO`` aren't defined.  To work around this, when ``public()`` is
used in its functional form, it will return the values in the order they are
seen [#]_ and you can simply assign them to explicit local variable names.

    >>> a, b, c = public(a=3, b=2, c=1)
    >>> print(__all__)
    ['SEVEN', 'a_bar', 'ONE', 'TWO', 'a', 'b', 'c']
    >>> print(a, b, c)
    3 2 1

It also works if you bind only a single value.

    >>> d = public(d=9)
    >>> print(__all__)
    ['SEVEN', 'a_bar', 'ONE', 'TWO', 'a', 'b', 'c', 'd']
    >>> print(d)
    9


@private
========

You might also want to be explicit about your private, i.e. non-public names.
This library also provides an ``@private`` decorator for this purpose.  While
it mostly serves for documentation purposes, this decorator also ensures that
the decorated object's name does *not* appear in the ``__all__``.  As above,
we'll start by resetting ``__all__``::

    >>> reset()
    >>> from public import private

    >>> @private
    ... def foo():
    ...    pass

    >>> print(__all__)
    []

You can see here that ``foo`` has been removed from the ``__all__``.  It's
okay if the name doesn't appear in ``__all__`` at all::

    >>> @private
    ... class Baz:
    ...     pass

    >>> print(__all__)
    []

In this case, ``Baz`` never appears in ``__all__``.  Like with ``@public``,
the ``@private`` decorator will initialize ``__all__`` if needed, but if it
exists in the module, it must be a list.  There is no functional API for
``@private``.


Caveats
=======

There are some important usage restrictions you should be aware of:

* Only use ``@public`` and ``@private`` on top-level object.  Specifically,
  don't try to use either decorator on a class method name.  While the
  declaration won't fail, you will get an exception when you attempt to ``from
  <module> import *`` because the name pulled from ``__all__`` won't be in the
  module's globals.
* If you explicitly set ``__all__`` in your module, be sure to set it to a
  list.  Some style guides require ``__all__`` to be a tuple, but since that's
  immutable, as soon as ``@public`` tries to append to it, you will get an
  exception.  Best practice is to not set ``__all__`` explicitly; let
  ``@public`` and ``@private`` do it!
* If you still want ``__all__`` to be immutable, put the following at the
  bottom of your module::

    __all__ = tuple(__all__)


Alternatives
============

This isn't a unique approach to ``@public``.  Other_ implementations_ do
exist.  There are some subtle differences between this package and those
others.  This package:

* uses keyword arguments to map names which don't have an ``__name__``
  attribute;
* can be used to bind names and values into a module's globals;
* can optionally put ``public`` in builtins.

.. rubric:: Footnotes

.. [#] This is ordering is guaranteed by `PEP 468 <https://peps.python.org/pep-0468/>`_.


.. _`issue 26632`: http://bugs.python.org/issue26632
.. _builtins: https://docs.python.org/3/library/builtins.html
.. _`directly controls`: https://docs.python.org/3/tutorial/modules.html#importing-from-a-package
.. _`actively prohibited`: http://pep8.readthedocs.io/en/latest/intro.html?highlight=e402#error-codes
.. _`out of sync`: http://bugs.python.org/issue23883
.. _Other: https://pypi.python.org/pypi/public
.. _implementations: http://bugs.python.org/issue22247#msg225637
.. _Sphinx: http://www.sphinx-doc.org/en/stable/
