apipkg-1.5/0000775000175000017500000000000013317330566014311 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/conftest.py0000664000175000017500000000053313261611321016476 0ustar rpfannscrpfannsc00000000000000import py import apipkg LOCAL_APIPKG = py.path.local(__file__).dirpath().join('src/apipkg/__init__.py') INSTALL_TYPE = 'editable' if apipkg.__file__ == LOCAL_APIPKG else 'full' def pytest_report_header(startdir): return "apipkg {install_type} install version={version}".format( install_type=INSTALL_TYPE, version=apipkg.__version__) apipkg-1.5/.travis.yml0000664000175000017500000000064713261611321016416 0ustar rpfannscrpfannsc00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.5-dev" # 3.5 development branch - "3.6" - "3.6-dev" # 3.6 development branch - "3.7-dev" # 3.7 development branch - "nightly" # currently points to 3.7-dev # command to install dependencies env: - EDITABLE=-e - EDITABLE= install: - pip install -U setuptools pip setuptools_scm pytest - pip install $EDITABLE . # command to run tests script: pytest apipkg-1.5/setup.py0000664000175000017500000000353613317330507016025 0ustar rpfannscrpfannsc00000000000000from setuptools import setup, find_packages def readme(): with open('README.rst') as fp: return fp.read() def main(): setup( name='apipkg', description='apipkg: namespace control and lazy-import mechanism', long_description=readme(), python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', setup_requires=[ 'setuptools_scm', 'setuptools>=30.3.0', # introduced setup.cfg metadata ], use_scm_version={ 'write_to': 'src/apipkg/version.py' }, url='https://github.com/pytest-dev/apipkg', license='MIT License', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', maintainer="Ronny Pfannschmidt", maintainer_email="opensource@ronnypfannschmidt.de", classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Topic :: Software Development :: Libraries', # Specify the Python versions you support here. # In particular, ensure that you indicate whether # you support Python 2, Python 3 or both. 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], packages=find_packages('src'), package_dir={'': 'src'}, ) if __name__ == '__main__': main() apipkg-1.5/setup.cfg0000664000175000017500000000010013317330566016121 0ustar rpfannscrpfannsc00000000000000[wheel] universal = true [egg_info] tag_build = tag_date = 0 apipkg-1.5/MANIFEST.in0000664000175000017500000000011313261611321016027 0ustar rpfannscrpfannsc00000000000000include tox.ini include setup.cfg include CHANGELOG include test_apipkg.py apipkg-1.5/.gitignore0000664000175000017500000000007313261611321016266 0ustar rpfannscrpfannsc00000000000000src/apipkg/version.py __pycache__ *.egg-info .cache/ .eggs/apipkg-1.5/tox.ini0000664000175000017500000000046413261611321015615 0ustar rpfannscrpfannsc00000000000000[tox] envlist=py27,py34,py35,py36,jython,flakes [tox:hudson] sdistsrc={distshare}/apipkg-* [testenv] deps=pytest commands=py.test [] [testenv:jython] deps=pytest commands=py.test-jython [] [testenv:flakes] deps=flake8 commands=flake8 [flake8] exclude=.tox/,.env/,dist/,build/,example/ max_complexity=11 apipkg-1.5/README.rst0000664000175000017500000000522313261611321015767 0ustar rpfannscrpfannsc00000000000000Welcome to apipkg! ------------------------ With apipkg you can control the exported namespace of a Python package and greatly reduce the number of imports for your users. It is a `small pure Python module`_ that works on CPython 2.7 and 3.4+, Jython and PyPy. It cooperates well with Python's ``help()`` system, custom importers (PEP302) and common command-line completion tools. Usage is very simple: you can require 'apipkg' as a dependency or you can copy paste the ~200 lines of code into your project. Tutorial example ------------------- Here is a simple ``mypkg`` package that specifies one namespace and exports two objects imported from different modules:: # mypkg/__init__.py import apipkg apipkg.initpkg(__name__, { 'path': { 'Class1': "_mypkg.somemodule:Class1", 'clsattr': "_mypkg.othermodule:Class2.attr", } } The package is initialized with a dictionary as namespace. You need to create a ``_mypkg`` package with a ``somemodule.py`` and ``othermodule.py`` containing the respective classes. The ``_mypkg`` is not special - it's a completely regular Python package. Namespace dictionaries contain ``name: value`` mappings where the value may be another namespace dictionary or a string specifying an import location. On accessing an namespace attribute an import will be performed:: >>> import mypkg >>> mypkg.path >>> mypkg.path.Class1 # '_mypkg.somemodule' gets imported now >>> mypkg.path.clsattr # '_mypkg.othermodule' gets imported now 4 # the value of _mypkg.othermodule.Class2.attr The ``mypkg.path`` namespace and its two entries are loaded when they are accessed. This means: * lazy loading - only what is actually needed is ever loaded * only the root "mypkg" ever needs to be imported to get access to the complete functionality * the underlying modules are also accessible, for example:: from mypkg.sub import Class1 Including apipkg in your package -------------------------------------- If you don't want to add an ``apipkg`` dependency to your package you can copy the `apipkg.py`_ file somewhere to your own package, for example ``_mypkg/apipkg.py`` in the above example. You then import the ``initpkg`` function from that new place and are good to go. .. _`small pure Python module`: .. _`apipkg.py`: https://github.com/pytest-dev/apipkg/blob/master/src/apipkg/__init__.py Feedback? ----------------------- If you have questions you are welcome to * join the #pylib channel on irc.freenode.net * create an issue on https://github.com/pytest-dev/apipkg/issues have fun, holger krekel apipkg-1.5/PKG-INFO0000664000175000017500000001057313317330566015414 0ustar rpfannscrpfannsc00000000000000Metadata-Version: 1.2 Name: apipkg Version: 1.5 Summary: apipkg: namespace control and lazy-import mechanism Home-page: https://github.com/pytest-dev/apipkg Author: holger krekel Maintainer: Ronny Pfannschmidt Maintainer-email: opensource@ronnypfannschmidt.de License: MIT License Description: Welcome to apipkg! ------------------------ With apipkg you can control the exported namespace of a Python package and greatly reduce the number of imports for your users. It is a `small pure Python module`_ that works on CPython 2.7 and 3.4+, Jython and PyPy. It cooperates well with Python's ``help()`` system, custom importers (PEP302) and common command-line completion tools. Usage is very simple: you can require 'apipkg' as a dependency or you can copy paste the ~200 lines of code into your project. Tutorial example ------------------- Here is a simple ``mypkg`` package that specifies one namespace and exports two objects imported from different modules:: # mypkg/__init__.py import apipkg apipkg.initpkg(__name__, { 'path': { 'Class1': "_mypkg.somemodule:Class1", 'clsattr': "_mypkg.othermodule:Class2.attr", } } The package is initialized with a dictionary as namespace. You need to create a ``_mypkg`` package with a ``somemodule.py`` and ``othermodule.py`` containing the respective classes. The ``_mypkg`` is not special - it's a completely regular Python package. Namespace dictionaries contain ``name: value`` mappings where the value may be another namespace dictionary or a string specifying an import location. On accessing an namespace attribute an import will be performed:: >>> import mypkg >>> mypkg.path >>> mypkg.path.Class1 # '_mypkg.somemodule' gets imported now >>> mypkg.path.clsattr # '_mypkg.othermodule' gets imported now 4 # the value of _mypkg.othermodule.Class2.attr The ``mypkg.path`` namespace and its two entries are loaded when they are accessed. This means: * lazy loading - only what is actually needed is ever loaded * only the root "mypkg" ever needs to be imported to get access to the complete functionality * the underlying modules are also accessible, for example:: from mypkg.sub import Class1 Including apipkg in your package -------------------------------------- If you don't want to add an ``apipkg`` dependency to your package you can copy the `apipkg.py`_ file somewhere to your own package, for example ``_mypkg/apipkg.py`` in the above example. You then import the ``initpkg`` function from that new place and are good to go. .. _`small pure Python module`: .. _`apipkg.py`: https://github.com/pytest-dev/apipkg/blob/master/src/apipkg/__init__.py Feedback? ----------------------- If you have questions you are welcome to * join the #pylib channel on irc.freenode.net * create an issue on https://github.com/pytest-dev/apipkg/issues have fun, holger krekel Platform: unix Platform: linux Platform: osx Platform: cygwin Platform: win32 Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Libraries Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* apipkg-1.5/LICENSE0000664000175000017500000000204513261611321015304 0ustar rpfannscrpfannsc00000000000000 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. apipkg-1.5/src/0000775000175000017500000000000013317330566015100 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/src/apipkg.egg-info/0000775000175000017500000000000013317330566020045 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/src/apipkg.egg-info/SOURCES.txt0000664000175000017500000000145513317330565021735 0ustar rpfannscrpfannsc00000000000000.gitignore .travis.yml CHANGELOG LICENSE MANIFEST.in README.rst conftest.py setup.cfg setup.py test_apipkg.py tox.ini ./.gitignore ./.travis.yml ./CHANGELOG ./LICENSE ./MANIFEST.in ./README.rst ./conftest.py ./setup.cfg ./setup.py ./test_apipkg.py ./tox.ini ./example/_mypkg/__init__.py ./example/_mypkg/othermodule.py ./example/_mypkg/somemodule.py ./example/mypkg/__init__.py ./example/mypkg/othermodule.py ./example/mypkg/somemodule.py ./src/apipkg/__init__.py example/_mypkg/__init__.py example/_mypkg/othermodule.py example/_mypkg/somemodule.py example/mypkg/__init__.py example/mypkg/othermodule.py example/mypkg/somemodule.py src/apipkg/__init__.py src/apipkg/version.py src/apipkg.egg-info/PKG-INFO src/apipkg.egg-info/SOURCES.txt src/apipkg.egg-info/dependency_links.txt src/apipkg.egg-info/top_level.txtapipkg-1.5/src/apipkg.egg-info/PKG-INFO0000664000175000017500000001057313317330565021147 0ustar rpfannscrpfannsc00000000000000Metadata-Version: 1.2 Name: apipkg Version: 1.5 Summary: apipkg: namespace control and lazy-import mechanism Home-page: https://github.com/pytest-dev/apipkg Author: holger krekel Maintainer: Ronny Pfannschmidt Maintainer-email: opensource@ronnypfannschmidt.de License: MIT License Description: Welcome to apipkg! ------------------------ With apipkg you can control the exported namespace of a Python package and greatly reduce the number of imports for your users. It is a `small pure Python module`_ that works on CPython 2.7 and 3.4+, Jython and PyPy. It cooperates well with Python's ``help()`` system, custom importers (PEP302) and common command-line completion tools. Usage is very simple: you can require 'apipkg' as a dependency or you can copy paste the ~200 lines of code into your project. Tutorial example ------------------- Here is a simple ``mypkg`` package that specifies one namespace and exports two objects imported from different modules:: # mypkg/__init__.py import apipkg apipkg.initpkg(__name__, { 'path': { 'Class1': "_mypkg.somemodule:Class1", 'clsattr': "_mypkg.othermodule:Class2.attr", } } The package is initialized with a dictionary as namespace. You need to create a ``_mypkg`` package with a ``somemodule.py`` and ``othermodule.py`` containing the respective classes. The ``_mypkg`` is not special - it's a completely regular Python package. Namespace dictionaries contain ``name: value`` mappings where the value may be another namespace dictionary or a string specifying an import location. On accessing an namespace attribute an import will be performed:: >>> import mypkg >>> mypkg.path >>> mypkg.path.Class1 # '_mypkg.somemodule' gets imported now >>> mypkg.path.clsattr # '_mypkg.othermodule' gets imported now 4 # the value of _mypkg.othermodule.Class2.attr The ``mypkg.path`` namespace and its two entries are loaded when they are accessed. This means: * lazy loading - only what is actually needed is ever loaded * only the root "mypkg" ever needs to be imported to get access to the complete functionality * the underlying modules are also accessible, for example:: from mypkg.sub import Class1 Including apipkg in your package -------------------------------------- If you don't want to add an ``apipkg`` dependency to your package you can copy the `apipkg.py`_ file somewhere to your own package, for example ``_mypkg/apipkg.py`` in the above example. You then import the ``initpkg`` function from that new place and are good to go. .. _`small pure Python module`: .. _`apipkg.py`: https://github.com/pytest-dev/apipkg/blob/master/src/apipkg/__init__.py Feedback? ----------------------- If you have questions you are welcome to * join the #pylib channel on irc.freenode.net * create an issue on https://github.com/pytest-dev/apipkg/issues have fun, holger krekel Platform: unix Platform: linux Platform: osx Platform: cygwin Platform: win32 Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Libraries Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* apipkg-1.5/src/apipkg.egg-info/dependency_links.txt0000664000175000017500000000000113317330565024112 0ustar rpfannscrpfannsc00000000000000 apipkg-1.5/src/apipkg.egg-info/top_level.txt0000664000175000017500000000000713317330565022573 0ustar rpfannscrpfannsc00000000000000apipkg apipkg-1.5/src/apipkg/0000775000175000017500000000000013317330566016353 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/src/apipkg/version.py0000664000175000017500000000016213317330565020410 0ustar rpfannscrpfannsc00000000000000# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control version = '1.5' apipkg-1.5/src/apipkg/__init__.py0000664000175000017500000001506313317326170020465 0ustar rpfannscrpfannsc00000000000000""" apipkg: control the exported namespace of a Python package. see https://pypi.python.org/pypi/apipkg (c) holger krekel, 2009 - MIT license """ import os import sys from types import ModuleType from .version import version as __version__ def _py_abspath(path): """ special version of abspath that will leave paths from jython jars alone """ if path.startswith('__pyclasspath__'): return path else: return os.path.abspath(path) def distribution_version(name): """try to get the version of the named distribution, returs None on failure""" from pkg_resources import get_distribution, DistributionNotFound try: dist = get_distribution(name) except DistributionNotFound: pass else: return dist.version def initpkg(pkgname, exportdefs, attr=None, eager=False): """ initialize given package from the export definitions. """ attr = attr or {} oldmod = sys.modules.get(pkgname) d = {} f = getattr(oldmod, '__file__', None) if f: f = _py_abspath(f) d['__file__'] = f if hasattr(oldmod, '__version__'): d['__version__'] = oldmod.__version__ if hasattr(oldmod, '__loader__'): d['__loader__'] = oldmod.__loader__ if hasattr(oldmod, '__path__'): d['__path__'] = [_py_abspath(p) for p in oldmod.__path__] if hasattr(oldmod, '__package__'): d['__package__'] = oldmod.__package__ if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None): d['__doc__'] = oldmod.__doc__ d.update(attr) if hasattr(oldmod, "__dict__"): oldmod.__dict__.update(d) mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d) sys.modules[pkgname] = mod # eagerload in bypthon to avoid their monkeypatching breaking packages if 'bpython' in sys.modules or eager: for module in list(sys.modules.values()): if isinstance(module, ApiModule): module.__dict__ def importobj(modpath, attrname): """imports a module, then resolves the attrname on it""" module = __import__(modpath, None, None, ['__doc__']) if not attrname: return module retval = module names = attrname.split(".") for x in names: retval = getattr(retval, x) return retval class ApiModule(ModuleType): """the magical lazy-loading module standing""" def __docget(self): try: return self.__doc except AttributeError: if '__doc__' in self.__map__: return self.__makeattr('__doc__') def __docset(self, value): self.__doc = value __doc__ = property(__docget, __docset) def __init__(self, name, importspec, implprefix=None, attr=None): self.__name__ = name self.__all__ = [x for x in importspec if x != '__onfirstaccess__'] self.__map__ = {} self.__implprefix__ = implprefix or name if attr: for name, val in attr.items(): # print "setting", self.__name__, name, val setattr(self, name, val) for name, importspec in importspec.items(): if isinstance(importspec, dict): subname = '%s.%s' % (self.__name__, name) apimod = ApiModule(subname, importspec, implprefix) sys.modules[subname] = apimod setattr(self, name, apimod) else: parts = importspec.split(':') modpath = parts.pop(0) attrname = parts and parts[0] or "" if modpath[0] == '.': modpath = implprefix + modpath if not attrname: subname = '%s.%s' % (self.__name__, name) apimod = AliasModule(subname, modpath) sys.modules[subname] = apimod if '.' not in name: setattr(self, name, apimod) else: self.__map__[name] = (modpath, attrname) def __repr__(self): repr_list = [] if hasattr(self, '__version__'): repr_list.append("version=" + repr(self.__version__)) if hasattr(self, '__file__'): repr_list.append('from ' + repr(self.__file__)) if repr_list: return '' % (self.__name__, " ".join(repr_list)) return '' % (self.__name__,) def __makeattr(self, name): """lazily compute value for name or raise AttributeError if unknown.""" # print "makeattr", self.__name__, name target = None if '__onfirstaccess__' in self.__map__: target = self.__map__.pop('__onfirstaccess__') importobj(*target)() try: modpath, attrname = self.__map__[name] except KeyError: if target is not None and name != '__onfirstaccess__': # retry, onfirstaccess might have set attrs return getattr(self, name) raise AttributeError(name) else: result = importobj(modpath, attrname) setattr(self, name, result) try: del self.__map__[name] except KeyError: pass # in a recursive-import situation a double-del can happen return result __getattr__ = __makeattr @property def __dict__(self): # force all the content of the module # to be loaded when __dict__ is read dictdescr = ModuleType.__dict__['__dict__'] dict = dictdescr.__get__(self) if dict is not None: hasattr(self, 'some') for name in self.__all__: try: self.__makeattr(name) except AttributeError: pass return dict def AliasModule(modname, modpath, attrname=None): mod = [] def getmod(): if not mod: x = importobj(modpath, None) if attrname is not None: x = getattr(x, attrname) mod.append(x) return mod[0] class AliasModule(ModuleType): def __repr__(self): x = modpath if attrname: x += "." + attrname return '' % (modname, x) def __getattribute__(self, name): try: return getattr(getmod(), name) except ImportError: return None def __setattr__(self, name, value): setattr(getmod(), name, value) def __delattr__(self, name): delattr(getmod(), name) return AliasModule(str(modname)) apipkg-1.5/example/0000775000175000017500000000000013317330566015744 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/example/_mypkg/0000775000175000017500000000000013317330566017232 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/example/_mypkg/somemodule.py0000664000175000017500000000010613261611321021737 0ustar rpfannscrpfannsc00000000000000 from _mypkg.othermodule import OtherClass class SomeClass: pass apipkg-1.5/example/_mypkg/othermodule.py0000664000175000017500000000003413261611321022115 0ustar rpfannscrpfannsc00000000000000 class OtherClass: pass apipkg-1.5/example/_mypkg/__init__.py0000664000175000017500000000000013261611321021316 0ustar rpfannscrpfannsc00000000000000apipkg-1.5/example/mypkg/0000775000175000017500000000000013317330566017073 5ustar rpfannscrpfannsc00000000000000apipkg-1.5/example/mypkg/somemodule.py0000664000175000017500000000010713261611321021601 0ustar rpfannscrpfannsc00000000000000 from __mypkg.othermodule import OtherClass class SomeClass: pass apipkg-1.5/example/mypkg/othermodule.py0000664000175000017500000000003413261611321021756 0ustar rpfannscrpfannsc00000000000000 class OtherClass: pass apipkg-1.5/example/mypkg/__init__.py0000664000175000017500000000027213261611321021172 0ustar rpfannscrpfannsc00000000000000# mypkg/__init__.py import apipkg apipkg.initpkg(__name__, { 'SomeClass': '_mypkg.somemodule:SomeClass', 'sub': { 'OtherClass': '_mypkg.somemodule:OtherClass', } }) apipkg-1.5/test_apipkg.py0000664000175000017500000004153413317326170017200 0ustar rpfannscrpfannsc00000000000000import types import sys import py import apipkg import subprocess import pytest # # test support for importing modules # ModuleType = types.ModuleType class TestRealModule: def setup_class(cls): cls.tmpdir = py.test.ensuretemp('test_apipkg') sys.path = [str(cls.tmpdir)] + sys.path pkgdir = cls.tmpdir.ensure('realtest', dir=1) tfile = pkgdir.join('__init__.py') tfile.write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, { 'x': { 'module': { '__doc__': '_xyz.testmodule:__doc__', 'mytest0': '_xyz.testmodule:mytest0', 'mytest1': '_xyz.testmodule:mytest1', 'MyTest': '_xyz.testmodule:MyTest', } } } ) """)) ipkgdir = cls.tmpdir.ensure("_xyz", dir=1) tfile = ipkgdir.join('testmodule.py') ipkgdir.ensure("__init__.py") tfile.write(py.code.Source(""" 'test module' from _xyz.othermodule import MyTest #__all__ = ['mytest0', 'mytest1', 'MyTest'] def mytest0(): pass def mytest1(): pass """)) ipkgdir.join("othermodule.py").write("class MyTest: pass") def setup_method(self, *args): # Unload the test modules before each test. module_names = ['realtest', 'realtest.x', 'realtest.x.module'] for modname in module_names: if modname in sys.modules: del sys.modules[modname] def test_realmodule(self): import realtest.x assert 'realtest.x.module' in sys.modules assert getattr(realtest.x.module, 'mytest0') def test_realmodule_repr(self): import realtest.x assert "" == repr(realtest.x) def test_realmodule_from(self): from realtest.x import module assert getattr(module, 'mytest1') def test_realmodule__all__(self): import realtest.x.module assert realtest.x.__all__ == ['module'] assert len(realtest.x.module.__all__) == 4 def test_realmodule_dict_import(self): "Test verifying that accessing the __dict__ invokes the import" import realtest.x.module moddict = realtest.x.module.__dict__ assert 'mytest0' in moddict assert 'mytest1' in moddict assert 'MyTest' in moddict def test_realmodule___doc__(self): """test whether the __doc__ attribute is set properly from initpkg""" import realtest.x.module print(realtest.x.module.__map__) assert realtest.x.module.__doc__ == 'test module' class TestScenarios: def test_relative_import(self, monkeypatch, tmpdir): pkgdir = tmpdir.mkdir("mymodule") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ '__doc__': '.submod:maindoc', 'x': '.submod:x', 'y': { 'z': '.submod:x' }, }) """)) pkgdir.join('submod.py').write("x=3\nmaindoc='hello'") monkeypatch.syspath_prepend(tmpdir) import mymodule assert isinstance(mymodule, apipkg.ApiModule) assert mymodule.x == 3 assert mymodule.__doc__ == 'hello' assert mymodule.y.z == 3 def test_recursive_import(self, monkeypatch, tmpdir): pkgdir = tmpdir.mkdir("recmodule") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ 'some': '.submod:someclass', }) """)) pkgdir.join('submod.py').write(py.code.Source(""" import recmodule class someclass: pass print(recmodule.__dict__) """)) monkeypatch.syspath_prepend(tmpdir) import recmodule assert isinstance(recmodule, apipkg.ApiModule) assert recmodule.some.__name__ == "someclass" def test_module_alias_import(self, monkeypatch, tmpdir): pkgdir = tmpdir.mkdir("aliasimport") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ 'some': 'os.path', }) """)) monkeypatch.syspath_prepend(tmpdir) import aliasimport for k, v in py.std.os.path.__dict__.items(): assert getattr(aliasimport.some, k) == v def test_from_module_alias_import(self, monkeypatch, tmpdir): pkgdir = tmpdir.mkdir("fromaliasimport") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ 'some': 'os.path', }) """)) monkeypatch.syspath_prepend(tmpdir) from fromaliasimport.some import join assert join is py.std.os.path.join def xtest_nested_absolute_imports(): apipkg.ApiModule('email', { 'message2': { 'Message': 'email.message:Message', }, }) # nesting is supposed to put nested items into sys.modules assert 'email.message2' in sys.modules # alternate ideas for specifying package + preliminary code # def test_parsenamespace(): spec = """ path.local __.path.local::LocalPath path.svnwc __.path.svnwc::WCCommandPath test.raises __.test.outcome::raises """ d = parsenamespace(spec) print(d) assert d == { 'test': {'raises': '__.test.outcome::raises'}, 'path': { 'svnwc': '__.path.svnwc::WCCommandPath', 'local': '__.path.local::LocalPath'} } def xtest_parsenamespace_errors(): py.test.raises(ValueError, """ parsenamespace('path.local xyz') """) py.test.raises(ValueError, """ parsenamespace('x y z') """) def parsenamespace(spec): ns = {} for line in spec.split("\n"): line = line.strip() if not line or line[0] == "#": continue parts = [x.strip() for x in line.split()] if len(parts) != 2: raise ValueError("Wrong format: %r" % (line,)) apiname, spec = parts if not spec.startswith("__"): raise ValueError("%r does not start with __" % (spec,)) apinames = apiname.split(".") cur = ns for name in apinames[:-1]: cur.setdefault(name, {}) cur = cur[name] cur[apinames[-1]] = spec return ns def test_initpkg_replaces_sysmodules(monkeypatch): mod = ModuleType('hello') monkeypatch.setitem(sys.modules, 'hello', mod) apipkg.initpkg('hello', {'x': 'os.path:abspath'}) newmod = sys.modules['hello'] assert newmod != mod assert newmod.x == py.std.os.path.abspath def test_initpkg_transfers_attrs(monkeypatch): mod = ModuleType('hello') mod.__version__ = 10 mod.__file__ = "hello.py" mod.__loader__ = "loader" mod.__package__ = "package" mod.__doc__ = "this is the documentation" monkeypatch.setitem(sys.modules, 'hello', mod) apipkg.initpkg('hello', {}) newmod = sys.modules['hello'] assert newmod != mod assert newmod.__file__ == py.path.local(mod.__file__) assert newmod.__version__ == mod.__version__ assert newmod.__loader__ == mod.__loader__ assert newmod.__package__ == mod.__package__ assert newmod.__doc__ == mod.__doc__ def test_initpkg_nodoc(monkeypatch): mod = ModuleType('hello') mod.__file__ = "hello.py" monkeypatch.setitem(sys.modules, 'hello', mod) apipkg.initpkg('hello', {}) newmod = sys.modules['hello'] assert not newmod.__doc__ def test_initpkg_overwrite_doc(monkeypatch): hello = ModuleType('hello') hello.__doc__ = "this is the documentation" monkeypatch.setitem(sys.modules, 'hello', hello) apipkg.initpkg('hello', {"__doc__": "sys:__doc__"}) newhello = sys.modules['hello'] assert newhello != hello assert newhello.__doc__ == sys.__doc__ def test_initpkg_not_transfers_not_existing_attrs(monkeypatch): mod = ModuleType('hello') mod.__file__ = "hello.py" assert not hasattr(mod, '__path__') assert not hasattr(mod, '__package__') or mod.__package__ is None monkeypatch.setitem(sys.modules, 'hello', mod) apipkg.initpkg('hello', {}) newmod = sys.modules['hello'] assert newmod != mod assert newmod.__file__ == py.path.local(mod.__file__) assert not hasattr(newmod, '__path__') assert not hasattr(newmod, '__package__') or mod.__package__ is None def test_initpkg_not_changing_jython_paths(monkeypatch): mod = ModuleType('hello') mod.__file__ = '__pyclasspath__/test.py' mod.__path__ = ['__pyclasspath__/fun', 'ichange'] monkeypatch.setitem(sys.modules, 'hello', mod) apipkg.initpkg('hello', {}) newmod = sys.modules['hello'] assert newmod != mod assert newmod.__file__.startswith('__pyclasspath__') unchanged, changed = newmod.__path__ assert changed != 'ichange' assert unchanged.startswith('__pyclasspath__') def test_initpkg_defaults(monkeypatch): mod = ModuleType('hello') monkeypatch.setitem(sys.modules, 'hello', mod) apipkg.initpkg('hello', {}) newmod = sys.modules['hello'] assert newmod.__file__ is None assert not hasattr(newmod, '__version__') def test_name_attribute(): api = apipkg.ApiModule('name_test', { 'subpkg': {}, }) assert api.__name__ == 'name_test' assert api.subpkg.__name__ == 'name_test.subpkg' def test_error_loading_one_element(monkeypatch, tmpdir): pkgdir = tmpdir.mkdir("errorloading1") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ 'x': '.notexists:x', 'y': '.submod:y' }, ) """)) pkgdir.join('submod.py').write("y=0") monkeypatch.syspath_prepend(tmpdir) import errorloading1 assert isinstance(errorloading1, apipkg.ApiModule) assert errorloading1.y == 0 py.test.raises(ImportError, 'errorloading1.x') py.test.raises(ImportError, 'errorloading1.x') def test_onfirstaccess(tmpdir, monkeypatch): pkgdir = tmpdir.mkdir("firstaccess") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ '__onfirstaccess__': '.submod:init', 'l': '.submod:l', }, ) """)) pkgdir.join('submod.py').write(py.code.Source(""" l = [] def init(): l.append(1) """)) monkeypatch.syspath_prepend(tmpdir) import firstaccess assert isinstance(firstaccess, apipkg.ApiModule) assert len(firstaccess.l) == 1 assert len(firstaccess.l) == 1 assert '__onfirstaccess__' not in firstaccess.__all__ @pytest.mark.parametrize('mode', ['attr', 'dict', 'onfirst']) def test_onfirstaccess_setsnewattr(tmpdir, monkeypatch, mode): pkgname = 'mode_' + mode pkgdir = tmpdir.mkdir(pkgname) pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, exportdefs={ '__onfirstaccess__': '.submod:init', }, ) """)) pkgdir.join('submod.py').write(py.code.Source(""" def init(): import %s as pkg pkg.newattr = 42 """ % pkgname)) monkeypatch.syspath_prepend(tmpdir) mod = __import__(pkgname) assert isinstance(mod, apipkg.ApiModule) if mode == 'attr': assert mod.newattr == 42 elif mode == "dict": print(list(mod.__dict__.keys())) assert 'newattr' in mod.__dict__ elif mode == "onfirst": assert not hasattr(mod, '__onfirstaccess__') assert not hasattr(mod, '__onfirstaccess__') assert '__onfirstaccess__' not in vars(mod) def test_bpython_getattr_override(tmpdir, monkeypatch): def patchgetattr(self, name): raise AttributeError(name) monkeypatch.setattr(apipkg.ApiModule, '__getattr__', patchgetattr) api = apipkg.ApiModule('bpy', { 'abspath': 'os.path:abspath', }) d = api.__dict__ assert 'abspath' in d def test_chdir_with_relative_imports_support_lazy_loading(tmpdir, monkeypatch): monkeypatch.syspath_prepend(py.path.local('src')) pkg = tmpdir.mkdir('pkg') tmpdir.mkdir('messy') pkg.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, { 'test': '.sub:test', }) """)) pkg.join('sub.py').write('def test(): pass') tmpdir.join('main.py').write(py.code.Source(""" import os import sys sys.path.insert(0, '') import pkg import py print(py.__file__) py.builtin.print_(pkg.__path__, file=sys.stderr) py.builtin.print_(pkg.__file__, file=sys.stderr) py.builtin.print_(pkg, file=sys.stderr) os.chdir('messy') pkg.test() assert os.path.isabs(pkg.sub.__file__), pkg.sub.__file__ """)) res = subprocess.call( [py.std.sys.executable, 'main.py'], cwd=str(tmpdir), ) assert res == 0 def test_dotted_name_lookup(tmpdir, monkeypatch): pkgdir = tmpdir.mkdir("dotted_name_lookup") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, dict(abs='os:path.abspath')) """)) monkeypatch.syspath_prepend(tmpdir) import dotted_name_lookup assert dotted_name_lookup.abs == py.std.os.path.abspath def test_extra_attributes(tmpdir, monkeypatch): pkgdir = tmpdir.mkdir("extra_attributes") pkgdir.join('__init__.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, dict(abs='os:path.abspath'), dict(foo='bar')) """)) monkeypatch.syspath_prepend(tmpdir) import extra_attributes assert extra_attributes.foo == 'bar' def test_aliasmodule_aliases_an_attribute(): am = apipkg.AliasModule("mymod", "pprint", 'PrettyPrinter') r = repr(am) assert "" == r assert am.format assert not hasattr(am, "lqkje") def test_aliasmodule_aliases_unimportable(): am = apipkg.AliasModule("mymod", "qlwkejqlwe", 'main') r = repr(am) assert "" == r assert am.qwe is None def test_aliasmodule_unicode(): am = apipkg.AliasModule(py.builtin._totext("mymod"), "pprint") assert am def test_aliasmodule_repr(): am = apipkg.AliasModule("mymod", "sys") r = repr(am) assert "" == r am.version assert repr(am) == r def test_aliasmodule_proxy_methods(tmpdir, monkeypatch): pkgdir = tmpdir pkgdir.join('aliasmodule_proxy.py').write(py.code.Source(""" def doit(): return 42 """)) pkgdir.join('my_aliasmodule_proxy.py').write(py.code.Source(""" import apipkg apipkg.initpkg(__name__, dict(proxy='aliasmodule_proxy')) def doit(): return 42 """)) monkeypatch.syspath_prepend(tmpdir) import aliasmodule_proxy as orig from my_aliasmodule_proxy import proxy doit = proxy.doit assert doit is orig.doit del proxy.doit py.test.raises(AttributeError, "orig.doit") proxy.doit = doit assert orig.doit is doit def test_aliasmodule_nested_import_with_from(tmpdir, monkeypatch): import os pkgdir = tmpdir.mkdir("api1") pkgdir.ensure("__init__.py").write(py.std.textwrap.dedent(""" import apipkg apipkg.initpkg(__name__, { 'os2': 'api2', 'os2.path': 'api2.path2', }) """)) tmpdir.join("api2.py").write(py.std.textwrap.dedent(""" import os, sys from os import path sys.modules['api2.path2'] = path x = 3 """)) monkeypatch.syspath_prepend(tmpdir) from api1 import os2 from api1.os2.path import abspath assert abspath == os.path.abspath # check that api1.os2 mirrors os.* assert os2.x == 3 import api1 assert 'os2.path' not in api1.__dict__ def test_initpkg_without_old_module(): apipkg.initpkg("initpkg_without_old_module", dict(modules="sys:modules")) from initpkg_without_old_module import modules assert modules is sys.modules def test_get_distribution_version(): assert apipkg.distribution_version('setuptools') is not None assert apipkg.distribution_version('email') is None assert apipkg.distribution_version('py') is not None def test_eagerload_on_bython(monkeypatch): monkeypatch.delitem(sys.modules, 'bpython', raising=False) apipkg.initpkg( 'apipkg.testmodule.example.lazy', {'test': 'apipkg.does_not_exist'}) monkeypatch.setitem(sys.modules, 'bpython', True) with pytest.raises(ImportError): apipkg.initpkg( 'apipkg.testmodule.example.lazy', {'test': 'apipkg.does_not_exist'}) apipkg-1.5/CHANGELOG0000664000175000017500000000422113317327421015516 0ustar rpfannscrpfannsc000000000000001.5 ---- - switch to setuptools_scm - move to github - fix up python compat matrix - avoid dict iteration (fixes issue on python3) - preserve __package__ - ths gets us better pep 302 compliance 1.4 ----------------- - revert the automated version gathering 1.3 ---------------------------------------- - fix issue2 - adapt tests on Jython - handle jython __pkgpath__ missabstraction when running python from jar files - alias modules pointing to unimportable modules will return None for all their attributes instead of raising ImportError. This addresses python3.4 where any call to getframeinfo() can choke on sys.modules contents if pytest is not installed (because py.test.* imports it). - introduce apipkg.distribution_version(name) as helper to obtain the current version number of a package from install metadata its used by default with the package name - add an eagerloading option and eagerload automatically if bpython is used (workaround for their monkeypatching) 1.2 ---------------------------------------- - Allow to import from Aliasmodules (thanks Ralf Schmitt) - avoid loading __doc__ early, so ApiModule is now fully lazy 1.1 ---------------------------------------- - copy __doc__ and introduce a new argument to initpkg() (thanks Ralf Schmitt) - don't use a "0" default for __version__ 1.0 ---------------------------------------- - make apipkg memorize the absolute path where a package starts importing so that subsequent chdir + imports won't break. - allow to alias modules - allow to use dotted names in alias specifications (thanks Ralf Schmitt). 1.0.0b6 ---------------------------------------- - fix recursive import issue resulting in a superflous KeyError - default to __version__ '0' and not set __loader__ or __path__ at all if it doesn't exist on the underlying init module 1.0.0b5 ---------------------------------------- - fixed MANIFEST.in - also transfer __loader__ attribute (thanks Ralf Schmitt) - compat fix for BPython 1.0.0b3 (compared to 1.0.0b2) ------------------------------------ - added special __onfirstaccess__ attribute whose value will be called on the first attribute access of an apimodule.