atpublic-0.5/0000775000175000017500000000000013024254112013435 5ustar barrybarry00000000000000atpublic-0.5/setup.py0000664000175000017500000000357713024252350015165 0ustar barrybarry00000000000000# 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.cfg0000664000175000017500000000007313024254112015256 0ustar barrybarry00000000000000[egg_info] tag_date = 0 tag_build = tag_svn_revision = 0 atpublic-0.5/NEWS.rst0000664000175000017500000000110313024254057014746 0ustar barrybarry00000000000000============== @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.in0000664000175000017500000000024513024254057015204 0ustar barrybarry00000000000000include *.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/0000775000175000017500000000000013024254112016732 5ustar barrybarry00000000000000atpublic-0.5/atpublic.egg-info/SOURCES.txt0000664000175000017500000000051013024254112020612 0ustar barrybarry00000000000000LICENSE.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.catpublic-0.5/atpublic.egg-info/PKG-INFO0000664000175000017500000000174313024254112020034 0ustar barrybarry00000000000000Metadata-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.txt0000664000175000017500000000000113024254112023000 0ustar barrybarry00000000000000 atpublic-0.5/atpublic.egg-info/top_level.txt0000664000175000017500000000000713024254112021461 0ustar barrybarry00000000000000public atpublic-0.5/setup_helpers.py0000664000175000017500000001210612714113364016701 0ustar barrybarry00000000000000# 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/0000775000175000017500000000000013024254112014224 5ustar barrybarry00000000000000atpublic-0.5/src/public.c0000664000175000017500000001061013024252350015646 0ustar barrybarry00000000000000/* 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-INFO0000664000175000017500000000174313024254112014537 0ustar barrybarry00000000000000Metadata-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.rst0000664000175000017500000002016513024252350015132 0ustar barrybarry00000000000000========= @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/0000775000175000017500000000000013024254112014713 5ustar barrybarry00000000000000atpublic-0.5/public/__init__.py0000664000175000017500000000150213024254057017032 0ustar barrybarry00000000000000# 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.py0000664000175000017500000000135313024252350016547 0ustar barrybarry00000000000000# 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/0000775000175000017500000000000013024254112016055 5ustar barrybarry00000000000000atpublic-0.5/public/tests/__init__.py0000664000175000017500000000000012714122424020161 0ustar barrybarry00000000000000atpublic-0.5/public/tests/test_public.py0000664000175000017500000001265413024252350020756 0ustar barrybarry00000000000000# 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.ini0000664000175000017500000000252313024252350014754 0ustar barrybarry00000000000000# 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.txt0000664000175000017500000000104712714113364015272 0ustar barrybarry00000000000000Copyright 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.ini0000664000175000017500000000030512720410343015731 0ustar barrybarry00000000000000[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.py0000664000175000017500000001657712714117013014757 0ustar barrybarry00000000000000# -*- 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)