Skip to content

Commit

Permalink
Using sys.meta_path for the import deprecations.
Browse files Browse the repository at this point in the history
Very clean and much easier to maintain way.
  • Loading branch information
krischer committed Mar 23, 2015
1 parent db55fbd commit b804a0e
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 46 deletions.
124 changes: 121 additions & 3 deletions obspy/__init__.py
Expand Up @@ -32,6 +32,10 @@
from future.builtins import * # NOQA
from future.utils import PY2, native_str

import imp
import warnings
import sys

# don't change order
from obspy.core.utcdatetime import UTCDateTime # NOQA
from obspy.core.util import _getVersionString
Expand Down Expand Up @@ -69,6 +73,120 @@
__all__ = [native_str(i) for i in __all__]


if __name__ == '__main__':
import doctest
doctest.testmod(exclude_empty=True)
class ObsPyDeprecationWarning(UserWarning):
"""
Make a custom deprecation warning as deprecation warnings or hidden by
default since Python 2.7 and 3.2 and we really want users to notice these.
"""
pass


class ObsPyRestructureMetaPathFinderAndLoader(object):
"""
Meta path finder and module loader helping users in transitioning to the
new module structure.
Make sure to remove this once 0.11 has been released!
"""
# Maps all imports to their new imports.
import_map = {
# I/O modules
"obspy.ah": "obspy.io.ah",
"obspy.cnv": "obspy.io.cnv",
"obspy.css": "obspy.io.css",
"obspy.datamark": "obspy.io.datamark",
"obspy.gse2": "obspy.io.gse2",
"obspy.kinemetrics": "obspy.io.kinemetrics",
"obspy.mseed": "obspy.io.mseed",
"obspy.ndk": "obspy.io.ndk",
"obspy.nlloc": "obspy.io.nlloc",
"obspy.pdas": "obspy.io.pdas",
"obspy.pde": "obspy.io.pde",
"obspy.sac": "obspy.io.sac",
"obspy.seg2": "obspy.io.seg2",
"obspy.segy": "obspy.io.segy",
"obspy.seisan": "obspy.io.seisan",
"obspy.sh": "obspy.io.sh",
"obspy.wav": "obspy.io.wav",
"obspy.xseed": "obspy.io.xseed",
"obspy.y": "obspy.io.y",
"obspy.zmap": "obspy.io.zmap",
# Clients
"obspy.arclink": "obspy.io.arclink",
"obspy.earthworm": "obspy.io.earthworm",
"obspy.fdsn": "obspy.io.fdsn",
"obspy.iris": "obspy.io.iris",
"obspy.neic": "obspy.io.neic",
"obspy.neries": "obspy.io.neries",
"obspy.seedlink": "obspy.io.seedlink",
"obspy.seishub": "obspy.io.seishub",
# geodetics
"obspy.core.util.geodetics": "obspy.geodetics"
}

def find_module(self, fullname, path=None):
if not path or not path[0].startswith(__path__[0]):
return None

for key in self.import_map.keys():
if fullname.startswith(key):
break
else:
return None
# Use this instance also as the loader.
return self

def load_module(self, name):
# Use cached modules.
if name in sys.modules:
return sys.modules[name]
# Otherwise check if the name is part of the import map.
elif name in self.import_map:
new_name = self.import_map[name]

# Don't load again if already loaded.
if new_name in sys.modules:
module = sys.modules[new_name]
else:
module = self._find_and_load_module(new_name)

# Warn here as at this point the module has already been imported.
warnings.warn("Module '%s' is deprecated and will stop working "
"with the next ObsPy version. Please import module "
"'%s'instead." % (name, new_name),
ObsPyDeprecationWarning)
sys.modules[new_name] = module
# This probably does not happen with a proper import. Not sure if we
# should keep this condition as it might obsfuscate non-working
# imports.
else:
module = self._find_and_load_module(name)

sys.modules[name] = module
return module

def _find_and_load_module(self, name, path=None):
"""
Finds and loads it. But if there's a . in the name, handles it
properly.
From the python-future module as it already did the painful steps to
make it work on Python 2 and Python 3.
"""
bits = name.split('.')
while len(bits) > 1:
# Treat the first bit as a package
packagename = bits.pop(0)
package = self._find_and_load_module(packagename, path)
try:
path = package.__path__
except AttributeError:
if name in sys.modules:
return sys.modules[name]
name = bits[0]
module_info = imp.find_module(name, path)
return imp.load_module(name, *module_info)


# Install meta path handler.
sys.meta_path.append(ObsPyRestructureMetaPathFinderAndLoader())
10 changes: 1 addition & 9 deletions obspy/core/util/__init__.py
Expand Up @@ -22,8 +22,6 @@
unicode_literals)
from future.builtins import * # NOQA

import sys

# import order matters - NamedTemporaryFile must be one of the first!
from obspy.core.util.attribdict import AttribDict
from obspy.core.util.base import (ALL_MODULES, DEFAULT_MODULES,
Expand All @@ -35,14 +33,8 @@
skipIf, uncompressFile)
from obspy.core.util.misc import (BAND_CODE, CatchOutput, complexifyString,
guessDelta, loadtxt, scoreatpercentile,
toIntOrZero, DynamicImportRerouteModule)
toIntOrZero)
from obspy.core.util.obspy_types import (ComplexWithUncertainties, Enum,
FloatWithUncertainties)
from obspy.core.util.testing import add_doctests, add_unittests
from obspy.core.util.version import get_git_version as _getVersionString


sys.modules[__name__] = DynamicImportRerouteModule(
name=__name__, doc=__doc__, locs=locals(), import_map={
"geodetics": "obspy.geodetics"
})
34 changes: 0 additions & 34 deletions obspy/core/util/misc.py
Expand Up @@ -12,8 +12,6 @@
unicode_literals)
from future.builtins import * # NOQA

import copy
import importlib
import inspect
import itertools
import math
Expand Down Expand Up @@ -497,35 +495,3 @@ def factorize_int(x):
if __name__ == '__main__':
import doctest
doctest.testmod(exclude_empty=True)


# Make a custom deprecation warning as deprecation warnings or hidden by
# default since Python 2.7 and 3.2 and we really want users to notice these.
class ObsPyDeprecationWarning(UserWarning):
pass


class DynamicImportRerouteModule(ModuleType):
"""
Class assisting in dynamically rerouting imports.
"""
def __init__(self, name, doc, locs, import_map):
super(DynamicImportRerouteModule, self).__init__(name=name)

# Keep the metadata of the module.
self.__dict__.update(locs)

self.import_map = import_map

def __getattr__(self, name):
try:
real_module_name = self.import_map[name]
except:
raise AttributeError

warnings.warn("Module '%s' is deprecated and will stop working with "
"the next ObsPy version. Please import module "
"'%s'instead." % (self.__name__ + "." + name,
self.import_map[name]),
ObsPyDeprecationWarning)
return importlib.import_module(real_module_name)

2 comments on commit b804a0e

@QuLogic
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a feeling there might be some "fancy" way to do this without resorting to a bunch of separate extra files.

@megies
Copy link
Member

@megies megies commented on b804a0e Mar 25, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a programmatic way in sys for it.. nice find!

Please sign in to comment.