diff options
author | Anthon van der Neut <anthon@mnt.org> | 2015-08-27 16:01:05 +0200 |
---|---|---|
committer | Anthon van der Neut <anthon@mnt.org> | 2015-08-27 16:01:05 +0200 |
commit | 93e249469454df3c83e7bd29359d460baf29e9f6 (patch) | |
tree | d2478078fe3eb4713deeae6b7c426efbaaf1b813 | |
parent | a1facf25cc3a0578afed89e9ea4d11cf9ea46e13 (diff) | |
download | ruamel.base-93e249469454df3c83e7bd29359d460baf29e9f6.tar.gz |
no longer create __init__.py files
-rw-r--r-- | README.rst | 3 | ||||
-rw-r--r-- | __init__.py | 12 | ||||
-rw-r--r-- | _test/test_base.py | 12 | ||||
-rw-r--r-- | setup.py | 364 | ||||
-rw-r--r-- | test/test_base.py | 8 | ||||
-rw-r--r-- | tox.ini | 3 |
6 files changed, 305 insertions, 97 deletions
@@ -1,7 +1,4 @@ ruamel.base -- provides a single definition point for ``ruamel/__init__.py``, - ``ruamel/ext/__init__.py`` and - ``ruamel/std/__init__.py`` - (future) place for elements common to multiple packages diff --git a/__init__.py b/__init__.py index bcc1555..e25444e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,15 @@ # coding: utf-8 +from __future__ import print_function + +_package_data = dict( + full_package_name="ruamel.base", + version_info = (1, 0, 0), + author='Anthon van der Neut', + author_email='a.van.der.neut@ruamel.eu', + description="common routines for ruamel packages", + entry_points=None, +) def _convert_version(tup): """create a PEP 386 pseudo-format conformant string from tuple tup""" @@ -19,7 +29,7 @@ def _convert_version(tup): return ret_val -version_info = (0, 3) +version_info = _package_data['version_info'] __version__ = _convert_version(version_info) del _convert_version diff --git a/_test/test_base.py b/_test/test_base.py new file mode 100644 index 0000000..cd49d07 --- /dev/null +++ b/_test/test_base.py @@ -0,0 +1,12 @@ +# coding: utf-8 + +from __future__ import print_function + +import pytest + +# default for tox stub is to Fail +def test_base(): + import ruamel + with pytest.raises(ImportError): + import ruamel.std + assert True @@ -1,33 +1,66 @@ -#! /usr/bin/env python +## header # coding: utf-8 from __future__ import print_function -import sys -import os -from textwrap import dedent +if __name__ != '__main__': + raise NotImplementedError('should never include setup.py') + +## definitions + +full_package_name = None + +def _package_data(fn): + data = {} + with open(fn) as fp: + parsing = False + for line in fp.readlines(): + if line.startswith('_package_data'): + parsing = True + continue + if not parsing: + continue + if line.startswith(')'): + break + k, v = [x.strip() for x in line.split('=', 1)] + if v[-1] == ',': + v = v[:-1] + if v[0] in '\'"' and v[0] == v[-1]: + data[k] = v[1:-1] + elif v == 'None': + data[k] = None + elif v == 'True': + data[k] = True + elif v == 'False': + data[k] = False + elif v[0] == '(' and v[-1] == ')': + data[k] = tuple([x.strip()[1:-1] if x[0] in '\'"' else int(x) \ + for x in v[1:-1].split(', ')]) + elif v[0] == '[' and v[-1] == ']': + data[k] = [x.strip()[1:-1] if x[0] in '\'"' else int(x) \ + for x in v[1:-1].split(', ')] + else: + print('Unknown: >>>>> {0!r} {1!r}'.format(k, v)) + return data -name_space = 'ruamel' -package_name = 'base' -full_package_name = name_space + '.' + package_name +pkg_data = _package_data('__init__.py') exclude_files = [ 'setup.py', ] +## imports +import os +import sys -def get_version(): - v_i = 'version_info = ' - for line in open('__init__.py'): - if not line.startswith(v_i): - continue - s_e = line[len(v_i):].strip()[1:-1].split(', ') - els = [x.strip()[1:-1] if x[0] in '\'"' else int(x) for x in s_e] - return els +from setuptools import setup +from setuptools.command import install_lib + +## helper def _check_convert_version(tup): - """create a PEP 386 pseudo-format conformant string from tuple tup""" + """Create a PEP 386 pseudo-format conformant string from tuple tup.""" ret_val = str(tup[0]) # first is always digit next_sep = "." # separator for next extension, can be "" or "." nr_digits = 0 # nr of adjacent digits in rest, to verify @@ -57,47 +90,12 @@ def _check_convert_version(tup): return ret_val -version_info = get_version() +version_info = pkg_data['version_info'] version_str = _check_convert_version(version_info) -if __name__ == '__main__': - # put here so setup.py can be imported more easily - from setuptools import setup, find_packages, Extension - from setuptools.command import install_lib - - class MyInstallLib(install_lib.install_lib): - "create __init__.py on the fly" - def run(self): - install_lib.install_lib.run(self) - init_txt = dedent('''\ - # coding: utf-8 - # Copyright (c) 2013-2015 Anthon van der Neut, RUAMEL bvba - "generated __init__.py " - try: - __import__('pkg_resources').declare_namespace(__name__) - except ImportError: - pass - ''') - init_paths = ["ruamel/std", ] # "ruamel/ext"] - for init_path_s in init_paths: - init_path = init_path_s.split('/') - for product_init in [ - os.path.join( - *([self.install_dir] + init_path[:p+1] + ['__init__.py'])) - for p in range(len(init_path)) - ]: - if not os.path.exists(product_init): - d = os.path.dirname(product_init) - if not os.path.exists(d): - os.makedirs(d) - print('creating %s' % product_init) - with open(product_init, "wt") as fp: - fp.write(init_txt) - setup = os.path.join(self.install_dir, 'setup.py') - def install(self): - fpp = full_package_name.split('.') # full package path + fpp = pkg_data['full_package_name'].split('.') # full package path full_exclude_files = [os.path.join(*(fpp + [x])) for x in exclude_files] alt_files = [] @@ -112,40 +110,238 @@ class MyInstallLib(install_lib.install_lib): return alt_files -def main(): - install_requires = [ - ] - # if sys.version_info < (3, 4): - # install_requires.append("") - packages = [full_package_name] + [(full_package_name + '.' + x) for x - in find_packages(exclude=['tests'])] - setup( - name=full_package_name, - version=version_str, - description="common elements for ruamel packages, " - "hierarchy structure with __init__.py", - install_requires=install_requires, - long_description=open('README.rst').read(), - url='https://bitbucket.org/ruamel/' + package_name, - author='Anthon van der Neut', - author_email='a.van.der.neut@ruamel.eu', - license="MIT license", - package_dir={full_package_name: '.'}, - namespace_packages=[name_space], - packages=packages, - cmdclass={'install_lib': MyInstallLib}, - classifiers=[ - 'Development Status :: 4 - Beta', +class NameSpacePackager(object): + def __init__(self, pkg_data): + assert isinstance(pkg_data, dict) + self._pkg_data = pkg_data + self.full_package_name = self._pkg_data['full_package_name'] + self._split = None + self.depth = self.full_package_name.count('.') + self.command = None + if sys.argv[0] == 'setup.py' and sys.argv[1] == 'install' and \ + not '--single-version-externally-managed' in sys.argv: + print('error: have to install with "pip install ."') + sys.exit(1) + for x in sys.argv: + if x[0] == '-' or x == 'setup.py': + continue + self.command = x + break + + @property + def split(self): + """split the full package name in list of compontents""" + if self._split is None: + fpn = self.full_package_name.split('.') + self._split = [] + while fpn: + self._split.insert(0, '.'.join(fpn)) + fpn = fpn[:-1] + for d in os.listdir('.'): + if not os.path.isdir(d) or d == self._split[0] or d[0] == '_': + continue + x = os.path.join(d, '__init__.py') + if os.path.exists(x): + self._split.append(self.full_package_name + '.' + d) + return self._split + + @property + def namespace_packages(self): + return self.split[:self.depth] + + @property + def package_dir(self): + return { + # don't specify empty dir, clashes with package_data spec + self.full_package_name: '.', + self.split[0]: self.split[0], + } + + def create_dirs(self): + """create the directories necessary for namespace packaging""" + if not os.path.exists(self.split[0]): + for d in self.split[:self.depth]: + d = os.path.join(*d.split('.')) + os.mkdir(d) + with open(os.path.join(d, '__init__.py'), 'w') as fp: + fp.write('import pkg_resources\n' + 'pkg_resources.declare_namespace(__name__)\n') + os.symlink( + # a.b gives a/b -> .. + # a.b.c gives a/b/c -> ../.. + os.path.join(*['..'] * self.depth), + os.path.join(*self.split[self.depth].split('.')) + ) + + def check(self): + try: + from pip.exceptions import InstallationError + except ImportError: + return + # arg is either develop (pip install -e) or install + if self.command not in ['install', 'develop']: + return + + # if hgi and hgi.base are both in namespace_packages matching + # against the top (hgi.) it suffices to find minus-e and non-minus-e + # installed packages. As we don't know the order in namespace_packages + # do some magic + prefix = self.split[0] + prefixes = set([prefix, prefix.replace('_', '-')]) + for p in sys.path: + if not p: + continue # directory with setup.py + if os.path.exists(os.path.join(p, 'setup.py')) : + continue # some linked in stuff might not be hgi based + if not os.path.isdir(p): + continue + if p.startswith('/tmp/'): + continue + for fn in os.listdir(p): + for pre in prefixes: + if fn.startswith(pre): + break + else: + continue + full_name = os.path.join(p, fn) + # not in prefixes the toplevel is never changed from _ to - + if fn == prefix and os.path.isdir(full_name): + # directory -> other, non-minus-e, install + if self.command == 'develop': + raise InstallationError( + 'Cannot mix develop (pip install -e),\nwith ' + 'non-develop installs for package name {0}'.format( + fn)) + elif fn == prefix: + raise InstallationError( + 'non directory package {0} in {1}'.format( + fn, p)) + for pre in [x + '.' for x in prefixes]: + if fn.startswith(pre): + break + else: + continue # hgiabc instead of hgi. + if fn.endswith('-link') and self.command == 'install': + raise InstallationError( + 'Cannot mix non-develop with develop\n(pip install -e)' + ' installs for package name {0}'.format(fn)) + + def entry_points(self, script_name=None, package_name=None): + ep = self._pkg_data.get('entry_points', True) + if ep is None: + return None + if ep is not True: + return {'console_scripts': [ep]} + if package_name is None: + package_name = self.full_package_name + if not script_name: + script_name = package_name.split('.')[-1] + return {'console_scripts': [ + '{0} = {1}:main'.format(script_name, package_name), + ]} + + @property + def url(self): + return 'https://bitbucket.org/{0}/{1}'.format( + *self.full_package_name.split('.', 1)) + + @property + def author(self): + return self._pkg_data['author'] + + @property + def author_email(self): + return self._pkg_data['author_email'] + + @property + def license(self): + lic = self._pkg_data.get('license') + #if lic is None: + # lic_file_name = os.path.join(os.path.dirname(__file__), 'LICENSE') + # assert os.path.exists(lic_file_name) + return "MIT license" + + @property + def description(self): + return self._pkg_data['description'] + + @property + def status(self): + # αβ + status = self._pkg_data.get('status', 'β') + if status == 'α': + return (3, 'Alpha') + elif status == 'β': + return (4, 'Beta') + elif 'stable' in status.lower(): + return (5, 'Production/Stable') + raise NotImplementedError + + @property + def classifiers(self): + return [ + 'Development Status :: {0} - {1}'.format(*self.status), 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', + 'License :: ' + ('Other/Proprietary License' \ + if self._pkg_data.get('license') else \ + 'OSI Approved :: MIT License'), 'Operating System :: OS Independent', 'Programming Language :: Python', ] + + @property + def install_requires(self): + return pkg_data.get('install_requires', []) + + @property + def data_files(self): + df = self._pkg_data.get('data_files', []) + if self._pkg_data.get('license') is None: + df.append('LICENSE') + if not df: + return None + return [('.', df),] + + @property + def package_data(self): + df = self._pkg_data.get('data_files', []) + if self._pkg_data.get('license') is None: + # include the file + df.append('LICENSE') + # but don't install it + exclude_files.append('LICENSE') + if not df: + return None + return {self.full_package_name: df} + +## call setup +def main(): + nsp = NameSpacePackager(pkg_data) + nsp.check() + nsp.create_dirs() + kw = dict( + name=nsp.full_package_name, + namespace_packages=nsp.namespace_packages, + version=version_str, + packages=nsp.split, + url=nsp.url, + author=nsp.author, + author_email=nsp.author_email, + cmdclass={'install_lib': MyInstallLib}, + package_dir=nsp.package_dir, + entry_points=nsp.entry_points(), + description=nsp.description, + install_requires=nsp.install_requires, + license=nsp.license, + classifiers=nsp.classifiers, + package_data=nsp.package_data, ) + for k in sorted(kw): + v = kw[k] + print(k, '->', v) + with open('README.rst') as fp: + kw['long_description'] = fp.read() + setup(**kw) + -if __name__ == '__main__': - if len(sys.argv) > 1 and sys.argv[1] == 'sdist': - assert full_package_name == os.path.abspath(os.path.dirname( - __file__)).split('site-packages' + os.path.sep)[1].replace( - os.path.sep, '.') - main() +main() diff --git a/test/test_base.py b/test/test_base.py deleted file mode 100644 index d2c0125..0000000 --- a/test/test_base.py +++ /dev/null @@ -1,8 +0,0 @@ - - -# default for tox stub is to Fail -def test_base(): - import ruamel - import ruamel.std - # import ruamel.ext - assert True @@ -2,6 +2,7 @@ envlist = py26,py27,py33,py34 [testenv] -commands = py.test test +commands = + py.test _test deps = pytest |