# @public

This is a very simple decorator and function which populates a module’s `__all__` and optionally the module globals. This provides both a pure-Python implementation and an optional C implementation.

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

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

## The solution

The solution is to provide 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.

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 in Python versions earlier than 3.6, 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 an optional C extension module. By default, the import above provides you with the more efficient C implementation, if available. See the installation instructions below for details.

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()

## Installation

Use the normal `setup.py install` or `pip install` commands to install this library. By default, the C extension is not built, in order to make it more portable to environments without a C compiler. If you want a version that’s a little more efficient than the pure-Python implementation, set the environment variable `ATPUBLIC_BUILD_EXTENSION=1` when you build/install the module.

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

- @public NEWS
  - 0.5 (2016-12-14)
  - 0.4 (2016-11-28)
  - 0.3 (2016-05-25)
  - 0.2 (2016-05-22)
  - 0.1 (2016-05-09)

## Indices and tables

- Index
- Module Index
- Search Page