diff --git a/setup.py b/setup.py index f8754e16c..ea2d267fc 100755 --- a/setup.py +++ b/setup.py @@ -25,6 +25,7 @@ 'python-dateutil', 'windows-curses<2.3.1; platform_system == "Windows"', #1841 'importlib-metadata >= 3.6', + 'importlib_resources; python_version<"3.9"' ], packages=['visidata', 'visidata.loaders', 'visidata.vendor', 'visidata.tests', 'visidata.ddw', 'visidata.man', 'visidata.themes', 'visidata.features', 'visidata.experimental', 'visidata.apps', 'visidata.apps.vgit', 'visidata.apps.vdsql', 'visidata.desktop'], data_files=[('share/man/man1', ['visidata/man/vd.1', 'visidata/man/visidata.1']), ('share/applications/', ['visidata/desktop/visidata.desktop'])], diff --git a/visidata/__init__.py b/visidata/__init__.py index d07b07371..3807d8787 100644 --- a/visidata/__init__.py +++ b/visidata/__init__.py @@ -14,10 +14,13 @@ class EscapeException(BaseException): def addGlobals(*args, **kwargs): '''Update the VisiData globals dict with items from *args* and *kwargs*, which are mappings of names to functions. - Importers can call ``addGlobals(globals())`` to have their globals accessible to execstrings.''' + Importers can call ``addGlobals(globals())`` to have their globals accessible to execstrings. + + Dunder methods are ignored, to prevent accidentally overwriting housekeeping methods.''' + drop_dunder = lambda d: {k: v for k, v in d.items() if not k.startswith("__")} for g in args: - globals().update(g) - globals().update(kwargs) + globals().update(drop_dunder(g)) + globals().update(drop_dunder(kwargs)) def getGlobals(): diff --git a/visidata/help.py b/visidata/help.py index 68ac23c35..eb35f1610 100644 --- a/visidata/help.py +++ b/visidata/help.py @@ -119,10 +119,9 @@ def draw(self, scr, x=None, y=None): @VisiData.api @functools.lru_cache(maxsize=None) def getHelpPane(vd, name, module='vdplus'): - from pkg_resources import resource_filename ret = HelpPane(name) try: - ret.amgr.load(name, Path(resource_filename(module,'ddw/'+name+'.ddw')).open(encoding='utf-8')) + ret.amgr.load(name, (vd.pkg_resources_files(module)/f'ddw/{name}.ddw').open(encoding='utf-8')) ret.amgr.trigger(name, loop=True) except FileNotFoundError as e: vd.debug(str(e)) @@ -135,11 +134,11 @@ def getHelpPane(vd, name, module='vdplus'): @VisiData.api def openManPage(vd): - from pkg_resources import resource_filename import os with SuspendCurses(): - if os.system(' '.join(['man', resource_filename(__name__, 'man/vd.1')])) != 0: - vd.push(TextSheet('man_vd', source=Path(resource_filename(__name__, 'man/vd.txt')))) + module_path = vd.pkg_resources_files(__name__.split('.')[0]) + if os.system(' '.join(['man', str(module_path/'man/vd.1')])) != 0: + vd.push(TextSheet('man_vd', source=module_path/'man/vd.txt')) # in VisiData, g^H refers to the man page diff --git a/visidata/loaders/google.py b/visidata/loaders/google.py index 44d1b4d5d..2f1155e06 100644 --- a/visidata/loaders/google.py +++ b/visidata/loaders/google.py @@ -1,14 +1,18 @@ + from visidata import vd, VisiData, Sheet, IndexSheet, SequenceSheet, ColumnItem, Path, AttrDict, ColumnAttr, asyncthread def _google_creds_fn(): - from pkg_resources import resource_filename + + filename = 'google_creds.json' + google_creds_path = vd.pkg_resources_files('vdplus.api.google') / filename import os - if not os.path.exists(Path(resource_filename('vdplus.api.google', 'google-creds.json'))): - vd.error('google-creds.json file does not exist. create it by following this guide: https://github.com/saulpw/visidata/blob/develop/docs/gmail.md') + if not os.path.exists(google_creds_path): + vd.error(f'{filename} file does not exist in {google_creds_path.parent}\n' + 'Create it by following this guide: https://github.com/saulpw/visidata/blob/develop/docs/gmail.md') else: - return resource_filename('vdplus.api.google', 'google-creds.json') + return str(google_creds_path) @VisiData.api diff --git a/visidata/main.py b/visidata/main.py index 5a04fed2d..bcbb57836 100755 --- a/visidata/main.py +++ b/visidata/main.py @@ -158,9 +158,7 @@ def main_vd(): print(vd.version_info) return 0 if '-h' in sys.argv or '--help' in sys.argv: - from pkg_resources import resource_filename - with open(resource_filename(__name__, 'man/vd.txt'), 'r') as fp: - print(fp.read()) + print((Path(vd.pkg_resources_files(visidata)) / 'man' / 'vd.txt').open().read()) return 0 vd.status(__version_info__) diff --git a/visidata/path.py b/visidata/path.py index 0805d5ada..64ddf07b0 100644 --- a/visidata/path.py +++ b/visidata/path.py @@ -12,6 +12,18 @@ vd.option('encoding', 'utf-8', 'encoding passed to codecs.open when opening a file', replay=True) vd.option('encoding_errors', 'surrogateescape', 'encoding_errors passed to codecs.open', replay=True) +@VisiData.api +def pkg_resources_files(vd, package): + ''' + Returns a Traversable object (Path-like), based on the location of the package. + importlib.resources.files exists in Python >= 3.9; use importlib_resources for the rest. + ''' + try: + from importlib.resources import files + except ImportError: #1968 + from importlib_resources import files + return files(package) + @lru_cache() def vstat(path, force=False): try: diff --git a/visidata/settings.py b/visidata/settings.py index 605d8cddc..16b822df1 100644 --- a/visidata/settings.py +++ b/visidata/settings.py @@ -468,7 +468,7 @@ def importStar(vd, pkgname): m = vd.importModule(pkgname) vd.addGlobals({pkgname:m}) - vd.addGlobals({k:v for k, v in m.__dict__.items() if not k.startswith('__')}) + vd.addGlobals(m.__dict__) @VisiData.api diff --git a/visidata/tests/test_commands.py b/visidata/tests/test_commands.py index e433a1c4c..f97c8a7e2 100644 --- a/visidata/tests/test_commands.py +++ b/visidata/tests/test_commands.py @@ -1,4 +1,3 @@ -import pkg_resources import pytest from unittest.mock import Mock @@ -157,7 +156,7 @@ def runOneTest(self, mock_screen, longname): else: vd.getkeystroke = Mock(side_effect=['^J']) - sample_file = pkg_resources.resource_filename('visidata', 'tests/sample.tsv') + sample_file = vd.pkg_resources_files(visidata) / 'tests/sample.tsv' vs = visidata.TsvSheet('test_commands', source=visidata.Path(sample_file)) vs.reload.__wrapped__(vs) vs.vd = vd