Skip to content

Commit

Permalink
general: extract the hack to warn of legacy imports and fallback to c…
Browse files Browse the repository at this point in the history
…ore/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:
- #12
- #89
- #102
  • Loading branch information
karlicoss committed Jun 1, 2022
1 parent 8336d18 commit 9461df6
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 82 deletions.
2 changes: 1 addition & 1 deletion misc/check_legacy_init_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 55 additions & 0 deletions my/core/legacy.py
Original file line number Diff line number Diff line change
@@ -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
72 changes: 16 additions & 56 deletions my/fbmessenger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
###
40 changes: 15 additions & 25 deletions my/reddit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *

0 comments on commit 9461df6

Please sign in to comment.