diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..a3e2149 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +1f423a22d8b27926da7d7b7393c833da0f3714a4 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 76c0f80..ea72cdd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,9 +6,17 @@ updates: interval: "monthly" labels: - "type: Maintenance" + groups: + actions: + patterns: + - "*" - package-ecosystem: "pip" directory: "/" schedule: interval: "monthly" labels: - "type: Maintenance" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ca30553..e095b5c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -21,7 +21,7 @@ jobs: - name: Install packages run: | - python -m pip install --upgrade pip wheel setuptools + python -m pip install --upgrade pip wheel setuptools spin python -m pip install ".[test]" python -m pip install --upgrade numpy python -m pip uninstall --yes scipy @@ -29,11 +29,11 @@ jobs: - name: Measure test coverage run: | - python -m pytest --cov=lazy_loader --durations=10 + spin test -c -- --durations=10 # Tests fail if using `--doctest-modules`. I.e., - # python -m pytest --cov=lazy_loader --doctest-modules --durations=20 + # spin test -- --doctest-modules - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/label-check.yaml b/.github/workflows/label-check.yaml index a0e2e5c..d6e97f2 100644 --- a/.github/workflows/label-check.yaml +++ b/.github/workflows/label-check.yaml @@ -4,8 +4,10 @@ on: pull_request: types: - opened + - reopened - labeled - unlabeled + - synchronize env: LABELS: ${{ join( github.event.pull_request.labels.*.name, ' ' ) }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index b2d6384..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: lint - -on: [push, pull_request] - -jobs: - default: - runs-on: ${{ matrix.os }}-latest - strategy: - matrix: - os: [ubuntu] - python-version: ["3.10"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install ".[lint]" - - - name: Lint - run: pre-commit run --all-files --show-diff-on-failure --color always diff --git a/.github/workflows/milestone-merged-prs.yaml b/.github/workflows/milestone-merged-prs.yaml index 71ae037..f455839 100644 --- a/.github/workflows/milestone-merged-prs.yaml +++ b/.github/workflows/milestone-merged-prs.yaml @@ -12,7 +12,7 @@ jobs: name: attach to PR runs-on: ubuntu-latest steps: - - uses: scientific-python/attach-next-milestone-action@bc07be829f693829263e57d5e8489f4e57d3d420 + - uses: scientific-python/attach-next-milestone-action@c9cfab10ad0c67fed91b01103db26b7f16634639 with: token: ${{ secrets.MILESTONE_LABELER_TOKEN }} force: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c454680..6cf247b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,8 +26,8 @@ jobs: - name: Build wheels run: | git clean -fxd - pip install -U build twine wheel - python -m build --sdist --wheel + pip install -U build twine wheel spin + spin sdist -- --wheel - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af6fc19..a299fe7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,10 @@ name: test -on: [push, pull_request] +on: [workflow_dispatch, push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: default: @@ -9,16 +13,7 @@ jobs: matrix: os: [ubuntu, macos, windows] python-version: - [ - "3.7", - "3.8", - "3.9", - "3.10", - "3.11", - "3.12-dev", - "pypy-3.8", - "pypy-3.9", - ] + ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.9", "pypy-3.10"] steps: - uses: actions/checkout@v4 @@ -30,9 +25,9 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip spin python -m pip install ".[test]" - name: Test run: | - python -m pytest + spin test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0484b49..5a6b373 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,15 @@ # Install pre-commit hooks via # pre-commit install +ci: + autofix_prs: false + autofix_commit_msg: | + '[pre-commit.ci 🤖] Apply code format tools to PR' + autoupdate_schedule: quarterly + repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: c4a0b883114b00d8d76b479c820ce7950211c99b # frozen: v4.5.0 + rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -16,20 +22,21 @@ repos: - id: check-toml - id: check-added-large-files - - repo: https://github.com/pre-commit/mirrors-prettier - rev: ffb6a759a979008c0e6dff86e39f4745a2d9eac4 # frozen: v3.1.0 + - repo: https://github.com/rbubley/mirrors-prettier + rev: 787fb9f542b140ba0b2aced38e6a3e68021647a3 # frozen: v3.5.3 hooks: - id: prettier files: \.(html|md|yml|yaml|toml) args: [--prose-wrap=preserve] - - repo: https://github.com/psf/black - rev: 552baf822992936134cbd31a38f69c8cfe7c0f05 # frozen: 24.3.0 - hooks: - - id: black - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 0ccbb5b7942d83fbcf7cb5e0fd99633efd2351d7 # frozen: v0.3.5 + rev: 971923581912ef60a6b70dbf0c3e9a39563c9d47 # frozen: v0.11.4 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix] + args: ["--fix", "--show-fixes", "--exit-non-zero-on-fix"] + - id: ruff-format + + - repo: https://github.com/codespell-project/codespell + rev: "63c8f8312b7559622c0d82815639671ae42132ac" # frozen: v2.4.1 + hooks: + - id: codespell diff --git a/.spin/cmds.py b/.spin/cmds.py new file mode 100644 index 0000000..7db4b98 --- /dev/null +++ b/.spin/cmds.py @@ -0,0 +1,35 @@ +import importlib +import sys +import textwrap + +import click +from spin.cmds.util import run + + +@click.command() +@click.argument("pytest_args", nargs=-1) +@click.option( + "-c", + "--coverage", + is_flag=True, + help="Generate a coverage report of executed tests.", +) +def test(pytest_args, coverage=False): + """🔧 Run tests""" + if not importlib.util.find_spec("lazy_loader"): + click.secho( + textwrap.dedent("""\ + ERROR: The package is not installed. + + Please do an editable install: + + pip install -e .[test] + + prior to running the tests."""), + fg="red", + ) + sys.exit(1) + + if coverage: + pytest_args = ("--cov=lazy_loader", *pytest_args) + run([sys.executable, "-m", "pytest", *list(pytest_args)]) diff --git a/CHANGELOG.md b/CHANGELOG.md index acba09a..70b9d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,49 +1,49 @@ -# lazy_loader 0.4 +# lazy-loader 0.4 -We're happy to announce the release of lazy_loader 0.4! +We're happy to announce the release of lazy-loader 0.4! ## Enhancements -- ENH: Add require argument to load() to accept version specifiers ([#48](https://github.com/scientific-python/lazy_loader/pull/48)). -- Add version as __version__ ([#97](https://github.com/scientific-python/lazy_loader/pull/97)). +- ENH: Add require argument to load() to accept version specifiers ([#48](https://github.com/scientific-python/lazy-loader/pull/48)). +- Add version as **version** ([#97](https://github.com/scientific-python/lazy-loader/pull/97)). ## Bug Fixes -- Avoid exception when __frame_data["code_context"] is None ([#83](https://github.com/scientific-python/lazy_loader/pull/83)). -- Make `lazy_load.load` partially thread-safe ([#90](https://github.com/scientific-python/lazy_loader/pull/90)). +- Avoid exception when \_\_frame_data["code_context"] is None ([#83](https://github.com/scientific-python/lazy-loader/pull/83)). +- Make `lazy_load.load` partially thread-safe ([#90](https://github.com/scientific-python/lazy-loader/pull/90)). ## Documentation -- Add security contact ([#91](https://github.com/scientific-python/lazy_loader/pull/91)). -- Recommend newer Python versions to avoid race ([#102](https://github.com/scientific-python/lazy_loader/pull/102)). +- Add security contact ([#91](https://github.com/scientific-python/lazy-loader/pull/91)). +- Recommend newer Python versions to avoid race ([#102](https://github.com/scientific-python/lazy-loader/pull/102)). ## Maintenance -- Use label-check and attach-next-milestone-action ([#64](https://github.com/scientific-python/lazy_loader/pull/64)). -- Use setuptools ([#65](https://github.com/scientific-python/lazy_loader/pull/65)). -- Specify what goes in sdist ([#66](https://github.com/scientific-python/lazy_loader/pull/66)). -- Use changelist ([#67](https://github.com/scientific-python/lazy_loader/pull/67)). -- Used dependabot ([#68](https://github.com/scientific-python/lazy_loader/pull/68)). -- Bump pre-commit from 3.3 to 3.3.3 ([#69](https://github.com/scientific-python/lazy_loader/pull/69)). -- Bump scientific-python/attach-next-milestone-action from f94a5235518d4d34911c41e19d780b8e79d42238 to a4889cfde7d2578c1bc7400480d93910d2dd34f6 ([#72](https://github.com/scientific-python/lazy_loader/pull/72)). -- Bump scientific-python/attach-next-milestone-action from a4889cfde7d2578c1bc7400480d93910d2dd34f6 to bc07be829f693829263e57d5e8489f4e57d3d420 ([#74](https://github.com/scientific-python/lazy_loader/pull/74)). -- Bump actions/checkout from 3 to 4 ([#75](https://github.com/scientific-python/lazy_loader/pull/75)). -- Bump changelist from 0.1 to 0.3 ([#77](https://github.com/scientific-python/lazy_loader/pull/77)). -- Bump pre-commit from 3.3.3 to 3.4.0 ([#76](https://github.com/scientific-python/lazy_loader/pull/76)). -- Use trusted publisher ([#78](https://github.com/scientific-python/lazy_loader/pull/78)). -- Bump pre-commit from 3.4.0 to 3.5.0 ([#80](https://github.com/scientific-python/lazy_loader/pull/80)). -- Bump changelist from 0.3 to 0.4 ([#81](https://github.com/scientific-python/lazy_loader/pull/81)). -- Bump actions/checkout from 3 to 4 ([#82](https://github.com/scientific-python/lazy_loader/pull/82)). -- Bump actions/setup-python from 4 to 5 ([#85](https://github.com/scientific-python/lazy_loader/pull/85)). -- Bump pre-commit from 3.5.0 to 3.6.0 ([#84](https://github.com/scientific-python/lazy_loader/pull/84)). -- Update pre-commit ([#87](https://github.com/scientific-python/lazy_loader/pull/87)). -- Use setup-python pip cache ([#95](https://github.com/scientific-python/lazy_loader/pull/95)). -- Bump codecov/codecov-action from 3 to 4 ([#93](https://github.com/scientific-python/lazy_loader/pull/93)). -- Bump pre-commit from 3.6.0 to 3.6.2 ([#100](https://github.com/scientific-python/lazy_loader/pull/100)). -- Bump changelist from 0.4 to 0.5 ([#99](https://github.com/scientific-python/lazy_loader/pull/99)). -- Refuse star imports in stub loader ([#101](https://github.com/scientific-python/lazy_loader/pull/101)). -- Bump pre-commit from 3.6.2 to 3.7.0 ([#103](https://github.com/scientific-python/lazy_loader/pull/103)). -- Update pre-commit repos ([#104](https://github.com/scientific-python/lazy_loader/pull/104)). +- Use label-check and attach-next-milestone-action ([#64](https://github.com/scientific-python/lazy-loader/pull/64)). +- Use setuptools ([#65](https://github.com/scientific-python/lazy-loader/pull/65)). +- Specify what goes in sdist ([#66](https://github.com/scientific-python/lazy-loader/pull/66)). +- Use changelist ([#67](https://github.com/scientific-python/lazy-loader/pull/67)). +- Used dependabot ([#68](https://github.com/scientific-python/lazy-loader/pull/68)). +- Bump pre-commit from 3.3 to 3.3.3 ([#69](https://github.com/scientific-python/lazy-loader/pull/69)). +- Bump scientific-python/attach-next-milestone-action from f94a5235518d4d34911c41e19d780b8e79d42238 to a4889cfde7d2578c1bc7400480d93910d2dd34f6 ([#72](https://github.com/scientific-python/lazy-loader/pull/72)). +- Bump scientific-python/attach-next-milestone-action from a4889cfde7d2578c1bc7400480d93910d2dd34f6 to bc07be829f693829263e57d5e8489f4e57d3d420 ([#74](https://github.com/scientific-python/lazy-loader/pull/74)). +- Bump actions/checkout from 3 to 4 ([#75](https://github.com/scientific-python/lazy-loader/pull/75)). +- Bump changelist from 0.1 to 0.3 ([#77](https://github.com/scientific-python/lazy-loader/pull/77)). +- Bump pre-commit from 3.3.3 to 3.4.0 ([#76](https://github.com/scientific-python/lazy-loader/pull/76)). +- Use trusted publisher ([#78](https://github.com/scientific-python/lazy-loader/pull/78)). +- Bump pre-commit from 3.4.0 to 3.5.0 ([#80](https://github.com/scientific-python/lazy-loader/pull/80)). +- Bump changelist from 0.3 to 0.4 ([#81](https://github.com/scientific-python/lazy-loader/pull/81)). +- Bump actions/checkout from 3 to 4 ([#82](https://github.com/scientific-python/lazy-loader/pull/82)). +- Bump actions/setup-python from 4 to 5 ([#85](https://github.com/scientific-python/lazy-loader/pull/85)). +- Bump pre-commit from 3.5.0 to 3.6.0 ([#84](https://github.com/scientific-python/lazy-loader/pull/84)). +- Update pre-commit ([#87](https://github.com/scientific-python/lazy-loader/pull/87)). +- Use setup-python pip cache ([#95](https://github.com/scientific-python/lazy-loader/pull/95)). +- Bump codecov/codecov-action from 3 to 4 ([#93](https://github.com/scientific-python/lazy-loader/pull/93)). +- Bump pre-commit from 3.6.0 to 3.6.2 ([#100](https://github.com/scientific-python/lazy-loader/pull/100)). +- Bump changelist from 0.4 to 0.5 ([#99](https://github.com/scientific-python/lazy-loader/pull/99)). +- Refuse star imports in stub loader ([#101](https://github.com/scientific-python/lazy-loader/pull/101)). +- Bump pre-commit from 3.6.2 to 3.7.0 ([#103](https://github.com/scientific-python/lazy-loader/pull/103)). +- Update pre-commit repos ([#104](https://github.com/scientific-python/lazy-loader/pull/104)). ## Contributors @@ -67,107 +67,107 @@ duplicates._ # Changelog -## [v0.3](https://github.com/scientific-python/lazy_loader/tree/v0.3) (2023-06-30) +## [v0.3](https://github.com/scientific-python/lazy-loader/tree/v0.3) (2023-06-30) -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.2...v0.3) +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.2...v0.3) **Merged pull requests:** -- Announce Python 3.12 support [\#63](https://github.com/scientific-python/lazy_loader/pull/63) ([jarrodmillman](https://github.com/jarrodmillman)) -- Ignore B028 [\#62](https://github.com/scientific-python/lazy_loader/pull/62) ([jarrodmillman](https://github.com/jarrodmillman)) -- Use dependabot to update requirements [\#61](https://github.com/scientific-python/lazy_loader/pull/61) ([jarrodmillman](https://github.com/jarrodmillman)) -- Use dependabot to update GH actions [\#60](https://github.com/scientific-python/lazy_loader/pull/60) ([jarrodmillman](https://github.com/jarrodmillman)) -- Use ruff [\#59](https://github.com/scientific-python/lazy_loader/pull/59) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update requirements [\#58](https://github.com/scientific-python/lazy_loader/pull/58) ([jarrodmillman](https://github.com/jarrodmillman)) -- Warn and discourage lazy.load of subpackages [\#57](https://github.com/scientific-python/lazy_loader/pull/57) ([dschult](https://github.com/dschult)) -- Test on Python 3.12.0-beta.2 [\#53](https://github.com/scientific-python/lazy_loader/pull/53) ([jarrodmillman](https://github.com/jarrodmillman)) +- Announce Python 3.12 support [\#63](https://github.com/scientific-python/lazy-loader/pull/63) ([jarrodmillman](https://github.com/jarrodmillman)) +- Ignore B028 [\#62](https://github.com/scientific-python/lazy-loader/pull/62) ([jarrodmillman](https://github.com/jarrodmillman)) +- Use dependabot to update requirements [\#61](https://github.com/scientific-python/lazy-loader/pull/61) ([jarrodmillman](https://github.com/jarrodmillman)) +- Use dependabot to update GH actions [\#60](https://github.com/scientific-python/lazy-loader/pull/60) ([jarrodmillman](https://github.com/jarrodmillman)) +- Use ruff [\#59](https://github.com/scientific-python/lazy-loader/pull/59) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update requirements [\#58](https://github.com/scientific-python/lazy-loader/pull/58) ([jarrodmillman](https://github.com/jarrodmillman)) +- Warn and discourage lazy.load of subpackages [\#57](https://github.com/scientific-python/lazy-loader/pull/57) ([dschult](https://github.com/dschult)) +- Test on Python 3.12.0-beta.2 [\#53](https://github.com/scientific-python/lazy-loader/pull/53) ([jarrodmillman](https://github.com/jarrodmillman)) -## [v0.2](https://github.com/scientific-python/lazy_loader/tree/v0.2) +## [v0.2](https://github.com/scientific-python/lazy-loader/tree/v0.2) -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.1...v0.2) +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.1...v0.2) There were no changes since the release candidate, so see release notes for v0.2rc0 below for details. -## [v0.2rc0](https://github.com/scientific-python/lazy_loader/tree/v0.2rc0) +## [v0.2rc0](https://github.com/scientific-python/lazy-loader/tree/v0.2rc0) -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.1...v0.2rc0) +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.1...v0.2rc0) **Closed issues:** -- Allow to not fail on stub attach in frozen apps [\#38](https://github.com/scientific-python/lazy_loader/issues/38) -- Stub files with absolute imports [\#36](https://github.com/scientific-python/lazy_loader/issues/36) -- Help to packaging Debian package [\#35](https://github.com/scientific-python/lazy_loader/issues/35) -- conda upload [\#33](https://github.com/scientific-python/lazy_loader/issues/33) -- Possible issues with partial lazy loading [\#32](https://github.com/scientific-python/lazy_loader/issues/32) -- Type hints/Mypy best practices? [\#28](https://github.com/scientific-python/lazy_loader/issues/28) -- Re-export non descendant attribute? [\#27](https://github.com/scientific-python/lazy_loader/issues/27) -- This is awesome [\#6](https://github.com/scientific-python/lazy_loader/issues/6) +- Allow to not fail on stub attach in frozen apps [\#38](https://github.com/scientific-python/lazy-loader/issues/38) +- Stub files with absolute imports [\#36](https://github.com/scientific-python/lazy-loader/issues/36) +- Help to packaging Debian package [\#35](https://github.com/scientific-python/lazy-loader/issues/35) +- conda upload [\#33](https://github.com/scientific-python/lazy-loader/issues/33) +- Possible issues with partial lazy loading [\#32](https://github.com/scientific-python/lazy-loader/issues/32) +- Type hints/Mypy best practices? [\#28](https://github.com/scientific-python/lazy-loader/issues/28) +- Re-export non descendant attribute? [\#27](https://github.com/scientific-python/lazy-loader/issues/27) +- This is awesome [\#6](https://github.com/scientific-python/lazy-loader/issues/6) **Merged pull requests:** -- Add information that `pyi` files are used in runtime when use `attach\_stub` [\#47](https://github.com/scientific-python/lazy_loader/pull/47) ([Czaki](https://github.com/Czaki)) -- Update tests to improve coverage [\#45](https://github.com/scientific-python/lazy_loader/pull/45) ([jarrodmillman](https://github.com/jarrodmillman)) -- Use codecov GH action [\#44](https://github.com/scientific-python/lazy_loader/pull/44) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update year [\#43](https://github.com/scientific-python/lazy_loader/pull/43) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update GH actions [\#42](https://github.com/scientific-python/lazy_loader/pull/42) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update pre-commit [\#41](https://github.com/scientific-python/lazy_loader/pull/41) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update optional depedencies [\#40](https://github.com/scientific-python/lazy_loader/pull/40) ([jarrodmillman](https://github.com/jarrodmillman)) -- Fix extension substitution to work with `\*.pyc` files [\#39](https://github.com/scientific-python/lazy_loader/pull/39) ([Czaki](https://github.com/Czaki)) -- Sort returned \_\_all\_\_ [\#34](https://github.com/scientific-python/lazy_loader/pull/34) ([stefanv](https://github.com/stefanv)) +- Add information that `pyi` files are used in runtime when use `attach\_stub` [\#47](https://github.com/scientific-python/lazy-loader/pull/47) ([Czaki](https://github.com/Czaki)) +- Update tests to improve coverage [\#45](https://github.com/scientific-python/lazy-loader/pull/45) ([jarrodmillman](https://github.com/jarrodmillman)) +- Use codecov GH action [\#44](https://github.com/scientific-python/lazy-loader/pull/44) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update year [\#43](https://github.com/scientific-python/lazy-loader/pull/43) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update GH actions [\#42](https://github.com/scientific-python/lazy-loader/pull/42) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update pre-commit [\#41](https://github.com/scientific-python/lazy-loader/pull/41) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update optional dependencies [\#40](https://github.com/scientific-python/lazy-loader/pull/40) ([jarrodmillman](https://github.com/jarrodmillman)) +- Fix extension substitution to work with `\*.pyc` files [\#39](https://github.com/scientific-python/lazy-loader/pull/39) ([Czaki](https://github.com/Czaki)) +- Sort returned \_\_all\_\_ [\#34](https://github.com/scientific-python/lazy-loader/pull/34) ([stefanv](https://github.com/stefanv)) -## [v0.1](https://github.com/scientific-python/lazy_loader/tree/v0.1) (2022-09-21) +## [v0.1](https://github.com/scientific-python/lazy-loader/tree/v0.1) (2022-09-21) -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.1rc3...v0.1) +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.1rc3...v0.1) **Merged pull requests:** -- Update classifiers [\#31](https://github.com/scientific-python/lazy_loader/pull/31) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update precommit hooks [\#30](https://github.com/scientific-python/lazy_loader/pull/30) ([jarrodmillman](https://github.com/jarrodmillman)) -- Refer to SPEC for stub usage [\#29](https://github.com/scientific-python/lazy_loader/pull/29) ([stefanv](https://github.com/stefanv)) +- Update classifiers [\#31](https://github.com/scientific-python/lazy-loader/pull/31) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update precommit hooks [\#30](https://github.com/scientific-python/lazy-loader/pull/30) ([jarrodmillman](https://github.com/jarrodmillman)) +- Refer to SPEC for stub usage [\#29](https://github.com/scientific-python/lazy-loader/pull/29) ([stefanv](https://github.com/stefanv)) -## [v0.1rc3](https://github.com/scientific-python/lazy_loader/tree/v0.1rc3) (2022-08-29) +## [v0.1rc3](https://github.com/scientific-python/lazy-loader/tree/v0.1rc3) (2022-08-29) -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.1rc2...v0.1rc3) +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.1rc2...v0.1rc3) **Merged pull requests:** -- Add test and coverage badges [\#26](https://github.com/scientific-python/lazy_loader/pull/26) ([jarrodmillman](https://github.com/jarrodmillman)) -- Fix typos [\#25](https://github.com/scientific-python/lazy_loader/pull/25) ([jarrodmillman](https://github.com/jarrodmillman)) -- Measure test coverage [\#23](https://github.com/scientific-python/lazy_loader/pull/23) ([jarrodmillman](https://github.com/jarrodmillman)) -- Document release process [\#22](https://github.com/scientific-python/lazy_loader/pull/22) ([jarrodmillman](https://github.com/jarrodmillman)) -- Add classifiers [\#21](https://github.com/scientific-python/lazy_loader/pull/21) ([jarrodmillman](https://github.com/jarrodmillman)) -- Lint toml files [\#20](https://github.com/scientific-python/lazy_loader/pull/20) ([jarrodmillman](https://github.com/jarrodmillman)) -- Test on more versions and platforms [\#19](https://github.com/scientific-python/lazy_loader/pull/19) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update GH actions [\#18](https://github.com/scientific-python/lazy_loader/pull/18) ([jarrodmillman](https://github.com/jarrodmillman)) -- Split out linting CI from testing [\#17](https://github.com/scientific-python/lazy_loader/pull/17) ([jarrodmillman](https://github.com/jarrodmillman)) -- Update precommit hooks [\#16](https://github.com/scientific-python/lazy_loader/pull/16) ([jarrodmillman](https://github.com/jarrodmillman)) -- Specify lower bounds on dependencies [\#15](https://github.com/scientific-python/lazy_loader/pull/15) ([jarrodmillman](https://github.com/jarrodmillman)) -- Lower min required Python version to 3.7 [\#14](https://github.com/scientific-python/lazy_loader/pull/14) ([donatasm](https://github.com/donatasm)) -- feat: add attach_stub function to load imports from type stubs [\#10](https://github.com/scientific-python/lazy_loader/pull/10) ([tlambert03](https://github.com/tlambert03)) -- Avoid conflicts when function is implemented in same-named submodule [\#9](https://github.com/scientific-python/lazy_loader/pull/9) ([stefanv](https://github.com/stefanv)) -- DOC fix missing comma in usage example in README.md [\#7](https://github.com/scientific-python/lazy_loader/pull/7) ([adrinjalali](https://github.com/adrinjalali)) - -## [v0.1rc2](https://github.com/scientific-python/lazy_loader/tree/v0.1rc2) (2022-03-10) - -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.1rc1...v0.1rc2) +- Add test and coverage badges [\#26](https://github.com/scientific-python/lazy-loader/pull/26) ([jarrodmillman](https://github.com/jarrodmillman)) +- Fix typos [\#25](https://github.com/scientific-python/lazy-loader/pull/25) ([jarrodmillman](https://github.com/jarrodmillman)) +- Measure test coverage [\#23](https://github.com/scientific-python/lazy-loader/pull/23) ([jarrodmillman](https://github.com/jarrodmillman)) +- Document release process [\#22](https://github.com/scientific-python/lazy-loader/pull/22) ([jarrodmillman](https://github.com/jarrodmillman)) +- Add classifiers [\#21](https://github.com/scientific-python/lazy-loader/pull/21) ([jarrodmillman](https://github.com/jarrodmillman)) +- Lint toml files [\#20](https://github.com/scientific-python/lazy-loader/pull/20) ([jarrodmillman](https://github.com/jarrodmillman)) +- Test on more versions and platforms [\#19](https://github.com/scientific-python/lazy-loader/pull/19) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update GH actions [\#18](https://github.com/scientific-python/lazy-loader/pull/18) ([jarrodmillman](https://github.com/jarrodmillman)) +- Split out linting CI from testing [\#17](https://github.com/scientific-python/lazy-loader/pull/17) ([jarrodmillman](https://github.com/jarrodmillman)) +- Update precommit hooks [\#16](https://github.com/scientific-python/lazy-loader/pull/16) ([jarrodmillman](https://github.com/jarrodmillman)) +- Specify lower bounds on dependencies [\#15](https://github.com/scientific-python/lazy-loader/pull/15) ([jarrodmillman](https://github.com/jarrodmillman)) +- Lower min required Python version to 3.7 [\#14](https://github.com/scientific-python/lazy-loader/pull/14) ([donatasm](https://github.com/donatasm)) +- feat: add attach_stub function to load imports from type stubs [\#10](https://github.com/scientific-python/lazy-loader/pull/10) ([tlambert03](https://github.com/tlambert03)) +- Avoid conflicts when function is implemented in same-named submodule [\#9](https://github.com/scientific-python/lazy-loader/pull/9) ([stefanv](https://github.com/stefanv)) +- DOC fix missing comma in usage example in README.md [\#7](https://github.com/scientific-python/lazy-loader/pull/7) ([adrinjalali](https://github.com/adrinjalali)) + +## [v0.1rc2](https://github.com/scientific-python/lazy-loader/tree/v0.1rc2) (2022-03-10) + +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.1rc1...v0.1rc2) **Merged pull requests:** -- Add contributor README [\#5](https://github.com/scientific-python/lazy_loader/pull/5) ([stefanv](https://github.com/stefanv)) -- Simplify delayed exception handling and improve reporting [\#4](https://github.com/scientific-python/lazy_loader/pull/4) ([stefanv](https://github.com/stefanv)) +- Add contributor README [\#5](https://github.com/scientific-python/lazy-loader/pull/5) ([stefanv](https://github.com/stefanv)) +- Simplify delayed exception handling and improve reporting [\#4](https://github.com/scientific-python/lazy-loader/pull/4) ([stefanv](https://github.com/stefanv)) -## [v0.1rc1](https://github.com/scientific-python/lazy_loader/tree/v0.1rc1) (2022-03-01) +## [v0.1rc1](https://github.com/scientific-python/lazy-loader/tree/v0.1rc1) (2022-03-01) -[Full Changelog](https://github.com/scientific-python/lazy_loader/compare/v0.0...v0.1rc1) +[Full Changelog](https://github.com/scientific-python/lazy-loader/compare/v0.0...v0.1rc1) **Closed issues:** -- Create package on pypi [\#1](https://github.com/scientific-python/lazy_loader/issues/1) +- Create package on pypi [\#1](https://github.com/scientific-python/lazy-loader/issues/1) **Merged pull requests:** -- Run pre-commit hooks [\#3](https://github.com/scientific-python/lazy_loader/pull/3) ([tupui](https://github.com/tupui)) -- Add the packaging infrastructure [\#2](https://github.com/scientific-python/lazy_loader/pull/2) ([tupui](https://github.com/tupui)) +- Run pre-commit hooks [\#3](https://github.com/scientific-python/lazy-loader/pull/3) ([tupui](https://github.com/tupui)) +- Add the packaging infrastructure [\#2](https://github.com/scientific-python/lazy-loader/pull/2) ([tupui](https://github.com/tupui)) \* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_ diff --git a/MANIFEST.in b/MANIFEST.in index fc49293..ce1d4c7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,9 @@ include CHANGELOG.md +include .spin/cmds.py +recursive-include tests * + +global-exclude *~ +global-exclude *.pyc + +prune */.pytest_cache +prune */__pycache__ diff --git a/README.md b/README.md index 64b3d9c..17c255e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -[![PyPI](https://img.shields.io/pypi/v/lazy_loader)](https://pypi.org/project/lazy_loader/) -[![Test status](https://github.com/scientific-python/lazy_loader/workflows/test/badge.svg?branch=main)](https://github.com/scientific-python/lazy_loader/actions?query=workflow%3A%22test%22) -[![Test coverage](https://codecov.io/gh/scientific-python/lazy_loader/branch/main/graph/badge.svg)](https://app.codecov.io/gh/scientific-python/lazy_loader/branch/main) +[![PyPI](https://img.shields.io/pypi/v/lazy-loader)](https://pypi.org/project/lazy-loader/) +[![Test status](https://github.com/scientific-python/lazy-loader/workflows/test/badge.svg?branch=main)](https://github.com/scientific-python/lazy-loader/actions?query=workflow%3A%22test%22) +[![Test coverage](https://codecov.io/gh/scientific-python/lazy-loader/branch/main/graph/badge.svg)](https://app.codecov.io/gh/scientific-python/lazy-loader/branch/main) -`lazy_loader` makes it easy to load subpackages and functions on demand. +`lazy-loader` makes it easy to load subpackages and functions on demand. ## Motivation @@ -14,10 +14,10 @@ For a more detailed discussion, see [the SPEC](https://scientific-python.org/spe ## Installation ``` -pip install -U lazy_loader +pip install -U lazy-loader ``` -We recommend using `lazy_loader` with Python >= 3.11. +We recommend using `lazy-loader` with Python >= 3.11. If using Python 3.11, please upgrade to 3.11.9 or later. If using Python 3.12, please upgrade to 3.12.3 or later. These versions [avoid](https://github.com/python/cpython/pull/114781) a [known race condition](https://github.com/python/cpython/issues/114763). diff --git a/RELEASE.md b/RELEASE.md index cd1585c..a749d39 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,4 @@ -# Release process for `lazy_loader` +# Release process for `lazy-loader` ## Introduction @@ -17,19 +17,22 @@ Example `version number` export VERSION= export PREVIOUS= export ORG="scientific-python" - export REPO="lazy_loader" + export REPO="lazy-loader" + export LOG="CHANGELOG.md" - Autogenerate release notes - changelist ${ORG}/${REPO} v${PREVIOUS} main --version ${VERSION} + changelist ${ORG}/${REPO} v${PREVIOUS} main --version ${VERSION} --config pyproject.toml --out ${VERSION}.md - Put the output of the above command at the top of `CHANGELOG.md` -- Update `version` in `pyproject.toml`. + cat ${VERSION}.md | cat - ${LOG} > temp && mv temp ${LOG} + +- Update `version` in `lazy_loader/__init__.py`. - Commit changes: - git add pyproject.toml CHANGELOG.md + git add lazy_loader/__init__.py ${LOG} git commit -m "Designate ${VERSION} release" - Tag the release in git: @@ -43,13 +46,25 @@ Example `version number` git push --tags origin main - where `origin` is the name of the `github.com:scientific-python/lazy_loader` + where `origin` is the name of the `github.com:scientific-python/lazy-loader` repository -- Update `version` in `pyproject.toml`. +- Create release from tag + + - go to https://github.com/scientific-python/lazy-loader/releases/new?tag=v${VERSION} + - add v${VERSION} for the `Release title` + - paste contents (or upload) of ${VERSION}.md in the `Describe this release section` + - if pre-release check the box labelled `Set as a pre-release` + +- Update https://github.com/scientific-python/lazy-loader/milestones: + + - close old milestone + - ensure new milestone exists (perhaps setting due date) + +- Update `version` in `lazy_loader/__init__.py`. - Commit changes: - git add pyproject.toml + git add lazy_loader/__init__.py git commit -m 'Bump version' git push origin main diff --git a/lazy_loader/tests/fake_pkg/__init__.pyi b/lazy_loader/tests/fake_pkg/__init__.pyi deleted file mode 100644 index d3349bb..0000000 --- a/lazy_loader/tests/fake_pkg/__init__.pyi +++ /dev/null @@ -1 +0,0 @@ -from .some_func import some_func diff --git a/pyproject.toml b/pyproject.toml index c81bd40..828c1ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,58 +3,116 @@ requires = ["setuptools>=61.2"] build-backend = "setuptools.build_meta" [project] -name = "lazy_loader" -requires-python = ">=3.7" +name = "lazy-loader" +requires-python = ">=3.9" authors = [{name = "Scientific Python Developers"}] readme = "README.md" -license = {file = "LICENSE.md"} +license = "BSD-3-Clause" dynamic = ['version'] classifiers = [ - "Development Status :: 4 - Beta", - "License :: OSI Approved :: BSD License", + "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", - "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 :: 3.12", + "Programming Language :: Python :: 3.13", ] description = "Makes it easy to load subpackages and functions on demand." dependencies = [ "packaging", - "importlib_metadata; python_version < '3.8'", ] [project.optional-dependencies] -test = ["pytest >= 7.4", "pytest-cov >= 4.1"] -lint = ["pre-commit == 3.7.0"] -dev = ["changelist == 0.5"] +test = ["pytest >= 8.0", "pytest-cov >= 5.0", "coverage[toml] >= 7.2"] +lint = ["pre-commit == 4.2.0"] +dev = ["changelist == 0.5", "spin == 0.14"] [project.urls] Home = "https://scientific-python.org/specs/spec-0001/" -Source = "https://github.com/scientific-python/lazy_loader" +Source = "https://github.com/scientific-python/lazy-loader" +[tool.setuptools.dynamic.version] +attr = 'lazy_loader.__version__' [tool.changelist] ignored_user_logins = ["dependabot[bot]", "pre-commit-ci[bot]", "web-flow"] -[tool.setuptools.dynamic.version] -attr = 'lazy_loader.__version__' +[tool.pytest.ini_options] +minversion = "8.0" +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +xfail_strict = true +filterwarnings = ["error"] +log_cli_level = "info" [tool.ruff] -line-length = 88 -target-version = "py37" -select = [ - "C", - "E", - "F", - "W", - "B", - "I", - "UP", -] -ignore = ["B018", "B028"] exclude = [ - "lazy_loader/tests/fake_pkg/__init__.pyi", + "tests/fake_pkg/__init__.pyi", +] + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style +# "PTH", # flake8-use-pathlib + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet + "FURB", # refurb + "PYI", # flake8-pyi +] +ignore = [ + "PLR09", # Too many <...> + "PLR2004", # Magic value used in comparison + "ISC001", # Conflicts with formatter + "B018", # Found useless expression + "B028", # No explicit `stacklevel` keyword argument found + "PT011", # `pytest.raises(ValueError)` is too broad + "EM102", # Exception must not use an f-string literal + "EM101", # Exception must not use a string literal + "RET505", # Unnecessary `elif` after `return` statement + "SIM108", # Use ternary operator +] + +[tool.ruff.lint.per-file-ignores] +"tests/test_*.py" = [ + "ARG001", # Pytest fixtures are passed as arguments +] + +[tool.ruff.format] +docstring-code-format = true + +[tool.coverage.run] +branch = true +source = ["lazy_loader", "tests"] + +[tool.coverage.paths] +source = [ + "src/lazy_loader", + "*/site-packages/lazy_loader", +] + +[tool.spin] +package = 'lazy_loader' + +[tool.spin.commands] +Build = [ + 'spin.cmds.pip.install', + '.spin/cmds.py:test', + 'spin.cmds.build.sdist', ] diff --git a/lazy_loader/__init__.py b/src/lazy_loader/__init__.py similarity index 84% rename from lazy_loader/__init__.py rename to src/lazy_loader/__init__.py index 5f25913..0b0243e 100644 --- a/lazy_loader/__init__.py +++ b/src/lazy_loader/__init__.py @@ -14,8 +14,8 @@ import types import warnings -__version__ = "0.4" -__all__ = ["attach", "load", "attach_stub"] +__version__ = "0.5rc0.dev0" +__all__ = ["attach", "attach_stub", "load"] threadlock = threading.Lock() @@ -38,13 +38,9 @@ def attach(package_name, submodules=None, submod_attrs=None): The typical way to call this function, replacing the above imports, is:: __getattr__, __dir__, __all__ = lazy.attach( - __name__, - ['mysubmodule', 'anothersubmodule'], - {'foo': ['someattr']} + __name__, ["mysubmodule", "anothersubmodule"], {"foo": ["someattr"]} ) - This functionality requires Python 3.7 or higher. - Parameters ---------- package_name : str @@ -94,13 +90,13 @@ def __getattr__(name): raise AttributeError(f"No {package_name} attribute {name}") def __dir__(): - return __all__ + return __all__.copy() if os.environ.get("EAGER_IMPORT", ""): for attr in set(attr_to_modules.keys()) | submodules: __getattr__(attr) - return __getattr__, __dir__, list(__all__) + return __getattr__, __dir__, __all__.copy() class DelayedImportErrorModule(types.ModuleType): @@ -110,19 +106,16 @@ def __init__(self, frame_data, *args, message, **kwargs): super().__init__(*args, **kwargs) def __getattr__(self, x): - if x in ("__class__", "__file__", "__frame_data", "__message"): - super().__getattr__(x) - else: - fd = self.__frame_data - raise ModuleNotFoundError( - f"{self.__message}\n\n" - "This error is lazily reported, having originally occured in\n" - f' File {fd["filename"]}, line {fd["lineno"]}, in {fd["function"]}\n\n' - f'----> {"".join(fd["code_context"] or "").strip()}' - ) + fd = self.__frame_data + raise ModuleNotFoundError( + f"{self.__message}\n\n" + "This error is lazily reported, having originally occurred in\n" + f" File {fd['filename']}, line {fd['lineno']}, in {fd['function']}\n\n" + f"----> {''.join(fd['code_context'] or '').strip()}" + ) -def load(fullname, *, require=None, error_on_import=False): +def load(fullname, *, require=None, error_on_import=False, suppress_warning=False): """Return a lazily imported proxy for a module. We often see the following pattern:: @@ -164,7 +157,7 @@ def myfunc(): fullname : str The full name of the module or submodule to import. For example:: - sp = lazy.load('scipy') # import scipy as sp + sp = lazy.load("scipy") # import scipy as sp require : str A dependency requirement as defined in PEP-508. For example:: @@ -178,6 +171,10 @@ def myfunc(): Whether to postpone raising import errors until the module is accessed. If set to `True`, import errors are raised as soon as `load` is called. + suppress_warning : bool + Whether to prevent emitting a warning when loading subpackages. + If set to `True`, no warning will occur. + Returns ------- pm : importlib.util._LazyModule @@ -193,10 +190,10 @@ def myfunc(): if have_module and require is None: return module - if "." in fullname: + if not suppress_warning and "." in fullname: msg = ( "subpackages can technically be lazily loaded, but it causes the " - "package to be eagerly loaded even if it is already lazily loaded." + "package to be eagerly loaded even if it is already lazily loaded. " "So, you probably shouldn't use subpackages with this lazy feature." ) warnings.warn(msg, RuntimeWarning) @@ -226,21 +223,19 @@ def myfunc(): raise ModuleNotFoundError(not_found_message) import inspect - try: - parent = inspect.stack()[1] - frame_data = { - "filename": parent.filename, - "lineno": parent.lineno, - "function": parent.function, - "code_context": parent.code_context, - } - return DelayedImportErrorModule( - frame_data, - "DelayedImportErrorModule", - message=not_found_message, - ) - finally: - del parent + parent = inspect.stack()[1] + frame_data = { + "filename": parent.filename, + "lineno": parent.lineno, + "function": parent.function, + "code_context": parent.code_context, + } + del parent + return DelayedImportErrorModule( + frame_data, + "DelayedImportErrorModule", + message=not_found_message, + ) if spec is not None: module = importlib.util.module_from_spec(spec) @@ -269,16 +264,13 @@ def _check_requirement(require: str) -> bool: True if the installed version of the dependency matches the specified version, False otherwise. """ - import packaging.requirements + import importlib.metadata - try: - import importlib.metadata as importlib_metadata - except ImportError: # PY37 - import importlib_metadata + import packaging.requirements req = packaging.requirements.Requirement(require) return req.specifier.contains( - importlib_metadata.version(req.name), + importlib.metadata.version(req.name), prereleases=True, ) diff --git a/lazy_loader/tests/__init__.py b/tests/__init__.py similarity index 100% rename from lazy_loader/tests/__init__.py rename to tests/__init__.py diff --git a/lazy_loader/tests/fake_pkg/__init__.py b/tests/fake_pkg/__init__.py similarity index 54% rename from lazy_loader/tests/fake_pkg/__init__.py rename to tests/fake_pkg/__init__.py index 540fd73..39f038d 100644 --- a/lazy_loader/tests/fake_pkg/__init__.py +++ b/tests/fake_pkg/__init__.py @@ -1,5 +1,5 @@ import lazy_loader as lazy __getattr__, __lazy_dir__, __all__ = lazy.attach( - __name__, submod_attrs={"some_func": ["some_func"]} + __name__, submod_attrs={"some_func": ["some_func", "aux_func"]} ) diff --git a/tests/fake_pkg/__init__.pyi b/tests/fake_pkg/__init__.pyi new file mode 100644 index 0000000..8ace2b6 --- /dev/null +++ b/tests/fake_pkg/__init__.pyi @@ -0,0 +1 @@ +from .some_func import aux_func, some_func diff --git a/lazy_loader/tests/fake_pkg/some_func.py b/tests/fake_pkg/some_func.py similarity index 57% rename from lazy_loader/tests/fake_pkg/some_func.py rename to tests/fake_pkg/some_func.py index 10e99ed..5b7c10e 100644 --- a/lazy_loader/tests/fake_pkg/some_func.py +++ b/tests/fake_pkg/some_func.py @@ -1,3 +1,6 @@ def some_func(): """Function with same name as submodule.""" - pass + + +def aux_func(): + """Auxiliary function.""" diff --git a/lazy_loader/tests/import_np_parallel.py b/tests/import_np_parallel.py similarity index 100% rename from lazy_loader/tests/import_np_parallel.py rename to tests/import_np_parallel.py diff --git a/lazy_loader/tests/test_lazy_loader.py b/tests/test_lazy_loader.py similarity index 61% rename from lazy_loader/tests/test_lazy_loader.py rename to tests/test_lazy_loader.py index 19187ba..44e9c3d 100644 --- a/lazy_loader/tests/test_lazy_loader.py +++ b/tests/test_lazy_loader.py @@ -10,6 +10,26 @@ import lazy_loader as lazy +@pytest.fixture +def clean_fake_pkg(): + yield + sys.modules.pop("tests.fake_pkg.some_func", None) + sys.modules.pop("tests.fake_pkg", None) + sys.modules.pop("tests", None) + + +@pytest.mark.parametrize("attempt", [1, 2]) +def test_cleanup_fixture(clean_fake_pkg, attempt): + assert "tests.fake_pkg" not in sys.modules + assert "tests.fake_pkg.some_func" not in sys.modules + from tests import fake_pkg + + assert "tests.fake_pkg" in sys.modules + assert "tests.fake_pkg.some_func" not in sys.modules + assert isinstance(fake_pkg.some_func, types.FunctionType) + assert "tests.fake_pkg.some_func" in sys.modules + + def test_lazy_import_basics(): math = lazy.load("math") anything_not_real = lazy.load("anything_not_real") @@ -17,25 +37,19 @@ def test_lazy_import_basics(): # Now test that accessing attributes does what it should assert math.sin(math.pi) == pytest.approx(0, 1e-6) # poor-mans pytest.raises for testing errors on attribute access - try: + with pytest.raises(ModuleNotFoundError): anything_not_real.pi - raise AssertionError() # Should not get here - except ModuleNotFoundError: - pass assert isinstance(anything_not_real, lazy.DelayedImportErrorModule) # see if it changes for second access - try: + with pytest.raises(ModuleNotFoundError): anything_not_real.pi - raise AssertionError() # Should not get here - except ModuleNotFoundError: - pass def test_lazy_import_subpackages(): with pytest.warns(RuntimeWarning): hp = lazy.load("html.parser") assert "html" in sys.modules - assert type(sys.modules["html"]) == type(pytest) + assert type(sys.modules["html"]) is type(pytest) assert isinstance(hp, importlib.util._LazyModule) assert "html.parser" in sys.modules assert sys.modules["html.parser"] == hp @@ -68,11 +82,8 @@ def test_lazy_import_nonbuiltins(): if not isinstance(np, lazy.DelayedImportErrorModule): assert np.sin(np.pi) == pytest.approx(0, 1e-6) if isinstance(sp, lazy.DelayedImportErrorModule): - try: + with pytest.raises(ModuleNotFoundError): sp.pi - raise AssertionError() - except ModuleNotFoundError: - pass def test_lazy_attach(): @@ -103,19 +114,68 @@ def test_lazy_attach(): if v is not None: assert locls[k] == v + # Exercise __getattr__, though it will just error + with pytest.raises(ImportError): + locls["__getattr__"]("mysubmodule") -def test_attach_same_module_and_attr_name(): - from lazy_loader.tests import fake_pkg + # Attribute is supposed to be imported, error on submodule load + with pytest.raises(ImportError): + locls["__getattr__"]("some_var_or_func") - # Grab attribute twice, to ensure that importing it does not - # override function by module - assert isinstance(fake_pkg.some_func, types.FunctionType) - assert isinstance(fake_pkg.some_func, types.FunctionType) + # Attribute is unknown, raise AttributeError + with pytest.raises(AttributeError): + locls["__getattr__"]("unknown_attr") - # Ensure imports from submodule still work - from lazy_loader.tests.fake_pkg.some_func import some_func - assert isinstance(some_func, types.FunctionType) +def test_lazy_attach_noattrs(): + name = "mymod" + submods = ["mysubmodule", "anothersubmodule"] + _, _, all_ = lazy.attach(name, submods) + + assert all_ == sorted(submods) + + +def test_lazy_attach_returns_copies(): + _get, _dir, _all = lazy.attach( + __name__, ["my_submodule", "another_submodule"], {"foo": ["some_attr"]} + ) + assert _dir() is not _dir() + assert _dir() == _all + assert _dir() is not _all + + expected = ["another_submodule", "my_submodule", "some_attr"] + assert _dir() == expected + assert _all == expected + assert _dir() is not _all + + _dir().append("modify_returned_list") + assert _dir() == expected + assert _all == expected + assert _dir() is not _all + + _all.append("modify_returned_all") + assert _dir() == expected + assert _all == [*expected, "modify_returned_all"] + + +@pytest.mark.parametrize("eager_import", [False, True]) +def test_attach_same_module_and_attr_name(clean_fake_pkg, eager_import): + env = {} + if eager_import: + env["EAGER_IMPORT"] = "1" + + with mock.patch.dict(os.environ, env): + from tests import fake_pkg + + # Grab attribute twice, to ensure that importing it does not + # override function by module + assert isinstance(fake_pkg.some_func, types.FunctionType) + assert isinstance(fake_pkg.some_func, types.FunctionType) + + # Ensure imports from submodule still work + from tests.fake_pkg.some_func import some_func + + assert isinstance(some_func, types.FunctionType) FAKE_STUB = """ @@ -134,7 +194,7 @@ def test_stub_loading(tmp_path): def test_stub_loading_parity(): - from lazy_loader.tests import fake_pkg + from tests import fake_pkg from_stub = lazy.attach_stub(fake_pkg.__name__, fake_pkg.__file__) stub_getter, stub_dir, stub_all = from_stub @@ -160,10 +220,8 @@ def test_stub_loading_errors(tmp_path): def test_require_kwarg(): - have_importlib_metadata = importlib.util.find_spec("importlib.metadata") is not None - dot = "." if have_importlib_metadata else "_" # Test with a module that definitely exists, behavior hinges on requirement - with mock.patch(f"importlib{dot}metadata.version") as version: + with mock.patch("importlib.metadata.version") as version: version.return_value = "1.0.0" math = lazy.load("math", require="somepkg >= 2.0") assert isinstance(math, lazy.DelayedImportErrorModule) @@ -175,6 +233,10 @@ def test_require_kwarg(): math = lazy.load("math", require="somepkg >= 2.0") assert isinstance(math, lazy.DelayedImportErrorModule) + # Eager failure + with pytest.raises(ModuleNotFoundError): + lazy.load("math", require="somepkg >= 2.0", error_on_import=True) + # When a module can be loaded but the version can't be checked, # raise a ValueError with pytest.raises(ValueError): @@ -188,5 +250,6 @@ def test_parallel_load(): [ sys.executable, os.path.join(os.path.dirname(__file__), "import_np_parallel.py"), - ] + ], + check=True, )