atpublic-0.5/ 0000775 0001750 0001750 00000000000 13024254112 013435 5 ustar barry barry 0000000 0000000 atpublic-0.5/setup.py 0000664 0001750 0001750 00000003577 13024252350 015165 0 ustar barry barry 0000000 0000000 # Copyright (C) 2016 Barry Warsaw
#
# This project is licensed under the terms of the Apache 2.0 License. See
# LICENSE.txt for details.
import os
from setup_helpers import get_version, require_python
from setuptools import setup, find_packages, Extension
require_python(0x30400f0)
__version__ = get_version('public/__init__.py')
# It can be a pain in some environments to build the C extension module. For
# example, if you're using tox on a platform that doesn't have a C compiler.
# OTOH, we can be more efficient with the C extension. In order to give
# environments the option to build what they want, we'll only build the C
# extension if this environment variable is set.
ext_modules = []
if os.getenv('ATPUBLIC_BUILD_EXTENSION', False):
ext_modules.append(Extension('_public', ['src/public.c']))
setup(
name='atpublic',
version=__version__,
description='public -- @public for populating __all__',
long_description="""\
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 a C implementation. It is
proposed that the C implementation be added to builtins_ for Python 3.6.
""",
author='Barry Warsaw',
author_email='barry@python.org',
license='Apache 2.0',
url='http://public.readthedocs.io/',
packages=find_packages(),
include_package_data=True,
ext_modules=ext_modules,
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX',
'Operating System :: Microsoft :: Windows',
'Operating System :: MacOS :: MacOS X',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities',
]
)
atpublic-0.5/setup.cfg 0000664 0001750 0001750 00000000073 13024254112 015256 0 ustar barry barry 0000000 0000000 [egg_info]
tag_date = 0
tag_build =
tag_svn_revision = 0
atpublic-0.5/NEWS.rst 0000664 0001750 0001750 00000001103 13024254057 014746 0 ustar barry barry 0000000 0000000 ==============
@public NEWS
==============
0.5 (2016-12-14)
================
* Fix MANIFEST.in inclusion of the src directory for the C extension.
0.4 (2016-11-28)
================
* Add Python 3.6 support.
* Make building the C extension optional, for environments without a C
compiler.
0.3 (2016-05-25)
================
* Raise ``ValueError`` when ``__all__`` isn't a list (or subclass) instance.
0.2 (2016-05-22)
================
* Documentation updates based on initial feedback.
* Some minor test suite clean up.
0.1 (2016-05-09)
================
* Initial release.
atpublic-0.5/MANIFEST.in 0000664 0001750 0001750 00000000245 13024254057 015204 0 ustar barry barry 0000000 0000000 include *.py MANIFEST.in
global-include *.txt *.rst *.ini *.c *.h
recursive-include src
prune build
prune dist
prune .tox
prune .git
exclude .gitignore
exclude *.so
atpublic-0.5/atpublic.egg-info/ 0000775 0001750 0001750 00000000000 13024254112 016732 5 ustar barry barry 0000000 0000000 atpublic-0.5/atpublic.egg-info/SOURCES.txt 0000664 0001750 0001750 00000000510 13024254112 020612 0 ustar barry barry 0000000 0000000 LICENSE.txt
MANIFEST.in
NEWS.rst
README.rst
conf.py
coverage.ini
setup.py
setup_helpers.py
tox.ini
atpublic.egg-info/PKG-INFO
atpublic.egg-info/SOURCES.txt
atpublic.egg-info/dependency_links.txt
atpublic.egg-info/top_level.txt
public/__init__.py
public/public.py
public/tests/__init__.py
public/tests/test_public.py
src/public.c atpublic-0.5/atpublic.egg-info/PKG-INFO 0000664 0001750 0001750 00000001743 13024254112 020034 0 ustar barry barry 0000000 0000000 Metadata-Version: 1.1
Name: atpublic
Version: 0.5
Summary: public -- @public for populating __all__
Home-page: http://public.readthedocs.io/
Author: Barry Warsaw
Author-email: barry@python.org
License: Apache 2.0
Description: 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 a C implementation. It is
proposed that the C implementation be added to builtins_ for Python 3.6.
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
atpublic-0.5/atpublic.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 13024254112 023000 0 ustar barry barry 0000000 0000000
atpublic-0.5/atpublic.egg-info/top_level.txt 0000664 0001750 0001750 00000000007 13024254112 021461 0 ustar barry barry 0000000 0000000 public
atpublic-0.5/setup_helpers.py 0000664 0001750 0001750 00000012106 12714113364 016701 0 ustar barry barry 0000000 0000000 # Copyright (C) 2009-2015 Barry A. Warsaw
#
# This file is part of setup_helpers.py
#
# setup_helpers.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, version 3 of the License.
#
# setup_helpers.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with setup_helpers.py. If not, see .
"""setup.py helper functions."""
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
'description',
'find_doctests',
'get_version',
'long_description',
'require_python',
]
import os
import re
import sys
DEFAULT_VERSION_RE = re.compile(
r'(?P\d+\.\d+(?:\.\d+)?(?:(?:a|b|rc)\d+)?)')
EMPTYSTRING = ''
__version__ = '2.3'
def require_python(minimum):
"""Require at least a minimum Python version.
The version number is expressed in terms of `sys.hexversion`. E.g. to
require a minimum of Python 2.6, use::
>>> require_python(0x206000f0)
:param minimum: Minimum Python version supported.
:type minimum: integer
"""
if sys.hexversion < minimum:
hversion = hex(minimum)[2:]
if len(hversion) % 2 != 0:
hversion = '0' + hversion
split = list(hversion)
parts = []
while split:
parts.append(int(''.join((split.pop(0), split.pop(0))), 16))
major, minor, micro, release = parts
if release == 0xf0:
print('Python {0}.{1}.{2} or better is required'.format(
major, minor, micro))
else:
print('Python {0}.{1}.{2} ({3}) or better is required'.format(
major, minor, micro, hex(release)[2:]))
sys.exit(1)
def get_version(filename, pattern=None):
"""Extract the __version__ from a file without importing it.
While you could get the __version__ by importing the module, the very act
of importing can cause unintended consequences. For example, Distribute's
automatic 2to3 support will break. Instead, this searches the file for a
line that starts with __version__, and extract the version number by
regular expression matching.
By default, two or three dot-separated digits are recognized, but by
passing a pattern parameter, you can recognize just about anything. Use
the `version` group name to specify the match group.
:param filename: The name of the file to search.
:type filename: string
:param pattern: Optional alternative regular expression pattern to use.
:type pattern: string
:return: The version that was extracted.
:rtype: string
"""
if pattern is None:
cre = DEFAULT_VERSION_RE
else:
cre = re.compile(pattern)
with open(filename) as fp:
for line in fp:
if line.startswith('__version__'):
mo = cre.search(line)
assert mo, 'No valid __version__ string found'
return mo.group('version')
raise AssertionError('No __version__ assignment found')
def find_doctests(start='.', extension='.rst'):
"""Find separate-file doctests in the package.
This is useful for Distribute's automatic 2to3 conversion support. The
`setup()` keyword argument `convert_2to3_doctests` requires file names,
which may be difficult to track automatically as you add new doctests.
:param start: Directory to start searching in (default is cwd)
:type start: string
:param extension: Doctest file extension (default is .txt)
:type extension: string
:return: The doctest files found.
:rtype: list
"""
doctests = []
for dirpath, dirnames, filenames in os.walk(start):
doctests.extend(os.path.join(dirpath, filename)
for filename in filenames
if filename.endswith(extension))
return doctests
def long_description(*filenames):
"""Provide a long description."""
res = ['']
for filename in filenames:
with open(filename) as fp:
for line in fp:
res.append(' ' + line)
res.append('')
res.append('\n')
return EMPTYSTRING.join(res)
def description(filename):
"""Provide a short description."""
# This ends up in the Summary header for PKG-INFO and it should be a
# one-liner. It will get rendered on the package page just below the
# package version header but above the long_description, which ironically
# gets stuff into the Description header. It should not include reST, so
# pick out the first single line after the double header.
with open(filename) as fp:
for lineno, line in enumerate(fp):
if lineno < 3:
continue
line = line.strip()
if len(line) > 0:
return line
atpublic-0.5/src/ 0000775 0001750 0001750 00000000000 13024254112 014224 5 ustar barry barry 0000000 0000000 atpublic-0.5/src/public.c 0000664 0001750 0001750 00000010610 13024252350 015646 0 ustar barry barry 0000000 0000000 /*
Copyright (C) 2016 Barry Warsaw
This project is licensed under the terms of the Apache 2.0 License. See
LICENSE.txt for details.
*/
#include
static PyObject *
public(PyObject *self, PyObject *args, PyObject *kwds)
{
PyObject *arg = NULL;
PyObject *globals = NULL;
PyObject *all = NULL;
PyObject *rtn = NULL;
if (!PyArg_UnpackTuple(args, "public", 0, 1, &arg))
return NULL;
/* kwds can be empty, but the keys must be strings. */
if (kwds != NULL && !PyArg_ValidateKeywordArguments(kwds))
return NULL;
if (!(globals = PyEval_GetGlobals())) {
PyErr_Format(PyExc_TypeError,
"@public called with no active globals");
return NULL;
}
if (!(all = PyDict_GetItemString(globals, "__all__"))) {
if (!(all = PyList_New(0)))
return NULL;
if (PyDict_SetItemString(globals, "__all__", all) < 0)
goto byebye;
}
else {
/* Make sure __all__ is a list, raising an exception if not. Bump
all's reference count since it's currently borrowed, and this way
we guarantee we own a reference for common exit cleanup.
*/
if (!PyList_Check(all)) {
PyErr_Format(PyExc_ValueError,
"__all__ must be a list not: %S",
all->ob_type);
goto byebye;
}
Py_INCREF(all);
}
if (arg != NULL) {
PyObject *name = NULL;
/* There is a single positional argument. This must have a "__name__"
attribute, which we will put in __all__. The keywords dictionary
must be empty.
*/
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_Format(PyExc_TypeError,
"Positional and keywords are mutually exclusive");
goto byebye;
}
if (!PyObject_HasAttrString(arg, "__name__")) {
PyErr_Format(PyExc_TypeError,
"Positional argument has no __name__");
goto byebye;
}
if (!(name = PyObject_GetAttrString(arg, "__name__")) ||
!PyUnicode_Check(name))
{
PyErr_Format(PyExc_TypeError, "Bad __name__ value");
Py_XDECREF(name);
goto byebye;
}
if (PyList_Append(all, name) < 0) {
Py_DECREF(name);
goto byebye;
}
Py_DECREF(name);
rtn = arg;
}
else if (kwds == NULL || PyDict_Size(kwds) == 0) {
PyErr_Format(
PyExc_TypeError,
"Either a single positional or keyword arguments required");
goto byebye;
}
else {
/* There are only keyword arguments, so for each of these, insert the
key in __all__ *and* bind the name/key to the value in globals. We
force the use of globals here because we're modifying the global
__all__ so it doesn't make sense to touch locals.
*/
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(kwds, &pos, &key, &value)) {
if (PyList_Append(all, key) < 0)
goto byebye;
if (PyDict_SetItem(globals, key, value) < 0)
goto byebye;
}
rtn = Py_None;
}
byebye:
Py_DECREF(all);
Py_XINCREF(rtn);
return rtn;
}
PyDoc_STRVAR(public_doc,
"public(named)\n\
public(**kws)\n\
\n\
May be used as a decorator or called directly. When used as a decorator\n\
the thing being decorated must have an __name__ attribute. The value of\n\
__name__ will be inserted in the global __all__ list. In this use case\n\
it is an error to also include keyword arguments.\n\
\n\
When called directly, it is an error to pass a positional argument. The\n\
keys must be strings, and are inserted into the global __all__ list. The\n\
mapping of keys to values is also inserted into the globals, thus creating\n\
name bindings for each key/value pair.");
static PyMethodDef public_methods[] = {
{"public", (PyCFunction)public, METH_VARARGS | METH_KEYWORDS, public_doc},
{NULL, NULL}
};
static struct PyModuleDef public_def = {
PyModuleDef_HEAD_INIT,
"public",
NULL,
0, /* size of module state */
public_methods,
NULL
};
PyObject *
PyInit__public(void)
{
return PyModule_Create(&public_def);
}
/*
* Local Variables:
* c-file-style: "python3"
* End:
*/
atpublic-0.5/PKG-INFO 0000664 0001750 0001750 00000001743 13024254112 014537 0 ustar barry barry 0000000 0000000 Metadata-Version: 1.1
Name: atpublic
Version: 0.5
Summary: public -- @public for populating __all__
Home-page: http://public.readthedocs.io/
Author: Barry Warsaw
Author-email: barry@python.org
License: Apache 2.0
Description: 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 a C implementation. It is
proposed that the C implementation be added to builtins_ for Python 3.6.
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
atpublic-0.5/README.rst 0000664 0001750 0001750 00000020165 13024252350 015132 0 ustar barry barry 0000000 0000000 =========
@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 import *`` statement. In the absence of an
``__all__``, when this statement is executed, every name in ```` 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 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 import *`` statement. This in essence gives
the ```` 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 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
atpublic-0.5/public/ 0000775 0001750 0001750 00000000000 13024254112 014713 5 ustar barry barry 0000000 0000000 atpublic-0.5/public/__init__.py 0000664 0001750 0001750 00000001502 13024254057 017032 0 ustar barry barry 0000000 0000000 # Copyright (C) 2016 Barry Warsaw
#
# This project is licensed under the terms of the Apache 2.0 License. See
# LICENSE.txt for details.
"""@public -- populate __all__"""
from public.public import public as py_public
try:
from _public import public as c_public
except ImportError: # pragma: nocover
# This library was built without the extension module.
c_public = None
__version__ = '0.5'
if c_public is None: # pragma: nocover
py_public(public=py_public)
py_public(py_public=py_public)
else: # pragma: nocover
c_public(public=c_public)
c_public(py_public=py_public)
def install():
"""Install @public into builtins."""
import builtins
builtins.public = c_public or py_public
atpublic-0.5/public/public.py 0000664 0001750 0001750 00000001353 13024252350 016547 0 ustar barry barry 0000000 0000000 # Copyright (C) 2016 Barry Warsaw
#
# This project is licensed under the terms of the Apache 2.0 License. See
# LICENSE.txt for details.
"""Pure-Python implementation."""
import sys
# http://bugs.python.org/issue26632
def public(thing=None, **kws):
mdict = (sys._getframe(1).f_globals
if thing is None
else sys.modules[thing.__module__].__dict__)
dunder_all = mdict.setdefault('__all__', [])
if not isinstance(dunder_all, list):
raise ValueError(
'__all__ must be a list not: {}'.format(type(dunder_all)))
if thing is not None:
dunder_all.append(thing.__name__)
for key, value in kws.items():
dunder_all.append(key)
mdict[key] = value
return thing
atpublic-0.5/public/tests/ 0000775 0001750 0001750 00000000000 13024254112 016055 5 ustar barry barry 0000000 0000000 atpublic-0.5/public/tests/__init__.py 0000664 0001750 0001750 00000000000 12714122424 020161 0 ustar barry barry 0000000 0000000 atpublic-0.5/public/tests/test_public.py 0000664 0001750 0001750 00000012654 13024252350 020756 0 ustar barry barry 0000000 0000000 # Copyright (C) 2016 Barry Warsaw
#
# This project is licensed under the terms of the Apache 2.0 License. See
# LICENSE.txt for details.
import os
import sys
import builtins
import unittest
try:
from _public import public as c_public
except ImportError:
# This library was built without the extension module.
c_public = None
from contextlib import ExitStack, contextmanager
from importlib import import_module
from public import install
from public.public import public as py_public
from tempfile import TemporaryDirectory
@contextmanager
def syspath(directory):
try:
sys.path.insert(0, directory)
yield
finally:
assert sys.path[0] == directory
del sys.path[0]
@contextmanager
def sysmodules():
modules = sys.modules.copy()
try:
yield
finally:
sys.modules = modules
class TestPublic(unittest.TestCase):
import_line = 'from public import public'
def setUp(self):
self.resources = ExitStack()
self.addCleanup(self.resources.close)
self.tmpdir = self.resources.enter_context(TemporaryDirectory())
self.resources.enter_context(syspath(self.tmpdir))
self.resources.enter_context(sysmodules())
self.modpath = os.path.join(self.tmpdir, 'example.py')
def test_atpublic_function(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
@public
def a_function():
pass
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.__all__, ['a_function'])
def test_atpublic_function_runnable(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
@public
def a_function():
return 1
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.a_function(), 1)
def test_atpublic_class(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
@public
class AClass:
pass
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.__all__, ['AClass'])
def test_atpublic_class_runnable(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
@public
class AClass:
pass
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertIsInstance(module.AClass(), module.AClass)
def test_atpublic_two_things(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
@public
def foo():
pass
@public
class AClass:
pass
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.__all__, ['foo', 'AClass'])
def test_atpublic_append_to_all(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
__all__ = ['a', 'b']
a = 1
b = 2
{}
@public
def foo():
pass
@public
class AClass:
pass
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.__all__, ['a', 'b', 'foo', 'AClass'])
def test_atpublic_keywords(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
public(a=1, b=2)
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(sorted(module.__all__), ['a', 'b'])
def test_atpublic_keywords_multicall(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
public(b=1)
public(a=2)
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.__all__, ['b', 'a'])
def test_atpublic_keywords_global_bindings(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
{}
public(a=1, b=2)
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.a, 1)
self.assertEqual(module.b, 2)
def test_atpublic_mixnmatch(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
__all__ = ['a', 'b']
a = 1
b = 2
{}
@public
def foo():
pass
@public
class AClass:
pass
public(c=3)
""".format(self.import_line), file=fp)
module = import_module('example')
self.assertEqual(module.__all__, ['a', 'b', 'foo', 'AClass', 'c'])
def test_all_is_a_tuple(self):
with open(self.modpath, 'w', encoding='utf-8') as fp:
print("""\
__all__ = ('foo',)
{}
def foo():
pass
@public
def bar():
pass
""".format(self.import_line), file=fp)
self.assertRaises(ValueError, import_module, 'example')
class TestPyPublic(TestPublic):
import_line = 'from public import py_public as public'
class TestInstall(unittest.TestCase):
@unittest.skipIf(c_public is None, 'Built without the extension module')
def test_install_c_public(self):
self.assertFalse(hasattr(builtins, 'public'))
self.addCleanup(delattr, builtins, 'public')
install()
self.assertTrue(hasattr(builtins, 'public'))
self.assertIs(builtins.public, c_public)
def test_install_py_public(self):
self.assertFalse(hasattr(builtins, 'public'))
self.addCleanup(delattr, builtins, 'public')
install()
self.assertTrue(hasattr(builtins, 'public'))
self.assertIs(builtins.public, c_public or py_public)
atpublic-0.5/tox.ini 0000664 0001750 0001750 00000002523 13024252350 014754 0 ustar barry barry 0000000 0000000 # Copyright (C) 2016 Barry Warsaw
#
# This project is licensed under the terms of the Apache 2.0 License. See
# LICENSE.txt for details.
[tox]
envlist = {py34,py35,py36}-{nocov,cov,diffcov,cext},qa,docs
skip_missing_interpreters=True
[testenv]
commands =
{nocov,cext}: python -m nose2 -v
{cov,diffcov}: python -m coverage run {[coverage]rc} -m nose2 -v
{cov,diffcov}: python -m coverage combine {[coverage]rc}
cov: python -m coverage html {[coverage]rc}
cov: python -m coverage report -m {[coverage]rc} --fail-under=100
diffcov: python -m coverage xml {[coverage]rc}
diffcov: diff-cover coverage.xml --html-report diffcov.html
diffcov: diff-cover coverage.xml --fail-under 100
deps =
nose2
flufl.testing
{cov,diffcov}: coverage
diffcov: diff_cover
setenv =
cov: COVERAGE_PROCESS_START={[coverage]rcfile}
cov: COVERAGE_OPTIONS="-p"
cov: COVERAGE_FILE={toxinidir}/.coverage
cext: ATPUBLIC_BUILD_EXTENSION=1
passenv =
ATPUBLIC_*
PYTHON*
usedevelop = True
[testenv:qa]
basepython = python3
commands =
python -m flake8 public
deps =
flake8
[testenv:docs]
basepython = python3
commands =
python setup.py build_sphinx
deps:
sphinx
[coverage]
rcfile = {toxinidir}/coverage.ini
rc = --rcfile={[coverage]rcfile}
[flake8]
max-line-length = 79
jobs = 1
enable-extensions = U4
atpublic-0.5/LICENSE.txt 0000664 0001750 0001750 00000001047 12714113364 015272 0 ustar barry barry 0000000 0000000 Copyright 2016 Barry Warsaw
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
atpublic-0.5/coverage.ini 0000664 0001750 0001750 00000000305 12720410343 015731 0 ustar barry barry 0000000 0000000 [run]
branch = true
parallel = true
omit =
setup*
.tox/*/lib/python*/site-packages/*
public/tests/*
/tmp/*
[paths]
source =
public
.tox/*/lib/python*/site-packages/public
atpublic-0.5/conf.py 0000664 0001750 0001750 00000016577 12714117013 014757 0 ustar barry barry 0000000 0000000 # -*- coding: utf-8 -*-
#
# This file is execfile()d with the current directory set to its containing
# dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.graphviz']
# Add any paths that contain templates here, relative to this directory.
# templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'README'
# General information about the project.
project = u'public'
copyright = u'2016 by Barry Warsaw'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
from public import __version__ as version
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build', 'eggs', '.tox']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'public'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'public.tex', u'public Documentation',
u'Barry Warsaw', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'public', u'public Documentation',
[u'Barry Warsaw'], 1)
]
def index_html():
import errno
cwd = os.getcwd()
try:
try:
os.makedirs('build/sphinx/html')
except OSError as error:
if error.errno != errno.EEXIST:
raise
os.chdir('build/sphinx/html')
try:
os.symlink('README.html', 'index.html')
print('index.html -> README.html')
except OSError as error:
if error.errno != errno.EEXIST:
raise
finally:
os.chdir(cwd)
import atexit
atexit.register(index_html)