From 9461df6aa5ceb538fc52ecbf338a6ced132e0c0f Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Wed, 1 Jun 2022 23:02:58 +0100 Subject: [PATCH] general: extract the hack to warn of legacy imports and fallback to core/legacy.py use it both in my.fbmessenger and my.reddit if in the future any new modules need to be switched to namespace package structure with all.py it should make it easy to do related: - https://github.com/karlicoss/HPI/issues/12 - https://github.com/karlicoss/HPI/issues/89 - https://github.com/karlicoss/HPI/issues/102 --- misc/check_legacy_init_py.py | 2 +- my/core/legacy.py | 55 +++++++++++++++++++++++++++ my/fbmessenger/__init__.py | 72 ++++++++---------------------------- my/reddit/__init__.py | 40 ++++++++------------ 4 files changed, 87 insertions(+), 82 deletions(-) create mode 100644 my/core/legacy.py diff --git a/misc/check_legacy_init_py.py b/misc/check_legacy_init_py.py index 102b924..c100368 100755 --- a/misc/check_legacy_init_py.py +++ b/misc/check_legacy_init_py.py @@ -12,7 +12,7 @@ import logzero # type: ignore[import] logger = logzero.logger -MSG = 'DEPRECATED! Instead of my.fbmessengerexport' +MSG = 'importing my.fbmessenger is DEPRECATED' def expect(*cmd: str, should_warn: bool=True) -> None: res = run(cmd, stderr=PIPE) diff --git a/my/core/legacy.py b/my/core/legacy.py new file mode 100644 index 0000000..21ec056 --- /dev/null +++ b/my/core/legacy.py @@ -0,0 +1,55 @@ +# I think 'compat' should be for python-specific compat stuff, whereas this for HPI specific backwards compatibility +import inspect +import re +from typing import List + +from my.core import warnings as W + + +def handle_legacy_import( + parent_module_name: str, + legacy_submodule_name: str, + parent_module_path: List[str], +) -> bool: + ### + # this is to trick mypy into treating this as a proper namespace package + # should only be used for backwards compatibility on packages that are convernted into namespace & all.py pattern + # - https://www.python.org/dev/peps/pep-0382/#namespace-packages-today + # - https://github.com/karlicoss/hpi_namespace_experiment + # - discussion here https://memex.zulipchat.com/#narrow/stream/279601-hpi/topic/extending.20HPI/near/269946944 + from pkgutil import extend_path + parent_module_path[:] = extend_path(parent_module_path, parent_module_name) + # 'this' source tree ends up first in the pythonpath when we extend_path() + # so we need to move 'this' source tree towards the end to make sure we prioritize overlays + parent_module_path[:] = parent_module_path[1:] + parent_module_path[:1] + ### + + # allow stuff like 'import my.module.submodule' and such + imported_as_parent = False + + # allow stuff like 'from my.module import submodule' + importing_submodule = False + + # some hacky traceback to inspect the current stack + # to see if the user is using the old style of importing + for f in inspect.stack(): + # seems that when a submodule is imported, at some point it'll call some internal import machinery + # with 'parent' set to the parent module + # if parent module is imported first (i.e. in case of deprecated usage), it won't be the case + args = inspect.getargvalues(f.frame) + if args.locals.get('parent') == parent_module_name: + imported_as_parent = True + + # this we can only detect from the code I guess + line = '\n'.join(f.code_context or []) + if re.match(rf'from\s+{parent_module_name}\s+import\s+{legacy_submodule_name}', line): + importing_submodule = True + + is_legacy_import = not (imported_as_parent or importing_submodule) + if is_legacy_import: + W.high(f'''\ +importing {parent_module_name} is DEPRECATED! \ +Instead, import from {parent_module_name}.{legacy_submodule_name} or {parent_module_name}.all \ +See https://github.com/karlicoss/HPI/blob/master/doc/MODULE_DESIGN.org#allpy for more info. +''') + return is_legacy_import diff --git a/my/fbmessenger/__init__.py b/my/fbmessenger/__init__.py index 2a3ba7f..3919c44 100644 --- a/my/fbmessenger/__init__.py +++ b/my/fbmessenger/__init__.py @@ -4,69 +4,29 @@ It should be removed in the future, and you should replace any imports like: from my.fbmessenger import ... to: -from my.fbmessenger.export import ... +from my.fbmessenger.all import ... since that allows for easier overriding using namespace packages -https://github.com/karlicoss/HPI/issues/102 +See https://github.com/karlicoss/HPI/blob/master/doc/MODULE_DESIGN.org#allpy for more info. """ -# TODO ^^ later, replace the above with from my.fbmessenger.all, when we add more data sources -import re -import inspect +# prevent it from apprearing in modules list/doctor +from ..core import __NOT_HPI_MODULE__ - -mname = __name__.split('.')[-1] - -# allow stuff like 'import my.module.submodule' and such -imported_as_parent = False - -# allow stuff like 'from my.module import submodule' -importing_submodule = False - -# some hacky traceback to inspect the current stack -# to see if the user is using the old style of importing -for f in inspect.stack(): - # seems that when a submodule is imported, at some point it'll call some internal import machinery - # with 'parent' set to the parent module - # if parent module is imported first (i.e. in case of deprecated usage), it won't be the case - args = inspect.getargvalues(f.frame) - if args.locals.get('parent') == f'my.{mname}': - imported_as_parent = True - - # this we can only detect from the code I guess - line = '\n'.join(f.code_context or []) - if re.match(rf'from\s+my\.{mname}\s+import\s+export', line): - # todo 'export' is hardcoded, not sure how to infer allowed objects anutomatically.. - importing_submodule = True - -legacy = not (imported_as_parent or importing_submodule) - -if legacy: - from my.core import warnings as W - # TODO: add link to instructions to migrate - W.high("DEPRECATED! Instead of my.fbmessengerexport, import from my.fbmessengerexport.export") - # only import in legacy mode - # otherswise might have unfortunate side effects (e.g. missing imports) - from .export import * - -# kinda annoying to keep it, but it's so legacy 'hpi module install my.fbmessenger' work -# needs to be on the top level (since it's extracted via ast module), but hopefully it doesn't hurt here +# kinda annoying to keep it, but it's so legacy 'hpi module install my.fbmessenger' works +# needs to be on the top level (since it's extracted via ast module) REQUIRES = [ 'git+https://github.com/karlicoss/fbmessengerexport', ] -# to prevent it from apprearing in modules list/doctor -from ..core import __NOT_HPI_MODULE__ +from my.core.legacy import handle_legacy_import +is_legacy_import = handle_legacy_import( + parent_module_name=__name__, + legacy_submodule_name='export', + parent_module_path=__path__, +) + +if is_legacy_import: + # todo not sure if possible to move this into legacy.py + from .export import * -### -# this is to trick mypy into treating this as a proper namespace package -# should only be used for backwards compatibility on packages that are convernted into namespace & all.py pattern -# - https://www.python.org/dev/peps/pep-0382/#namespace-packages-today -# - https://github.com/karlicoss/hpi_namespace_experiment -# - discussion here https://memex.zulipchat.com/#narrow/stream/279601-hpi/topic/extending.20HPI/near/269946944 -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) -# 'this' source tree ends up first in the pythonpath when we extend_path() -# so we need to move 'this' source tree towards the end to make sure we prioritize overlays -__path__ = __path__[1:] + __path__[:1] -### diff --git a/my/reddit/__init__.py b/my/reddit/__init__.py index aadd6a0..22813f1 100644 --- a/my/reddit/__init__.py +++ b/my/reddit/__init__.py @@ -6,36 +6,26 @@ from my.reddit import ... to: from my.reddit.all import ... since that allows for easier overriding using namespace packages -https://github.com/karlicoss/HPI/issues/102 +See https://github.com/karlicoss/HPI/blob/master/doc/MODULE_DESIGN.org#allpy for more info. """ -# For now, including this here, since importing the module -# causes .rexport to be imported, which requires rexport +# prevent it from apprearing in modules list/doctor +from ..core import __NOT_HPI_MODULE__ + +# kinda annoying to keep it, but it's so legacy 'hpi module install my.reddit' works +# needs to be on the top level (since it's extracted via ast module) REQUIRES = [ 'git+https://github.com/karlicoss/rexport', ] -import re -import traceback -# some hacky traceback to inspect the current stack -# to see if the user is using the old style of importing -warn = False -for f in traceback.extract_stack(): - line = f.line or '' # just in case it's None, who knows.. +from my.core.legacy import handle_legacy_import +is_legacy_import = handle_legacy_import( + parent_module_name=__name__, + legacy_submodule_name='rexport', + parent_module_path=__path__, +) - # cover the most common ways of previously interacting with the module - if 'import my.reddit ' in (line + ' '): - warn = True - elif 'from my import reddit' in line: - warn = True - elif re.match(r"from my\.reddit\simport\s(comments|saved|submissions|upvoted)", line): - warn = True - -# TODO: add link to instructions to migrate -if warn: - from my.core import warnings as W - W.high("DEPRECATED! Instead of my.reddit, import from my.reddit.all instead.") - - -from .rexport import * +if is_legacy_import: + # todo not sure if possible to move this into legacy.py + from .rexport import *