diff --git a/misc/check_legacy_init_py.py b/misc/check_legacy_init_py.py index 102b9244..c1003680 100755 --- a/misc/check_legacy_init_py.py +++ b/misc/check_legacy_init_py.py @@ -12,7 +12,7 @@ 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 00000000..21ec056c --- /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 2a3ba7f9..3919c44c 100644 --- a/my/fbmessenger/__init__.py +++ b/my/fbmessenger/__init__.py @@ -4,69 +4,29 @@ 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 - - -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 * +# 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.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 aadd6a06..22813f17 100644 --- a/my/reddit/__init__.py +++ b/my/reddit/__init__.py @@ -6,36 +6,26 @@ 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.. - - # 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 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__, +) -from .rexport import * +if is_legacy_import: + # todo not sure if possible to move this into legacy.py + from .rexport import *