From 06ac883ccbf6a163408a79b6d194d2b1fad589bd Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 31 May 2024 13:01:07 -0400 Subject: [PATCH 1/6] Start branch for 1.1.5 --- CHANGELOG.md | 5 ++++- src/xdoctest/__init__.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dedf44..7f734d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,10 @@ We are currently working on porting this changelog to the specifications in This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Version 1.1.4 - Unreleased +## Version 1.1.5 - Unreleased + + +## Version 1.1.4 - Released 2024-05-31 ### Fixed * Working around a `modname_to_modpath` issue. diff --git a/src/xdoctest/__init__.py b/src/xdoctest/__init__.py index 00372fd..c3ddfbf 100644 --- a/src/xdoctest/__init__.py +++ b/src/xdoctest/__init__.py @@ -313,7 +313,7 @@ def fib(n): mkinit xdoctest --nomods ''' -__version__ = '1.1.4' +__version__ = '1.1.5' # Expose only select submodules From 14313ee41e96a5480834ce651ecb3f4b31a4db88 Mon Sep 17 00:00:00 2001 From: joncrall Date: Sun, 2 Jun 2024 15:19:53 -0400 Subject: [PATCH 2/6] Better error message --- src/xdoctest/utils/util_import.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/xdoctest/utils/util_import.py b/src/xdoctest/utils/util_import.py index 10c99a5..43a44c8 100644 --- a/src/xdoctest/utils/util_import.py +++ b/src/xdoctest/utils/util_import.py @@ -180,9 +180,10 @@ def _custom_import_modpath(modpath, index=-1): with PythonPathContext(dpath, index=index): module = import_module_from_name(modname) except Exception as ex: # nocover - msg_parts = [ - 'ERROR: Failed to import modname={} with modpath={}'.format( - modname, modpath) + msg_parts = [( + 'ERROR: Failed to import modname={} with modpath={} and ' + 'sys.path modified with {} at index={}').format( + modname, modpath, repr(dpath), index) ] msg_parts.append('Caused by: {}'.format(repr(ex))) raise RuntimeError('\n'.join(msg_parts)) From 8801663cf164cdf6414d275f62f6a9e6aee85bda Mon Sep 17 00:00:00 2001 From: joncrall Date: Sun, 2 Jun 2024 15:26:20 -0400 Subject: [PATCH 3/6] Expose --version-info --- CHANGELOG.md | 3 +++ src/xdoctest/__main__.py | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f734d6..88b8c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## Version 1.1.5 - Unreleased +### Changed +* Minor modification to `xdoctest --version-info` and exposed it in CLI help. + ## Version 1.1.4 - Released 2024-05-31 diff --git a/src/xdoctest/__main__.py b/src/xdoctest/__main__.py index 3688b47..4b9e239 100644 --- a/src/xdoctest/__main__.py +++ b/src/xdoctest/__main__.py @@ -27,8 +27,8 @@ def main(argv=None): argv = sys.argv version_info = { - 'version': xdoctest.__version__, 'sys_version': sys.version, + 'version': xdoctest.__version__, } if '--version' in argv: @@ -36,8 +36,9 @@ def main(argv=None): return 0 if '--version-info' in argv: - for key, value in sorted(version_info.items()): - print('{} = {}'.format(key, value)) + print('sys_version = {}'.format(version_info['sys_version'])) + print('file = {}'.format(__file__)) + print('version = {}'.format(version_info['version'])) return 0 import argparse @@ -69,7 +70,8 @@ class RawDescriptionDefaultsHelpFormatter( If the `--command` key / value pair is unspecified, the first positional argument is used as the command. ''')) - parser.add_argument('--version', action='store_true', help='Display version info and quit') + parser.add_argument('--version', action='store_true', help='Display version and quit') + parser.add_argument('--version-info', action='store_true', help='Display version and other info and quit') # The bulk of the argparse CLI is defined in the doctest example from xdoctest import doctest_example From 9d8efaabf76bafdca40d4b6480ba2401501b0e71 Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 7 Jun 2024 20:09:13 -0400 Subject: [PATCH 4/6] Partial cleanup of dev folder --- dev/demo/demo_dynamic_analysis.py | 18 ++++++++++++++++++ dev/{ => demo}/demo_errors.py | 12 ++++++------ dev/{ => demo}/demo_issues.py | 10 +++++----- dev/{ => demo}/demo_properties.py | 0 dev/{ => demo}/demo_usage_with_logger.py | 6 +++--- dev/demo_dynamic_analysis.py | 18 ------------------ dev/{ => devcheck}/interactive_embed_tests.py | 4 ++-- dev/{ => maintain}/port_ubelt_utils.py | 2 +- 8 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 dev/demo/demo_dynamic_analysis.py rename dev/{ => demo}/demo_errors.py (79%) rename dev/{ => demo}/demo_issues.py (79%) rename dev/{ => demo}/demo_properties.py (100%) rename dev/{ => demo}/demo_usage_with_logger.py (89%) delete mode 100644 dev/demo_dynamic_analysis.py rename dev/{ => devcheck}/interactive_embed_tests.py (71%) rename dev/{ => maintain}/port_ubelt_utils.py (97%) diff --git a/dev/demo/demo_dynamic_analysis.py b/dev/demo/demo_dynamic_analysis.py new file mode 100644 index 0000000..af12026 --- /dev/null +++ b/dev/demo/demo_dynamic_analysis.py @@ -0,0 +1,18 @@ +""" +CommandLine: + xdoctest ~/code/xdoctest/dev/demo/demo_dynamic_analysis.py --analysis=auto + + xdoctest ~/code/xdoctest/dev/demo/demo_dynamic_analysis.py --analysis=dynamic + + xdoctest ~/code/xdoctest/dev/demo/demo_dynamic_analysis.py --xdoc-force-dynamic +""" + + +def func() -> None: + r''' Dynamic doctest + >>> %s + %s + ''' + return + +func.__doc__ %= ('print(1)', '1') diff --git a/dev/demo_errors.py b/dev/demo/demo_errors.py similarity index 79% rename from dev/demo_errors.py rename to dev/demo/demo_errors.py index 12fa530..e89fb6b 100644 --- a/dev/demo_errors.py +++ b/dev/demo/demo_errors.py @@ -9,7 +9,7 @@ def demo1(): """ CommandLine: - xdoctest -m ~/code/xdoctest/dev/demo_errors.py demo1 + xdoctest -m ~/code/xdoctest/dev/demo/demo_errors.py demo1 Example: >>> raise Exception('demo1') @@ -20,7 +20,7 @@ def demo1(): def demo2(): """ CommandLine: - xdoctest -m ~/code/xdoctest/dev/demo_errors.py demo2 + xdoctest -m ~/code/xdoctest/dev/demo/demo_errors.py demo2 Example: >>> print('error on different line') @@ -32,7 +32,7 @@ def demo2(): def demo3(): """ CommandLine: - xdoctest -m ~/code/xdoctest/dev/demo_errors.py demo3 + xdoctest -m ~/code/xdoctest/dev/demo/demo_errors.py demo3 Example: >>> print('demo5') @@ -44,7 +44,7 @@ def demo3(): class Demo5(object): """ CommandLine: - xdoctest -m ~/code/xdoctest/dev/demo_errors.py Demo5 + xdoctest -m ~/code/xdoctest/dev/demo/demo_errors.py Demo5 Example: >>> raise Exception @@ -52,7 +52,7 @@ class Demo5(object): def demo5(self): """ CommandLine: - xdoctest -m ~/code/xdoctest/dev/demo_errors.py Demo5.demo5 + xdoctest -m ~/code/xdoctest/dev/demo/demo_errors.py Demo5.demo5 Example: >>> raise Exception @@ -100,7 +100,7 @@ def demo_runtime_warning(): if __name__ == '__main__': """ CommandLine: - python ~/code/xdoctest/dev/demo_errors.py all + python ~/code/xdoctest/dev/demo/demo_errors.py all """ import xdoctest xdoctest.doctest_module(__file__) diff --git a/dev/demo_issues.py b/dev/demo/demo_issues.py similarity index 79% rename from dev/demo_issues.py rename to dev/demo/demo_issues.py index b6464cf..748c29a 100644 --- a/dev/demo_issues.py +++ b/dev/demo/demo_issues.py @@ -21,16 +21,16 @@ def demo(): # Correctly reports skipped (although an only skipped test report # should probably be yellow) - xdoctest -m dev/demo_issues.py demo_requires_skips_all_v1 + xdoctest -m dev/demo/demo_issues.py demo_requires_skips_all_v1 # Incorrectly reports success - xdoctest -m dev/demo_issues.py demo_requires_skips_all_v2 + xdoctest -m dev/demo/demo_issues.py demo_requires_skips_all_v2 # Correctly reports success - xdoctest -m dev/demo_issues.py demo_requires_skips_all_v2 --cliflag + xdoctest -m dev/demo/demo_issues.py demo_requires_skips_all_v2 --cliflag # Correctly reports success - xdoctest -m dev/demo_issues.py demo_requires_skips_all_v1 --cliflag + xdoctest -m dev/demo/demo_issues.py demo_requires_skips_all_v1 --cliflag """ # Programatic reproduction (notice the first one also reports itself in @@ -40,7 +40,7 @@ def demo(): xdoctest.doctest_callable(demo_requires_skips_all_v2) import sys, ubelt - sys.path.append(ubelt.expandpath('~/code/xdoctest/dev')) + sys.path.append(ubelt.expandpath('~/code/xdoctest/dev/demo')) import demo_issues # Correctly reports skipped diff --git a/dev/demo_properties.py b/dev/demo/demo_properties.py similarity index 100% rename from dev/demo_properties.py rename to dev/demo/demo_properties.py diff --git a/dev/demo_usage_with_logger.py b/dev/demo/demo_usage_with_logger.py similarity index 89% rename from dev/demo_usage_with_logger.py rename to dev/demo/demo_usage_with_logger.py index ca8630a..3bac805 100644 --- a/dev/demo_usage_with_logger.py +++ b/dev/demo/demo_usage_with_logger.py @@ -6,13 +6,13 @@ CommandLine: # Run with xdoctest runner - xdoctest ~/code/xdoctest/dev/demo_usage_with_logger.py + xdoctest ~/code/xdoctest/dev/demo/demo_usage_with_logger.py # Run with pytest runner - pytest -s --xdoctest --xdoctest-verbose=3 ~/code/xdoctest/dev/demo_usage_with_logger.py + pytest -s --xdoctest --xdoctest-verbose=3 ~/code/xdoctest/dev/demo/demo_usage_with_logger.py # Run with builtin main - python ~/code/xdoctest/dev/demo_usage_with_logger.py + python ~/code/xdoctest/dev/demo/demo_usage_with_logger.py References: .. [Issue111] https://github.com/Erotemic/xdoctest/issues/111 diff --git a/dev/demo_dynamic_analysis.py b/dev/demo_dynamic_analysis.py deleted file mode 100644 index bfce084..0000000 --- a/dev/demo_dynamic_analysis.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -CommandLine: - xdoctest ~/code/xdoctest/dev/demo_dynamic_analysis.py --analysis=auto - - xdoctest ~/code/xdoctest/dev/demo_dynamic_analysis.py --analysis=dynamic - - xdoctest ~/code/xdoctest/dev/demo_dynamic_analysis.py --xdoc-force-dynamic -""" - - -def func() -> None: - r''' Dynamic doctest - >>> %s - %s - ''' - return - -func.__doc__ %= ('print(1)', '1') diff --git a/dev/interactive_embed_tests.py b/dev/devcheck/interactive_embed_tests.py similarity index 71% rename from dev/interactive_embed_tests.py rename to dev/devcheck/interactive_embed_tests.py index 6b843d4..a86140a 100644 --- a/dev/interactive_embed_tests.py +++ b/dev/devcheck/interactive_embed_tests.py @@ -3,7 +3,7 @@ def interative_test_xdev_embed(): """ CommandLine: - xdoctest -m dev/interactive_embed_tests.py interative_test_xdev_embed + xdoctest -m dev/demo/interactive_embed_tests.py interative_test_xdev_embed Example: >>> interative_test_xdev_embed() @@ -16,7 +16,7 @@ def interative_test_xdev_embed(): def interative_test_ipdb_embed(): """ CommandLine: - xdoctest -m dev/interactive_embed_tests.py interative_test_ipdb_embed + xdoctest -m dev/demo/interactive_embed_tests.py interative_test_ipdb_embed Example: >>> interative_test_ipdb_embed() diff --git a/dev/port_ubelt_utils.py b/dev/maintain/port_ubelt_utils.py similarity index 97% rename from dev/port_ubelt_utils.py rename to dev/maintain/port_ubelt_utils.py index a89c5e6..a45682a 100644 --- a/dev/port_ubelt_utils.py +++ b/dev/maintain/port_ubelt_utils.py @@ -65,6 +65,6 @@ def _autogen_xdoctest_utils(): if __name__ == '__main__': """ CommandLine: - python ~/code/xdoctest/dev/port_ubelt_utils.py + python ~/code/xdoctest/dev/maintain/port_ubelt_utils.py """ _autogen_xdoctest_utils() From cb690fd6f4804c90192212c298a9e05786007215 Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 7 Jun 2024 20:10:36 -0400 Subject: [PATCH 5/6] Regenerate util_import --- src/xdoctest/utils/util_import.py | 99 ++++++++++++++++--------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/src/xdoctest/utils/util_import.py b/src/xdoctest/utils/util_import.py index 43a44c8..24b47e3 100644 --- a/src/xdoctest/utils/util_import.py +++ b/src/xdoctest/utils/util_import.py @@ -62,6 +62,37 @@ def _importlib_import_modpath(modpath): # nocover return module +def _pkgutil_modname_to_modpath(modname): # nocover + """ + faster version of :func:`_syspath_modname_to_modpath` using builtin python + mechanisms, but unfortunately it doesn't play nice with pytest. + + Note: + pkgutil.find_loader is deprecated in 3.12 and removed in 3.14 + + Args: + modname (str): the module name. + + Example: + >>> # xdoctest: +SKIP + >>> modname = 'xdoctest.static_analysis' + >>> _pkgutil_modname_to_modpath(modname) + ...static_analysis.py + >>> # xdoctest: +REQUIRES(CPython) + >>> _pkgutil_modname_to_modpath('_ctypes') + ..._ctypes... + + Ignore: + >>> _pkgutil_modname_to_modpath('cv2') + """ + import pkgutil + loader = pkgutil.find_loader(modname) + if loader is None: + raise Exception('No module named {} in the PYTHONPATH'.format(modname)) + modpath = loader.get_filename().replace('.pyc', '.py') + return modpath + + class PythonPathContext(object): """ Context for temporarily adding a dir to the PYTHONPATH. @@ -293,13 +324,14 @@ def import_module_from_path(modpath, index=-1): >>> assert module.testvar == 1 Example: - >>> # xdoctest: +SKIP("ubelt dependency") >>> import pytest + >>> # xdoctest: +SKIP("ubelt dependency") >>> with pytest.raises(IOError): >>> ub.import_module_from_path('does-not-exist') >>> with pytest.raises(IOError): >>> ub.import_module_from_path('does-not-exist.zip/') """ + modpath = os.fspath(modpath) if not os.path.exists(modpath): import re import zipimport @@ -453,6 +485,13 @@ def _static_parse(varname, fpath): """ Statically parse the a constant variable from a python file + Args: + varname (str): variable name to extract + fpath (str | PathLike): path to python file to parse + + Returns: + Any: the static value + Example: >>> # xdoctest: +SKIP("ubelt dependency") >>> dpath = ub.Path.appdir('tests/import/staticparse').ensuredir() @@ -476,6 +515,10 @@ def _static_parse(varname, fpath): >>> with pytest.raises(AttributeError): >>> fpath.write_text('a = list(range(10))') >>> assert _static_parse('c', fpath) is None + >>> if sys.version_info[0:2] >= (3, 6): + >>> # Test with type annotations + >>> fpath.write_text('b: int = 10') + >>> assert _static_parse('b', fpath) == 10 """ import ast @@ -488,9 +531,16 @@ def _static_parse(varname, fpath): class StaticVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: - if getattr(target, 'id', None) == varname: + target_id = getattr(target, 'id', None) + if target_id == varname: self.static_value = _parse_static_node_value(node.value) + def visit_AnnAssign(self, node): + target = node.target + target_id = getattr(target, 'id', None) + if target_id == varname: + self.static_value = _parse_static_node_value(node.value) + visitor = StaticVisitor() visitor.visit(pt) try: @@ -744,51 +794,6 @@ def check_dpath(dpath): return found_modpath -def _importlib_modname_to_modpath(modname): # nocover - import importlib.util - spec = importlib.util.find_spec(modname) - print(f'spec={spec}') - modpath = spec.origin.replace('.pyc', '.py') - return modpath - - -def _pkgutil_modname_to_modpath(modname): # nocover - """ - faster version of :func:`_syspath_modname_to_modpath` using builtin python - mechanisms, but unfortunately it doesn't play nice with pytest. - - Note: - pkgutil.find_loader is deprecated in 3.12 and removed in 3.14 - - Args: - modname (str): the module name. - - Example: - >>> # xdoctest: +SKIP - >>> modname = 'xdoctest.static_analysis' - >>> _pkgutil_modname_to_modpath(modname) - ...static_analysis.py - >>> # xdoctest: +REQUIRES(CPython) - >>> _pkgutil_modname_to_modpath('_ctypes') - ..._ctypes... - - Ignore: - >>> _pkgutil_modname_to_modpath('cv2') - """ - import pkgutil - loader = pkgutil.find_loader(modname) - if loader is None: - raise Exception('No module named {} in the PYTHONPATH'.format(modname)) - try: - modpath = loader.get_filename().replace('.pyc', '.py') - except Exception: - print('Issue in _pkgutil_modname_to_modpath') - print(f'loader = {loader!r}') - print(f'modname = {modname!r}') - raise - return modpath - - def modname_to_modpath(modname, hide_init=True, hide_main=False, sys_path=None): """ Finds the path to a python module from its name. From 2749cabe26ec3230c84ee125c9842605f2702626 Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 7 Jun 2024 20:11:26 -0400 Subject: [PATCH 6/6] Add note about fix in regened ubelt code --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b8c00..67b37e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed * Minor modification to `xdoctest --version-info` and exposed it in CLI help. +### Fixed +* `ub.modname_to_modpath` fixed in cases where editable installs use type annotations in their MAPPING definition. + ## Version 1.1.4 - Released 2024-05-31