From f212145cd30ebc0e383abfcdc3f0eff04a0a79e0 Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 13 Jan 2023 15:51:11 -0500 Subject: [PATCH 1/4] Additional check for bad decorators --- src/xdoctest/directive.py | 1 + tests/test_cases.py | 45 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/xdoctest/directive.py b/src/xdoctest/directive.py index 87beac4c..21777d98 100644 --- a/src/xdoctest/directive.py +++ b/src/xdoctest/directive.py @@ -675,6 +675,7 @@ def _is_requires_satisfied(arg, argv=None, environ=None): Example: >>> from xdoctest.directive import * # NOQA + >>> from xdoctest.directive import _is_requires_satisfied >>> _is_requires_satisfied('PY2', argv=[]) >>> _is_requires_satisfied('PY3', argv=[]) >>> _is_requires_satisfied('cpython', argv=[]) diff --git a/tests/test_cases.py b/tests/test_cases.py index dec15e58..5845fd3b 100644 --- a/tests/test_cases.py +++ b/tests/test_cases.py @@ -113,7 +113,7 @@ def test(self, s): assert 'running 0 test' in text -def test_correct_skipping_on_decorators(): +def test_correct_skipping_on_decorators1(): """ This is a weird case similar to the torch dispatch doctest @@ -180,3 +180,46 @@ def dispatch(*types, **kwargs): runner.doctest_module(modpath, 'all', argv=[''], config=config) print(cap.text) assert '1 skipped' in cap.text + + +def test_correct_skipping_on_decorators_simple(): + """ + minimal test for decorator skips + """ + + import xdoctest + from xdoctest import runner + from os.path import join + + source = utils.codeblock( + ''' + def _my_decorator(): + """ + Example: + >>> # xdoctest: +SKIP + >>> @_my_decorator() + ... def my_func(x): + ... ... + >>> f(3) + """ + return + ''') + + config = { + 'style': 'google', + } + temp = utils.TempDir() + dpath = temp.ensure() + with temp as temp: + modpath = join(dpath, 'test_example_run.py') + + with open(modpath, 'w') as file: + file.write(source) + + examples = list(xdoctest.core.parse_doctestables(modpath, style='google', analysis='static')) + print(f'examples={examples}') + + with utils.CaptureStdout() as cap: + runner.doctest_module(modpath, 'all', argv=[''], config=config) + print(cap.text) + assert '1 skipped' in cap.text From ede9d41c405118dda50c5d3c167d8930f9ed3504 Mon Sep 17 00:00:00 2001 From: joncrall Date: Fri, 13 Jan 2023 15:54:53 -0500 Subject: [PATCH 2/4] Update CI --- .github/workflows/tests.yml | 59 +++++++++++++++++++++++++++++-------- .readthedocs.yml | 23 +++------------ dev/setup_secrets.sh | 9 ++++++ requirements.txt | 4 +-- 4 files changed, 61 insertions(+), 34 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 66ec35f3..84463af1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout source uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 with: python-version: 3.8 - name: Install dependencies @@ -40,7 +40,7 @@ jobs: - name: Checkout source uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 with: python-version: 3.8 - name: Upgrade pip @@ -97,96 +97,123 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - arch: - - auto include: - python-version: '3.6' - os: ubuntu-latest + os: ubuntu-20.04 install-extras: tests-strict,runtime-strict + arch: auto - python-version: '3.6' os: macOS-latest install-extras: tests-strict,runtime-strict + arch: auto - python-version: '3.6' os: windows-latest install-extras: tests-strict,runtime-strict + arch: auto - python-version: '3.11' os: ubuntu-latest install-extras: tests-strict,runtime-strict,optional-strict + arch: auto - python-version: '3.11' os: macOS-latest install-extras: tests-strict,runtime-strict,optional-strict + arch: auto - python-version: '3.11' os: windows-latest install-extras: tests-strict,runtime-strict,optional-strict + arch: auto - python-version: '3.11' os: macOS-latest install-extras: tests + arch: auto - python-version: '3.11' os: windows-latest install-extras: tests + arch: auto - python-version: '3.6' - os: ubuntu-latest + os: ubuntu-20.04 install-extras: tests,optional + arch: auto - python-version: '3.7' os: ubuntu-latest install-extras: tests,optional + arch: auto - python-version: '3.8' os: ubuntu-latest install-extras: tests,optional + arch: auto - python-version: '3.9' os: ubuntu-latest install-extras: tests,optional + arch: auto - python-version: '3.10' os: ubuntu-latest install-extras: tests,optional + arch: auto - python-version: '3.11' os: ubuntu-latest install-extras: tests,optional + arch: auto - python-version: '3.6' os: macOS-latest install-extras: tests,optional + arch: auto - python-version: '3.7' os: macOS-latest install-extras: tests,optional + arch: auto - python-version: '3.8' os: macOS-latest install-extras: tests,optional + arch: auto - python-version: '3.9' os: macOS-latest install-extras: tests,optional + arch: auto - python-version: '3.10' os: macOS-latest install-extras: tests,optional + arch: auto - python-version: '3.11' os: macOS-latest install-extras: tests,optional + arch: auto - python-version: '3.6' os: windows-latest install-extras: tests,optional + arch: auto - python-version: '3.7' os: windows-latest install-extras: tests,optional + arch: auto - python-version: '3.8' os: windows-latest install-extras: tests,optional + arch: auto - python-version: '3.9' os: windows-latest install-extras: tests,optional + arch: auto - python-version: '3.10' os: windows-latest install-extras: tests,optional + arch: auto - python-version: '3.11' os: windows-latest install-extras: tests,optional + arch: auto - python-version: pypy-3.7 os: ubuntu-latest install-extras: tests,optional + arch: auto - python-version: pypy-3.7 os: macOS-latest install-extras: tests,optional + arch: auto - python-version: pypy-3.7 os: windows-latest install-extras: tests,optional + arch: auto steps: - name: Checkout source uses: actions/checkout@v3 @@ -204,7 +231,7 @@ jobs: with: platforms: all - name: Setup Python - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 with: python-version: ${{ matrix.python-version }} - name: Build pure wheel @@ -220,9 +247,17 @@ jobs: CI_PYTHON_VERSION: py${{ matrix.python-version }} run: |- # Find the path to the wheel - WHEEL_FPATH=$(ls wheelhouse/xdoctest*.whl) - # Install the wheel - python -m pip install ${WHEEL_FPATH}[${INSTALL_EXTRAS}] + ls wheelhouse + pip install tomli pkginfo + MOD_NAME=xdoctest + echo "MOD_NAME=$MOD_NAME" + WHEEL_FPATH=$(python -c "import pathlib; print(str(sorted(pathlib.Path('wheelhouse').glob('$MOD_NAME*.whl'))[-1]).replace(chr(92), chr(47)))") + echo "WHEEL_FPATH=$WHEEL_FPATH" + MOD_VERSION=$(python -c "from pkginfo import Wheel; print(Wheel('$WHEEL_FPATH').version)") + echo "MOD_VERSION=$MOD_VERSION" + # Install the wheel (ensure we are using the version we just built) + # NOTE: THE VERSION MUST BE NEWER THAN AN EXISTING PYPI VERSION OR THIS MAY FAIL + pip install "$MOD_NAME[$INSTALL_EXTRAS]==$MOD_VERSION" -f wheelhouse # Create a sandboxed directory WORKSPACE_DNAME="testdir_${CI_PYTHON_VERSION}_${GITHUB_RUN_ID}_${RUNNER_OS}" mkdir -p $WORKSPACE_DNAME @@ -230,7 +265,7 @@ jobs: # Get the path to the installed package and run the tests MOD_DPATH=$(python -c "import xdoctest, os; print(os.path.dirname(xdoctest.__file__))") echo "MOD_DPATH = $MOD_DPATH" - python -m pytest -p pytester -p no:doctest --xdoctest --cov-config ../pyproject.toml --cov-report term --cov=xdoctest $MOD_DPATH ../tests + python -m pytest -p pytester -p no:doctest --xdoctest --cov-config ../pyproject.toml --cov-report term --cov="$MOD_NAME" "$MOD_DPATH" ../tests # Move coverage file to a new name mv .coverage "../.coverage.$WORKSPACE_DNAME" cd .. @@ -238,7 +273,7 @@ jobs: shell: bash run: ls -la wheelhouse - name: Set up Python 3.8 to combine coverage Linux - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 if: runner.os == 'Linux' with: python-version: 3.8 diff --git a/.readthedocs.yml b/.readthedocs.yml index 8a1cff88..01bd0b26 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,33 +1,18 @@ # .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details -# +# # See Also: # https://readthedocs.org/dashboard/xdoctest/advanced/ # Required version: 2 - -# Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py - -# Build documentation with MkDocs -#mkdocs: -# configuration: mkdocs.yml - -# Optionally build your docs in additional formats such as PDF and ePub formats: all - -# Optionally set the version of Python and requirements required to build your docs python: version: 3.7 install: - - requirements: requirements/docs.txt - - method: pip - path: . - #extra_requirements: - # - docs - -#conda: -# environment: environment.yml + - requirements: requirements/docs.txt + - method: pip + path: . diff --git a/dev/setup_secrets.sh b/dev/setup_secrets.sh index 7cfdc0da..1ead971d 100644 --- a/dev/setup_secrets.sh +++ b/dev/setup_secrets.sh @@ -393,6 +393,15 @@ export_encrypted_code_signing_keys(){ MAIN_GPG_KEYID=$(gpg --list-keys --keyid-format LONG "$GPG_IDENTIFIER" | head -n 2 | tail -n 1 | awk '{print $1}') GPG_SIGN_SUBKEY=$(gpg --list-keys --with-subkey-fingerprints "$GPG_IDENTIFIER" | grep "\[S\]" -A 1 | tail -n 1 | awk '{print $1}') + # Careful, if you don't have a subkey, requesting it will export more than you want. + # Export the main key instead (its better to have subkeys, but this is a lesser evil) + if [[ "$GPG_SIGN_SUBKEY" == "" ]]; then + # NOTE: if you get here this probably means your subkeys expired (and + # wont even be visible), so we probably should check for that here and + # thrown an error instead of using this hack, which likely wont work + # anyway. + GPG_SIGN_SUBKEY=$(gpg --list-keys --with-subkey-fingerprints "$GPG_IDENTIFIER" | grep "\[C\]" -A 1 | tail -n 1 | awk '{print $1}') + fi echo "MAIN_GPG_KEYID = $MAIN_GPG_KEYID" echo "GPG_SIGN_SUBKEY = $GPG_SIGN_SUBKEY" diff --git a/requirements.txt b/requirements.txt index 58c4e6d0..bc18a503 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -r requirements/runtime.txt -r requirements/tests.txt --r requirements/tests-binary.txt --r requirements/jupyter.txt --r requirements/colors.txt +-r requirements/optional.txt From fe624a7619d4782b94329c78e830746149da252a Mon Sep 17 00:00:00 2001 From: joncrall Date: Sun, 29 Jan 2023 15:33:47 -0500 Subject: [PATCH 3/4] Update proposal --- dev/extension_proposal.py | 81 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/dev/extension_proposal.py b/dev/extension_proposal.py index e6c3ffc2..76029ba4 100644 --- a/dev/extension_proposal.py +++ b/dev/extension_proposal.py @@ -79,4 +79,85 @@ Again, the problem of distinguishing inputs / outputs persists. + +2023-01-27: New idea: backticks + + +Syntax Idea #1: Use markdown with explicit python tags + +def foobar(): + ''' + Example: + ```python + x = 1 + y = 2 + print(x + y) + ``` + + ```output + 3 + ``` + + ''' + + +Syntax Idea #2: Use a simplified markdown tag that is reminicent of markdown + +def foobar(): + ''' + Example: + `python + x = 1 + y = 2 + print(x + y) + + `output + 3 + + ''' + + +Syntax Idea #3: Lead output with an xdoctest directive + +def foobar(): + ''' + Example: + # xdoctest: example + x = 1 + y = 2 + print(x + y) + + # xdoctest: output + 3 + ''' + + +Shorter prefixes? + +def foobar(): + ''' + Example: + > x = 1 + > y = 2 + > print(x + y) + 3 + ''' + + +First line matters? +def foobar(): + ''' + Example: + + .. code:: python + + x = 1 + y = 2 + print(x + y) + + .. code:: output + + 3 + ''' + """ From c97a5a3be24f194f411a770faf6de78139b0a224 Mon Sep 17 00:00:00 2001 From: joncrall Date: Sun, 29 Jan 2023 15:34:17 -0500 Subject: [PATCH 4/4] Update xcookie CI Update type annotations Update setup for 311 wip Fixup path ref Update test requirements for 311 Forgot to bump version number Update CCI More verbosity in tests wip add docs got pyproject ignore Does changing package_dir help? readd keywords Try to fix setup.py Fix find-packages dir wip wip --- .circleci/config.yml | 32 +++-- .github/workflows/tests.yml | 120 ++++++++++------ CHANGELOG.md | 2 + dev/port_ubelt_utils.py | 3 +- pyproject.toml | 9 +- requirements/tests-binary.txt | 16 ++- setup.py | 221 ++++++++++++++++------------- src/xdoctest/__init__.py | 2 +- src/xdoctest/core.py | 54 +++---- src/xdoctest/core.pyi | 20 +-- src/xdoctest/directive.py | 8 +- src/xdoctest/directive.pyi | 4 +- src/xdoctest/doctest_example.pyi | 12 +- src/xdoctest/doctest_part.pyi | 6 +- src/xdoctest/exceptions.py | 6 +- src/xdoctest/exceptions.pyi | 13 +- src/xdoctest/plugin.pyi | 10 +- src/xdoctest/runner.pyi | 4 +- src/xdoctest/static_analysis.pyi | 9 +- src/xdoctest/utils/util_import.py | 6 +- src/xdoctest/utils/util_import.pyi | 6 +- 21 files changed, 329 insertions(+), 234 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36241cb6..436d47df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -118,15 +118,17 @@ jobs: name: run tests command: | . venv/bin/activate + ls + $PYTHON_EXE -m pip freeze $PYTHON_EXE run_tests.py # Upload to codecov.io (requires a CODECOV_TOKEN environ or github+circleci integration) - codecov - - store_artifacts: - path: test-reports - destination: test-reports - - store_artifacts: - path: .coverage - destination: .coverage + #codecov + #- store_artifacts: + # path: test-reports + # destination: test-reports + #- store_artifacts: + # path: .coverage + # destination: .coverage .test_full_template: &test_full_template @@ -160,15 +162,17 @@ jobs: name: run tests command: | . venv/bin/activate + ls + $PYTHON_EXE -m pip freeze $PYTHON_EXE run_tests.py # Upload to codecov.io (requires a CODECOV_TOKEN environ or github+circleci integration) - codecov - - store_artifacts: - path: test-reports - destination: test-reports - - store_artifacts: - path: .coverage - destination: .coverage + #codecov + #- store_artifacts: + # path: test-reports + # destination: test-reports + #- store_artifacts: + # path: .coverage + # destination: .coverage ################################### diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 84463af1..eba0abc0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,7 @@ # Based on ~/code/xcookie/xcookie/rc/tests.yml.in # Now based on ~/code/xcookie/xcookie/builders/github_actions.py -name: PurePy Build and Test +name: PurePyCI on: push: @@ -16,10 +16,10 @@ jobs: steps: - name: Checkout source uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python 3.11 for linting uses: actions/setup-python@v4.5.0 with: - python-version: 3.8 + python-version: '3.11' - name: Install dependencies run: |- python -m pip install --upgrade pip @@ -34,15 +34,15 @@ jobs: mypy --install-types --non-interactive ./src/xdoctest mypy ./src/xdoctest build_and_test_sdist: - name: Test sdist Python 3.8 + name: Build sdist runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python 3.11 uses: actions/setup-python@v4.5.0 with: - python-version: 3.8 + python-version: '3.11' - name: Upgrade pip run: |- python -m pip install --upgrade pip @@ -57,7 +57,7 @@ jobs: - name: Install sdist run: |- ls -al ./wheelhouse - pip install wheelhouse/xdoctest*.tar.gz -v + pip install --prefer-binary wheelhouse/xdoctest*.tar.gz -v - name: Test minimal loose sdist run: |- pwd @@ -92,9 +92,48 @@ jobs: with: name: wheels path: ./wheelhouse/*.tar.gz - build_and_test_purepy_wheels: + build_purepy_wheels: name: ${{ matrix.python-version }} on ${{ matrix.os }}, arch=${{ matrix.arch }} with ${{ matrix.install-extras }} runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + python-version: + - '3.11' + arch: + - auto + steps: + - name: Checkout source + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + if: runner.os == 'Linux' && matrix.arch != 'auto' + with: + platforms: all + - name: Setup Python + uses: actions/setup-python@v4.5.0 + with: + python-version: ${{ matrix.python-version }} + - name: Build pure wheel + shell: bash + run: |- + python -m pip install pip -U + python -m pip install setuptools>=0.8 build + python -m build --wheel --outdir wheelhouse + - name: Show built files + shell: bash + run: ls -la wheelhouse + - uses: actions/upload-artifact@v3 + name: Upload wheels artifact + with: + name: wheels + path: ./wheelhouse/xdoctest*.whl + test_purepy_wheels: + name: ${{ matrix.python-version }} on ${{ matrix.os }}, arch=${{ matrix.arch }} with ${{ matrix.install-extras }} + runs-on: ${{ matrix.os }} + needs: + - build_purepy_wheels strategy: matrix: include: @@ -219,12 +258,12 @@ jobs: uses: actions/checkout@v3 - name: Enable MSVC 64bit uses: ilammy/msvc-dev-cmd@v1 - if: matrix.os == 'windows-latest' && matrix.cibw_skip == '*-win32' - - name: Enable MSVC 32bit - uses: ilammy/msvc-dev-cmd@v1 - if: matrix.os == 'windows-latest' && matrix.cibw_skip == '*-win_amd64' - with: - arch: x86 + if: matrix.os == 'windows-latest' # && ${{ contains(matrix.cibw_skip, '*-win32') }} + #- name: Enable MSVC 32bit + # uses: ilammy/msvc-dev-cmd@v1 + # if: matrix.os == 'windows-latest' && ${{ contains(matrix.cibw_skip, '*-win_amd64') }} + # with: + # arch: x86 - name: Set up QEMU uses: docker/setup-qemu-action@v2 if: runner.os == 'Linux' && matrix.arch != 'auto' @@ -234,20 +273,20 @@ jobs: uses: actions/setup-python@v4.5.0 with: python-version: ${{ matrix.python-version }} - - name: Build pure wheel - shell: bash - run: |- - python -m pip install pip -U - python -m pip install setuptools>=0.8 build - python -m build --wheel --outdir wheelhouse + - uses: actions/download-artifact@v3 + name: Download wheels and sdist + with: + name: wheels + path: wheelhouse - name: Test wheel with ${{ matrix.install-extras }} shell: bash env: INSTALL_EXTRAS: ${{ matrix.install-extras }} CI_PYTHON_VERSION: py${{ matrix.python-version }} run: |- - # Find the path to the wheel - ls wheelhouse + echo "Finding the path to the wheel" + ls -al wheelhouse + echo "Installing helpers" pip install tomli pkginfo MOD_NAME=xdoctest echo "MOD_NAME=$MOD_NAME" @@ -255,28 +294,30 @@ jobs: echo "WHEEL_FPATH=$WHEEL_FPATH" MOD_VERSION=$(python -c "from pkginfo import Wheel; print(Wheel('$WHEEL_FPATH').version)") echo "MOD_VERSION=$MOD_VERSION" - # Install the wheel (ensure we are using the version we just built) + echo "Install the wheel (ensureing we are using the version we just built)" # NOTE: THE VERSION MUST BE NEWER THAN AN EXISTING PYPI VERSION OR THIS MAY FAIL - pip install "$MOD_NAME[$INSTALL_EXTRAS]==$MOD_VERSION" -f wheelhouse - # Create a sandboxed directory + pip install --prefer-binary "$MOD_NAME[$INSTALL_EXTRAS]==$MOD_VERSION" -f wheelhouse + echo "Install finished. Creating a sandbox directory to test it" WORKSPACE_DNAME="testdir_${CI_PYTHON_VERSION}_${GITHUB_RUN_ID}_${RUNNER_OS}" + echo "WORKSPACE_DNAME=$WORKSPACE_DNAME" mkdir -p $WORKSPACE_DNAME + echo "cd-ing into the workspace" cd $WORKSPACE_DNAME + pwd + ls -al + pip freeze # Get the path to the installed package and run the tests MOD_DPATH=$(python -c "import xdoctest, os; print(os.path.dirname(xdoctest.__file__))") echo "MOD_DPATH = $MOD_DPATH" + echo "running the pytest command inside the workspace" python -m pytest -p pytester -p no:doctest --xdoctest --cov-config ../pyproject.toml --cov-report term --cov="$MOD_NAME" "$MOD_DPATH" ../tests + echo "pytest command finished, moving the coverage file to the repo root" + ls -al # Move coverage file to a new name mv .coverage "../.coverage.$WORKSPACE_DNAME" + echo "changing directory back to th repo root" cd .. - - name: Show built files - shell: bash - run: ls -la wheelhouse - - name: Set up Python 3.8 to combine coverage Linux - uses: actions/setup-python@v4.5.0 - if: runner.os == 'Linux' - with: - python-version: 3.8 + ls -al - name: Combine coverage Linux if: runner.os == 'Linux' run: |- @@ -295,18 +336,14 @@ jobs: name: Codecov Upload with: file: ./tests/coverage.xml - - uses: actions/upload-artifact@v3 - name: Upload wheels artifact - with: - name: wheels - path: ./wheelhouse/xdoctest*.whl test_deploy: name: Uploading Test to PyPi runs-on: ubuntu-latest if: github.event_name == 'push' && ! startsWith(github.event.ref, 'refs/tags') && ! startsWith(github.event.ref, 'refs/heads/release') needs: - build_and_test_sdist - - build_and_test_purepy_wheels + - build_purepy_wheels + - test_purepy_wheels steps: - name: Checkout source uses: actions/checkout@v3 @@ -349,7 +386,8 @@ jobs: if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags') || startsWith(github.event.ref, 'refs/heads/release')) needs: - build_and_test_sdist - - build_and_test_purepy_wheels + - build_purepy_wheels + - test_purepy_wheels steps: - name: Checkout source uses: actions/checkout@v3 @@ -405,4 +443,4 @@ jobs: # --secret=EROTEMIC_TWINE_USERNAME=$EROTEMIC_TWINE_USERNAME \ # --secret=EROTEMIC_CI_SECRET=$EROTEMIC_CI_SECRET \ # --secret=EROTEMIC_TEST_TWINE_USERNAME=$EROTEMIC_TEST_TWINE_USERNAME \ -# --secret=EROTEMIC_TEST_TWINE_PASSWORD=$EROTEMIC_TEST_TWINE_PASSWORD \ No newline at end of file +# --secret=EROTEMIC_TEST_TWINE_PASSWORD=$EROTEMIC_TEST_TWINE_PASSWORD diff --git a/CHANGELOG.md b/CHANGELOG.md index 1463db45..494e93fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed * Binary tests are now only run on "full" installs to reduce minimal dependencies. +* Support for Python 3.11 +* Minor typing fixes ## Version 1.1.0 - Released 2022-09-05 diff --git a/dev/port_ubelt_utils.py b/dev/port_ubelt_utils.py index d9e72672..e398fada 100644 --- a/dev/port_ubelt_utils.py +++ b/dev/port_ubelt_utils.py @@ -44,7 +44,8 @@ def _autogen_xdoctest_utils(): ''' # -*- coding: utf-8 -*- """ - This file was autogenerated based on code in ubelt + This file was autogenerated based on code in ubelt via + dev/port_ubelt_utils.py in the xdoctest repo """ from __future__ import print_function, division, absolute_import, unicode_literals ''') diff --git a/pyproject.toml b/pyproject.toml index 6fecce5e..6db8489a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ description = "A rewrite of the builtin doctest module" url = "https://github.com/Erotemic/xdoctest" license = "Apache 2" dev_status = "stable" +typed = true classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -29,6 +30,7 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", # Supported Python versions "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.8", @@ -38,6 +40,9 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", ] +[tool.xcookie.setuptools] +keywords = '"xdoctest,doctest,test,docstr,pytest"' + [tool.xcookie.entry_points] # the pytest11 entry point makes the plugin available to pytest pytest11 = [ @@ -50,8 +55,8 @@ console_scripts = [ [tool.pytest.ini_options] -addopts = "-p no:doctest --xdoctest --xdoctest-style=google --ignore-glob=setup.py --ignore-glob=dev" -norecursedirs = ".git ignore build __pycache__ dev _skbuild" +addopts = "-p no:doctest --xdoctest --xdoctest-style=google --ignore-glob=setup.py --ignore-glob=dev --ignore-glob=docs" +norecursedirs = ".git ignore build __pycache__ dev _skbuild docs" filterwarnings = [ "default", "ignore:.*No cfgstr given in Cacher constructor or call.*:Warning", "ignore:.*Define the __nice__ method for.*:Warning", "ignore:.*private pytest class or function.*:Warning",] [tool.coverage.run] diff --git a/requirements/tests-binary.txt b/requirements/tests-binary.txt index f88481e1..4200d6c2 100644 --- a/requirements/tests-binary.txt +++ b/requirements/tests-binary.txt @@ -1,5 +1,13 @@ # For testing doctests in binary extension modules -scikit-build>=0.11.1 -cmake>=3.21.2 -ninja>=1.10.2 -pybind11>=2.7.1 + +scikit-build>=0.16.1 ; python_version < '4.0' and python_version >= '3.11' # Python 3.11+ +scikit-build>=0.11.1 ; python_version < '3.11' # Python <=3.10 + +ninja>=1.11.1 ; python_version < '4.0' and python_version >= '3.11' # Python 3.11+ +ninja>=1.10.2 ; python_version < '3.11' # Python <=3.10 + +pybind11>=2.10.3 ; python_version < '4.0' and python_version >= '3.11' # Python 3.11+ +pybind11>=2.7.1 ; python_version < '3.11' # Python <=3.10 + +cmake>=3.25.0 ; python_version < '4.0' and python_version >= '3.11' # Python 3.11+ +cmake>=3.21.2 ; python_version < '3.11' # Python <=3.10 diff --git a/setup.py b/setup.py index b3e706d4..cfe8ed0a 100755 --- a/setup.py +++ b/setup.py @@ -1,16 +1,18 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from setuptools import setup +# Generated by ~/code/xcookie/xcookie/builders/setup.py +# based on part ~/code/xcookie/xcookie/rc/setup.py.in import sys -from os.path import exists +import re +from os.path import exists, dirname, join from setuptools import find_packages +from setuptools import setup def parse_version(fpath): """ Statically parse the version number from a python file """ - value = static_parse('__version__', fpath) + value = static_parse("__version__", fpath) return value @@ -21,15 +23,15 @@ def static_parse(varname, fpath): import ast if not exists(fpath): - raise ValueError('fpath={!r} does not exist'.format(fpath)) - with open(fpath, 'r') as file_: + raise ValueError("fpath={!r} does not exist".format(fpath)) + with open(fpath, "r") as file_: sourcecode = file_.read() pt = ast.parse(sourcecode) class StaticVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: - if getattr(target, 'id', None) == varname: + if getattr(target, "id", None) == varname: self.static_value = node.value.s visitor = StaticVisitor() @@ -39,7 +41,7 @@ def visit_Assign(self, node): except AttributeError: import warnings - value = 'Unknown {}'.format(varname) + value = "Unknown {}".format(varname) warnings.warn(value) return value @@ -52,18 +54,16 @@ def parse_description(): pandoc --from=markdown --to=rst --output=README.rst README.md python -c "import setup; print(setup.parse_description())" """ - from os.path import dirname, join, exists - - readme_fpath = join(dirname(__file__), 'README.rst') + readme_fpath = join(dirname(__file__), "README.rst") # This breaks on pip install, so check that it exists. if exists(readme_fpath): - with open(readme_fpath, 'r') as f: + with open(readme_fpath, "r") as f: text = f.read() return text - return '' + return "" -def parse_requirements(fname='requirements.txt', versions=False): +def parse_requirements(fname="requirements.txt", versions=False): """ Parse the package dependencies listed in a requirements file but strips specific versioning information. @@ -77,12 +77,9 @@ def parse_requirements(fname='requirements.txt', versions=False): Returns: List[str]: list of requirements items """ - from os.path import exists, dirname, join - import re - require_fpath = fname - def parse_line(line, dpath=''): + def parse_line(line, dpath=""): """ Parse information from a line in a requirements text file @@ -90,147 +87,177 @@ def parse_line(line, dpath=''): line = '-e git+https://a.com/somedep@sometag#egg=SomeDep' """ # Remove inline comments - comment_pos = line.find(' #') + comment_pos = line.find(" #") if comment_pos > -1: line = line[:comment_pos] - if line.startswith('-r '): + if line.startswith("-r "): # Allow specifying requirements in other files - target = join(dpath, line.split(' ')[1]) + target = join(dpath, line.split(" ")[1]) for info in parse_require_file(target): yield info else: # See: https://www.python.org/dev/peps/pep-0508/ - info = {'line': line} - if line.startswith('-e '): - info['package'] = line.split('#egg=')[1] + info = {"line": line} + if line.startswith("-e "): + info["package"] = line.split("#egg=")[1] else: - if ';' in line: - pkgpart, platpart = line.split(';') + if "--find-links" in line: + # setuptools doesnt seem to handle find links + line = line.split("--find-links")[0] + if ";" in line: + pkgpart, platpart = line.split(";") # Handle platform specific dependencies # setuptools.readthedocs.io/en/latest/setuptools.html # #declaring-platform-specific-dependencies plat_deps = platpart.strip() - info['platform_deps'] = plat_deps + info["platform_deps"] = plat_deps else: pkgpart = line platpart = None # Remove versioning from the package - pat = '(' + '|'.join(['>=', '==', '>']) + ')' + pat = "(" + "|".join([">=", "==", ">"]) + ")" parts = re.split(pat, pkgpart, maxsplit=1) parts = [p.strip() for p in parts] - info['package'] = parts[0] + info["package"] = parts[0] if len(parts) > 1: op, rest = parts[1:] version = rest # NOQA - info['version'] = (op, version) + info["version"] = (op, version) yield info def parse_require_file(fpath): dpath = dirname(fpath) - with open(fpath, 'r') as f: + with open(fpath, "r") as f: for line in f.readlines(): line = line.strip() - if line and not line.startswith('#'): + if line and not line.startswith("#"): for info in parse_line(line, dpath=dpath): yield info def gen_packages_items(): if exists(require_fpath): for info in parse_require_file(require_fpath): - parts = [info['package']] - if versions and 'version' in info: - if versions == 'strict': + parts = [info["package"]] + if versions and "version" in info: + if versions == "strict": # In strict mode, we pin to the minimum version - if info['version']: + if info["version"]: # Only replace the first >= instance - verstr = ''.join(info['version']).replace('>=', '==', 1) + verstr = "".join(info["version"]).replace(">=", "==", 1) parts.append(verstr) else: - parts.extend(info['version']) - if not sys.version.startswith('3.4'): + parts.extend(info["version"]) + if not sys.version.startswith("3.4"): # apparently package_deps are broken in 3.4 - plat_deps = info.get('platform_deps') + plat_deps = info.get("platform_deps") if plat_deps is not None: - parts.append(';' + plat_deps) - item = ''.join(parts) + parts.append(";" + plat_deps) + item = "".join(parts) yield item packages = list(gen_packages_items()) return packages -NAME = 'xdoctest' -VERSION = parse_version('src/xdoctest/__init__.py') +# # Maybe use in the future? But has private deps +# def parse_requirements_alt(fpath='requirements.txt', versions='loose'): +# """ +# Args: +# versions (str): can be +# False or "free" - remove all constraints +# True or "loose" - use the greater or equal (>=) in the req file +# strict - replace all greater equal with equals +# """ +# # Note: different versions of pip might have different internals. +# # This may need to be fixed. +# from pip._internal.req import parse_requirements +# from pip._internal.network.session import PipSession +# requirements = [] +# for req in parse_requirements(fpath, session=PipSession()): +# if not versions or versions == 'free': +# req_name = req.requirement.split(' ')[0] +# requirements.append(req_name) +# elif versions == 'loose' or versions is True: +# requirements.append(req.requirement) +# elif versions == 'strict': +# part1, *rest = req.requirement.split(';') +# strict_req = ';'.join([part1.replace('>=', '==')] + rest) +# requirements.append(strict_req) +# else: +# raise KeyError(versions) +# requirements = [r.replace(' ', '') for r in requirements] +# return requirements + -if __name__ == '__main__': +NAME = "xdoctest" +INIT_PATH = "src/xdoctest/__init__.py" +VERSION = parse_version(INIT_PATH) + +if __name__ == "__main__": setupkw = {} - setupkw['install_requires'] = parse_requirements('requirements/runtime.txt') - setupkw['extras_require'] = { - 'all': parse_requirements('requirements.txt'), - 'tests': parse_requirements('requirements/tests.txt'), + + setupkw["install_requires"] = parse_requirements("requirements/runtime.txt") + setupkw["extras_require"] = { + "all": parse_requirements("requirements.txt"), + "tests": parse_requirements("requirements/tests.txt"), + "optional": parse_requirements("requirements/optional.txt"), + "all-strict": parse_requirements("requirements.txt", versions="strict"), + "runtime-strict": parse_requirements( + "requirements/runtime.txt", versions="strict" + ), + "tests-strict": parse_requirements("requirements/tests.txt", versions="strict"), + "optional-strict": parse_requirements( + "requirements/optional.txt", versions="strict" + ), 'tests-binary': parse_requirements('requirements/tests-binary.txt'), - 'optional': parse_requirements('requirements/optional.txt'), - 'all-strict': parse_requirements('requirements.txt', versions='strict'), - 'runtime-strict': parse_requirements('requirements/runtime.txt', versions='strict'), - 'tests-strict': parse_requirements('requirements/tests.txt', versions='strict'), 'tests-binary-strict': parse_requirements('requirements/tests-binary.txt', versions='strict'), - 'optional-strict': parse_requirements('requirements/optional.txt', versions='strict'), 'colors': parse_requirements('requirements/colors.txt'), 'jupyter': parse_requirements('requirements/jupyter.txt'), - } - - setupkw['name'] = NAME - setupkw['version'] = VERSION - setupkw['author'] = 'Jon Crall' - setupkw['author_email'] = 'erotemic@gmail.com' - setupkw['url'] = 'https://github.com/Erotemic/xdoctest' - setupkw['description'] = 'A rewrite of the builtin doctest module' - setupkw['long_description'] = parse_description() - setupkw['long_description_content_type'] = 'text/x-rst' - setupkw['license'] = 'Apache 2' - setupkw['python_requires'] = '>=3.6' - setupkw['classifiers'] = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities', + } + + setupkw["name"] = NAME + setupkw["version"] = VERSION + setupkw["author"] = "Jon Crall" + setupkw["author_email"] = "erotemic@gmail.com" + setupkw["url"] = "https://github.com/Erotemic/xdoctest" + setupkw["description"] = "A rewrite of the builtin doctest module" + setupkw["long_description"] = parse_description() + setupkw["long_description_content_type"] = "text/x-rst" + setupkw["license"] = "Apache 2" + setupkw["packages"] = find_packages("./src") + setupkw["python_requires"] = ">=3.6" + setupkw["classifiers"] = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", 'Topic :: Software Development :: Testing', 'Framework :: Pytest', - # This should be interpreted as Apache License v2.0 - 'License :: OSI Approved :: Apache Software License', - # Supported Python versions + "License :: OSI Approved :: Apache Software License", 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.6', + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: CPython', ] - setupkw['keywords'] = 'xdoctest,doctest,test,docstr,pytest' - # https://codefellows.github.io/sea-python-401d4/lectures/python_packaging_1.html - # We use a key of an empty string to indicate that the directory we are - # pointing to should be considered the root. Then the value is src, telling - # setuptools to use that directory as the root of our source. - setupkw['package_dir'] = { - '': 'src', - } - setupkw['packages'] = find_packages('src') - setupkw['package_data'] = { - 'xdoctest': ['py.typed', '*.pyi'], + setupkw["package_data"] = {"xdoctest": ["py.typed", "*.pyi"]} + setupkw["package_dir"] = { + "": "src", } - setupkw['entry_points'] = { - 'console_scripts': [ - 'xdoctest = xdoctest.__main__:main', + setupkw["entry_points"] = { + "console_scripts": [ + "xdoctest = xdoctest.__main__:main", ], - 'pytest11': [ - 'xdoctest = xdoctest.plugin', + "pytest11": [ + "xdoctest = xdoctest.plugin", ], } - if sys.version_info[0] == 3 and sys.version_info[1] <= 4: - setupkw.pop('long_description_content_type', None) + setupkw["keywords"] = "xdoctest,doctest,test,docstr,pytest" setup(**setupkw) diff --git a/src/xdoctest/__init__.py b/src/xdoctest/__init__.py index 326ffdea..fcc72249 100644 --- a/src/xdoctest/__init__.py +++ b/src/xdoctest/__init__.py @@ -314,7 +314,7 @@ def fib(n): mkinit xdoctest --nomods ''' -__version__ = '1.1.0' +__version__ = '1.1.1' # Expose only select submodules diff --git a/src/xdoctest/core.py b/src/xdoctest/core.py index 86bc80c4..739b4354 100644 --- a/src/xdoctest/core.py +++ b/src/xdoctest/core.py @@ -70,25 +70,26 @@ def parse_freeform_docstr_examples(docstr, callname=None, modpath=None, Args: docstr (str): an extracted docstring - callname (str, default=None): + callname (str | None): the name of the callable (e.g. function, class, or method) that this docstring belongs to. - modpath (str | PathLike, default=None): + modpath (str | PathLike | None): original module the docstring is from - lineno (int, default=1): + lineno (int): the line number (starting from 1) of the docstring. i.e. if you were to go to this line number in the source file the starting - quotes of the docstr would be on this line. + quotes of the docstr would be on this line. Defaults to 1. - fpath (str | PathLike, default=None): + fpath (str | PathLike | None): the file that the docstring is from (if the file was not a module, needed for backwards compatibility) - asone (bool, default=True): + asone (bool): if False doctests are broken into multiple examples based on spacing, otherwise they are executed as a single unit. + Defaults to True. Yields: xdoctest.doctest_example.DocTest : doctest object @@ -240,25 +241,25 @@ def parse_google_docstr_examples(docstr, callname=None, modpath=None, lineno=1, Args: docstr (str): an extracted docstring - callname (str, default=None): + callname (str | None): the name of the callable (e.g. function, class, or method) that this docstring belongs to. - modpath (str | PathLike, default=None): + modpath (str | PathLike | None): original module the docstring is from - lineno (int, default=1): + lineno (int): the line number (starting from 1) of the docstring. i.e. if you were to go to this line number in the source file the starting - quotes of the docstr would be on this line. + quotes of the docstr would be on this line. Defaults to 1. - fpath (str | PathLike, default=None): + fpath (str | PathLike | None): the file that the docstring is from (if the file was not a module, needed for backwards compatibility) - eager_parse (bool, default=True): + eager_parse (bool): if True eagerly evaluate the parser inside the google example - blocks + blocks. Defaults to True. Yields: xdoctest.doctest_example.DocTest : doctest object @@ -319,7 +320,7 @@ def parse_auto_docstr_examples(docstr, *args, **kwargs): def parse_docstr_examples(docstr, callname=None, modpath=None, lineno=1, - style='auto', fpath=None, parser_kw={}): + style='auto', fpath=None, parser_kw=None): """ Parses doctests from a docstr and generates example objects. The style influences which tests are found. @@ -327,26 +328,26 @@ def parse_docstr_examples(docstr, callname=None, modpath=None, lineno=1, Args: docstr (str): a previously extracted docstring - callname (str, default=None): + callname (str | None): the name of the callable (e.g. function, class, or method) that this docstring belongs to. - modpath (str | PathLike, default=None): + modpath (str | PathLike | None): original module the docstring is from - lineno (int, default=1): + lineno (int): the line number (starting from 1) of the docstring. i.e. if you were to go to this line number in the source file the starting - quotes of the docstr would be on this line. + quotes of the docstr would be on this line. Defaults to 1. - style (str, default='auto'): expected doctest style, which can - be "google", "freeform", or "auto". + style (str): expected doctest style, which can + be "google", "freeform", or "auto". Defaults to 'auto'. - fpath (str | PathLike, default=None): + fpath (str | PathLike | None): the file that the docstring is from (if the file was not a module, needed for backwards compatibility) - parser_kw (dict, default={}): passed to the parser + parser_kw (dict | None): passed to the parser as keyword args Yields: xdoctest.doctest_example.DocTest : parsed example @@ -391,6 +392,8 @@ def parse_docstr_examples(docstr, callname=None, modpath=None, lineno=1, n_parsed = 0 try: + if parser_kw is None: + parser_kw = {} for example in parser(docstr, callname=callname, modpath=modpath, fpath=fpath, lineno=lineno, **parser_kw): n_parsed += 1 @@ -458,14 +461,15 @@ def package_calldefs(pkg_identifier, exclude=[], ignore_syntax_errors=True, exclude (List[str]): glob-patterns of file names to exclude - ignore_syntax_errors (bool, default=True): + ignore_syntax_errors (bool): if False raise an error when syntax errors occur in a doctest + Defaults to True. - analysis (str, default='auto'): + analysis (str): if 'static', only static analysis is used to parse call definitions. If 'auto', uses dynamic analysis for compiled python extensions, but static analysis elsewhere, if 'dynamic', then - dynamic analysis is used to parse all calldefs. + dynamic analysis is used to parse all calldefs. Defaults to 'auto'. Yields: Tuple[Dict[str, xdoctest.static_analysis.CallDefNode], str | ModuleType] - diff --git a/src/xdoctest/core.pyi b/src/xdoctest/core.pyi index 87c45780..ed30b0bf 100644 --- a/src/xdoctest/core.pyi +++ b/src/xdoctest/core.pyi @@ -13,10 +13,10 @@ DOCTEST_STYLES: Incomplete def parse_freeform_docstr_examples( docstr: str, - callname: str = None, - modpath: Union[str, PathLike] = None, + callname: Union[str, None] = None, + modpath: Union[str, PathLike, None] = None, lineno: int = 1, - fpath: Union[str, PathLike] = None, + fpath: Union[str, PathLike, None] = None, asone: bool = True ) -> Generator[xdoctest.doctest_example.DocTest, None, Any]: ... @@ -24,10 +24,10 @@ def parse_freeform_docstr_examples( def parse_google_docstr_examples( docstr: str, - callname: str = None, - modpath: Union[str, PathLike] = None, + callname: Union[str, None] = None, + modpath: Union[str, PathLike, None] = None, lineno: int = 1, - fpath: Union[str, PathLike] = None, + fpath: Union[str, PathLike, None] = None, eager_parse: bool = True ) -> Generator[xdoctest.doctest_example.DocTest, None, None]: ... @@ -40,12 +40,12 @@ def parse_auto_docstr_examples(docstr, *args, def parse_docstr_examples( docstr: str, - callname: str = None, - modpath: Union[str, PathLike] = None, + callname: Union[str, None] = None, + modpath: Union[str, PathLike, None] = None, lineno: int = 1, style: str = 'auto', - fpath: Union[str, PathLike] = None, - parser_kw: dict = ... + fpath: Union[str, PathLike, None] = None, + parser_kw: Union[dict, None] = None ) -> Generator[xdoctest.doctest_example.DocTest, None, None]: ... diff --git a/src/xdoctest/directive.py b/src/xdoctest/directive.py index 21777d98..9f7d3f74 100644 --- a/src/xdoctest/directive.py +++ b/src/xdoctest/directive.py @@ -527,9 +527,9 @@ def effects(self, argv=None, environ=None): This is called by :func:`RuntimeState.update` to update itself Args: - argv (List[str], default=None): + argv (List[str] | None): if specified, overwrite sys.argv - environ (Dict[str, str], default=None): + environ (Dict[str, str] | None): if specified, overwrite os.environ Returns: @@ -667,8 +667,8 @@ def _is_requires_satisfied(arg, argv=None, environ=None): Args: arg (str): condition code - argv (List[str]): cmdline if arg is cmd code usually ``sys.argv`` - environ (Dict[str, str]): environment variables usually ``os.environ`` + argv (List[str] | None): cmdline if arg is cmd code usually ``sys.argv`` + environ (Dict[str, str] | None): environment variables usually ``os.environ`` Returns: bool: flag - True if the requirement is met diff --git a/src/xdoctest/directive.pyi b/src/xdoctest/directive.pyi index 7971c054..a1f0e86b 100644 --- a/src/xdoctest/directive.pyi +++ b/src/xdoctest/directive.pyi @@ -74,8 +74,8 @@ class Directive(utils.NiceRepr): ... def effects(self, - argv: List[str] = None, - environ: Dict[str, str] = None) -> List[Effect]: + argv: Union[List[str], None] = None, + environ: Union[Dict[str, str], None] = None) -> List[Effect]: ... diff --git a/src/xdoctest/doctest_example.pyi b/src/xdoctest/doctest_example.pyi index 3782977b..4bc891d5 100644 --- a/src/xdoctest/doctest_example.pyi +++ b/src/xdoctest/doctest_example.pyi @@ -16,7 +16,7 @@ class DoctestConfig(dict): def __init__(self, *args, **kwargs) -> None: ... - def getvalue(self, key: str, given: Any = None) -> Any: + def getvalue(self, key: str, given: Union[Any, None] = None) -> Any: ... @@ -80,22 +80,24 @@ class DocTest: linenos: bool = True, colored: Union[bool, None] = None, want: bool = True, - offset_linenos: bool = None, + offset_linenos: Union[bool, None] = None, prefix: bool = True) -> Generator[Any, None, None]: ... def format_src(self, linenos: bool = True, - colored: bool = None, + colored: Union[bool, None] = None, want: bool = True, - offset_linenos: bool = None, + offset_linenos: Union[bool, None] = None, prefix: bool = True) -> str: ... def anything_ran(self) -> bool: ... - def run(self, verbose: int = None, on_error: str = None) -> Dict: + def run(self, + verbose: Union[int, None] = None, + on_error: Union[str, None] = None) -> Dict: ... @property diff --git a/src/xdoctest/doctest_part.pyi b/src/xdoctest/doctest_part.pyi index 9735322a..16cbcdbc 100644 --- a/src/xdoctest/doctest_part.pyi +++ b/src/xdoctest/doctest_part.pyi @@ -58,15 +58,15 @@ class DoctestPart: def check(part, got_stdout: str, got_eval: str = ..., - runstate: directive.RuntimeState = None, - unmatched: list = None) -> None: + runstate: Union[directive.RuntimeState, None] = None, + unmatched: Union[list, None] = None) -> None: ... def format_part(self, linenos: bool = True, want: bool = True, startline: int = 1, - n_digits: int = None, + n_digits: Union[int, None] = None, colored: bool = False, partnos: bool = False, prefix: bool = True) -> str: diff --git a/src/xdoctest/exceptions.py b/src/xdoctest/exceptions.py index f4e1f60f..d22cdb1a 100644 --- a/src/xdoctest/exceptions.py +++ b/src/xdoctest/exceptions.py @@ -19,9 +19,9 @@ def __init__(self, msg, string=None, info=None, orig_ex=None): """ Args: msg (str): error message - string (str): the string that failed - info (Any): extra information - orig_ex (Exception): The underlying exceptoin + string (str | None): the string that failed + info (Any | None): extra information + orig_ex (Exception | None): The underlying exceptoin """ super(DoctestParseError, self).__init__(msg) self.msg = msg diff --git a/src/xdoctest/exceptions.pyi b/src/xdoctest/exceptions.pyi index 10989cdb..0e0baf95 100644 --- a/src/xdoctest/exceptions.pyi +++ b/src/xdoctest/exceptions.pyi @@ -1,3 +1,4 @@ +from typing import Union from typing import Any from typing import Any @@ -8,15 +9,15 @@ class MalformedDocstr(Exception): class DoctestParseError(Exception): msg: str - string: str - info: Any - orig_ex: Exception + string: Union[str, None] + info: Union[Any, None] + orig_ex: Union[Exception, None] def __init__(self, msg: str, - string: str = None, - info: Any = None, - orig_ex: Exception = None) -> None: + string: Union[str, None] = None, + info: Union[Any, None] = None, + orig_ex: Union[Exception, None] = None) -> None: ... diff --git a/src/xdoctest/plugin.pyi b/src/xdoctest/plugin.pyi index 4c5a89a8..44694715 100644 --- a/src/xdoctest/plugin.pyi +++ b/src/xdoctest/plugin.pyi @@ -40,10 +40,12 @@ class XDoctestItem(pytest.Item): obj: Incomplete fixture_request: Incomplete - def __init__(self, - name: str, - parent: Union[Any, None], - example: xdoctest.doctest_example.DocTest = None) -> None: + def __init__( + self, + name: str, + parent: Union[Any, None], + example: Union[xdoctest.doctest_example.DocTest, + None] = None) -> None: ... def setup(self) -> None: diff --git a/src/xdoctest/runner.pyi b/src/xdoctest/runner.pyi index 617d50fd..26dd4d13 100644 --- a/src/xdoctest/runner.pyi +++ b/src/xdoctest/runner.pyi @@ -24,12 +24,12 @@ def gather_doctests(doctest_identifiers, def doctest_module(module_identifier: Union[str, ModuleType, None] = None, - command: str = None, + command: Union[str, None] = None, argv: Union[List[str], None] = None, exclude: List[str] = ..., style: str = 'auto', verbose: Union[int, None] = None, - config: Dict[str, object] = None, + config: Union[Dict[str, object], None] = None, durations: Union[int, None] = None, analysis: str = 'auto') -> Dict[str, Any]: ... diff --git a/src/xdoctest/static_analysis.pyi b/src/xdoctest/static_analysis.pyi index ef72bf7f..21199f19 100644 --- a/src/xdoctest/static_analysis.pyi +++ b/src/xdoctest/static_analysis.pyi @@ -71,8 +71,9 @@ class TopLevelVisitor(ast.NodeVisitor): ... -def parse_static_calldefs(source: str = None, - fpath: str = None) -> Dict[str, CallDefNode]: +def parse_static_calldefs( + source: Union[str, None] = None, + fpath: Union[str, None] = None) -> Dict[str, CallDefNode]: ... @@ -82,8 +83,8 @@ def parse_calldefs(source: Incomplete | None = ..., def parse_static_value(key: str, - source: str = None, - fpath: str = None) -> object: + source: Union[str, None] = None, + fpath: Union[str, None] = None) -> object: ... diff --git a/src/xdoctest/utils/util_import.py b/src/xdoctest/utils/util_import.py index d7929f2d..24f4eb5f 100644 --- a/src/xdoctest/utils/util_import.py +++ b/src/xdoctest/utils/util_import.py @@ -26,8 +26,8 @@ def is_modname_importable(modname, sys_path=None, exclude=None): Args: modname (str): name of module to check - sys_path (list, default=None): if specified overrides ``sys.path`` - exclude (list): list of directory paths. if specified prevents these + sys_path (list | None, default=None): if specified overrides ``sys.path`` + exclude (list | None): list of directory paths. if specified prevents these directories from being searched. Returns: @@ -859,7 +859,7 @@ def modpath_to_modname(modpath, hide_init=True, hide_main=False, check=True, hide_main (bool, default=False): removes the __main__ suffix check (bool, default=True): if False, does not raise an error if modpath is a dir and does not contain an __init__ file. - relativeto (str, default=None): if specified, all checks are ignored + relativeto (str | None, default=None): if specified, all checks are ignored and this is considered the path to the root module. TODO: diff --git a/src/xdoctest/utils/util_import.pyi b/src/xdoctest/utils/util_import.pyi index 497720ce..b179667e 100644 --- a/src/xdoctest/utils/util_import.pyi +++ b/src/xdoctest/utils/util_import.pyi @@ -7,8 +7,8 @@ from _typeshed import Incomplete def is_modname_importable(modname: str, - sys_path: list = None, - exclude: list = None) -> bool: + sys_path: Union[list, None] = None, + exclude: Union[list, None] = None) -> bool: ... @@ -54,7 +54,7 @@ def modpath_to_modname(modpath: str, hide_init: bool = True, hide_main: bool = False, check: bool = True, - relativeto: str = None) -> str: + relativeto: Union[str, None] = None) -> str: ...