diff --git a/.travis.yml b/.travis.yml index 2a24ac3a..883d5fe0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ install: script: - export MAJOR_PYTHON_VERSION=`echo $TRAVIS_PYTHON_VERSION | cut -c 1` - coverage run --source=toolz $(which nosetests) - --with-doctest + --with-doctest toolz/ - if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then coverage report --show-missing --fail-under=100 ; fi - if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then pep8 --ignore=$PEP8_IGNORE --exclude=conf.py,tests,examples,bench -r --show-source . ; fi diff --git a/setup.py b/setup.py index a1d004f1..0560e67b 100755 --- a/setup.py +++ b/setup.py @@ -15,7 +15,8 @@ keywords='functional utility itertools functools', packages=['toolz', 'toolz.sandbox', - 'toolz.curried'], + 'toolz.curried', + 'tlz'], package_data={'toolz': ['tests/*.py']}, long_description=(open('README.rst').read() if exists('README.rst') else ''), diff --git a/tlz/__init__.py b/tlz/__init__.py new file mode 100644 index 00000000..9c9c84af --- /dev/null +++ b/tlz/__init__.py @@ -0,0 +1,9 @@ +"""``tlz`` mirrors the ``toolz`` API and uses ``cytoolz`` if possible. + +The ``tlz`` package is installed when ``toolz`` is installed. It provides +a convenient way to use functions from ``cytoolz``--a faster Cython +implementation of ``toolz``--if it is installed, otherwise it uses +functions from ``toolz``. +""" + +from . import _build_tlz diff --git a/tlz/_build_tlz.py b/tlz/_build_tlz.py new file mode 100644 index 00000000..34eac604 --- /dev/null +++ b/tlz/_build_tlz.py @@ -0,0 +1,100 @@ +import sys +import types +import toolz +from toolz.compatibility import import_module + + +class TlzLoader(object): + """ Finds and loads ``tlz`` modules when added to sys.meta_path""" + def __init__(self): + self.always_from_toolz = set([ + toolz.pipe, + ]) + + def _load_toolz(self, fullname): + rv = {} + package, dot, submodules = fullname.partition('.') + try: + module_name = ''.join(['cytoolz', dot, submodules]) + rv['cytoolz'] = import_module(module_name) + except ImportError: + pass + try: + module_name = ''.join(['toolz', dot, submodules]) + rv['toolz'] = import_module(module_name) + except ImportError: + pass + if not rv: + raise ImportError(fullname) + return rv + + def find_module(self, fullname, path=None): # pragma: py3 no cover + package, dot, submodules = fullname.partition('.') + if package == 'tlz': + return self + + def load_module(self, fullname): # pragma: py3 no cover + if fullname in sys.modules: # pragma: no cover + return sys.modules[fullname] + spec = TlzSpec(fullname, self) + module = self.create_module(spec) + sys.modules[fullname] = module + self.exec_module(module) + return module + + def find_spec(self, fullname, path, target=None): # pragma: no cover + package, dot, submodules = fullname.partition('.') + if package == 'tlz': + return TlzSpec(fullname, self) + + def create_module(self, spec): + return types.ModuleType(spec.name) + + def exec_module(self, module): + toolz_mods = self._load_toolz(module.__name__) + fast_mod = toolz_mods.get('cytoolz') or toolz_mods['toolz'] + slow_mod = toolz_mods.get('toolz') or toolz_mods['cytoolz'] + module.__dict__.update(toolz.merge(fast_mod.__dict__, module.__dict__)) + package = fast_mod.__package__ + if package is not None: + package, dot, submodules = package.partition('.') + module.__package__ = ''.join(['tlz', dot, submodules]) + if not module.__doc__: + module.__doc__ = fast_mod.__doc__ + + # show file from toolz during introspection + module.__file__ = slow_mod.__file__ + + for k, v in fast_mod.__dict__.items(): + tv = slow_mod.__dict__.get(k) + try: + hash(tv) + except TypeError: + tv = None + if tv in self.always_from_toolz: + module.__dict__[k] = tv + elif ( + isinstance(v, types.ModuleType) + and v.__package__ == fast_mod.__name__ + ): + package, dot, submodules = v.__name__.partition('.') + module_name = ''.join(['tlz', dot, submodules]) + submodule = import_module(module_name) + module.__dict__[k] = submodule + + +class TlzSpec(object): + def __init__(self, name, loader): + self.name = name + self.loader = loader + self.origin = None + self.submodule_search_locations = [] + self.loader_state = None + self.cached = None + self.parent = None + self.has_location = False + + +tlz_loader = TlzLoader() +sys.meta_path.append(tlz_loader) +tlz_loader.exec_module(sys.modules['tlz']) diff --git a/toolz/tests/test_tlz.py b/toolz/tests/test_tlz.py new file mode 100644 index 00000000..ed2646a8 --- /dev/null +++ b/toolz/tests/test_tlz.py @@ -0,0 +1,55 @@ +import toolz + + +def test_tlz(): + import tlz + tlz.curry + tlz.functoolz.curry + assert tlz.__package__ == 'tlz' + assert tlz.__name__ == 'tlz' + import tlz.curried + assert tlz.curried.__package__ == 'tlz.curried' + assert tlz.curried.__name__ == 'tlz.curried' + tlz.curried.curry + import tlz.curried.operator + assert tlz.curried.operator.__package__ in (None, 'tlz.curried') + assert tlz.curried.operator.__name__ == 'tlz.curried.operator' + assert tlz.functoolz.__name__ == 'tlz.functoolz' + m1 = tlz.functoolz + import tlz.functoolz as m2 + assert m1 is m2 + import tlz.sandbox + try: + import tlzthisisabadname.curried + 1/0 + except ImportError: + pass + try: + import tlz.curry + 1/0 + except ImportError: + pass + try: + import tlz.badsubmodulename + 1/0 + except ImportError: + pass + + assert toolz.__package__ == 'toolz' + assert toolz.curried.__package__ == 'toolz.curried' + assert toolz.functoolz.__name__ == 'toolz.functoolz' + try: + import cytoolz + assert cytoolz.__package__ == 'cytoolz' + assert cytoolz.curried.__package__ == 'cytoolz.curried' + assert cytoolz.functoolz.__name__ == 'cytoolz.functoolz' + except ImportError: + pass + + assert tlz.__file__ == toolz.__file__ + assert tlz.functoolz.__file__ == toolz.functoolz.__file__ + + assert tlz.pipe is toolz.pipe + + assert 'tlz' in tlz.__doc__ + assert tlz.curried.__doc__ is not None