Skip to main content

public -- @public for populating __all__

Project description

=========
@public
=========

This is a very simple decorator and function which populates a module's
``__all__`` and optionally the module globals. It is a standalone version of
the implementation being proposed for Python 3.6 in `issue 26632`_ on the
Python bug tracker.

This provides both a pure-Python implementation and a C implementation. It is
proposed that the C implementation be added to builtins_ for Python 3.6.


Background
==========

``__all__`` is great. It has both a functional and a documentation purpose.

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
===========

However, ``__all__`` has two problems.

First, its 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 all 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.


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

The solution is to provide a way to declare a name's public-ness 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 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.

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

@public
def foo():
pass

or::

@public
class Bar:
pass

If you were to print the ``__all__`` after both of those code snippets, you'd
see::

>>> 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 just append new names to the existing list.

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.

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::

public(SEVEN=7)
public(a_bar=Bar())

Now if you print the module's ``__all__`` you'll see::

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

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

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

**Note:** While you can use ``public()`` with multiple keyword arguments in a
single call, the order of the resulting ``__all__`` entries is undefined, due
to indeterminate dictionary sort order. If order matters to you, call
``public()`` multiple times each with a single keyword argument.


Usage
=====

To use this, just import it::

>>> from public import public

This package actually provides both a pure Python implementation and a C
implementation. It is expected/hoped that the C implementation might appear
in Python 3.6. By default, the import above provides you with the more
efficient C implementation. If for some reason you want the pure-Python
implementation just do::

>>> from public import py_public as public

Having to do this import in every module you want to use it can get pretty
tedious, so what if you could put ``public`` into Python's builtins? Then it
would be available in all your code for free::

>>> from public import install
>>> install()

and now you can just use ``@public`` without having to import anything in your
other modules.

By default, this installs the C implementation but if you wanted to install
the pure-Python version, just do::

>>> from public import py_install
>>> py_install()


Caveats
=======

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

* Only use ``@public`` on top-level object. Specifically, don't try to use
``@public`` on a class method name. While the declaration won't fail, when
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`` 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;
* provides both C and Python implementations;
* can optionally put ``public`` in builtins.


Author
======

``public`` is Copyright (C) 2016 Barry Warsaw

Contact Barry:

* barry@python.org
* @pumpichank on Twitter
* @warsaw on GitHub and GitLab

Licensed under the terms of the Apache License 2.0. See LICENSE.txt for
details.


Project details
===============

* Project home: https://gitlab.com/warsaw/public
* Report bugs at: https://gitlab.com/warsaw/public/issues
* Fork the code: https://gitlab.com/warsaw/public.git
* Documentation: http://public.readthedocs.io/en/latest/
* PyPI: https://pypi.python.org/pypi/atpublic


NEWS
====

.. toctree::
:maxdepth: 2

NEWS



Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`


.. _`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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

atpublic-0.3.tar.gz (18.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page