Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-lazy-loader for openSUSE:Factory checked in at 2024-04-21 20:28:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-lazy-loader (Old) and /work/SRC/openSUSE:Factory/.python-lazy-loader.new.26366 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-lazy-loader" Sun Apr 21 20:28:32 2024 rev:3 rq:1169421 version:0.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-lazy-loader/python-lazy-loader.changes 2023-12-08 22:32:42.113431470 +0100 +++ /work/SRC/openSUSE:Factory/.python-lazy-loader.new.26366/python-lazy-loader.changes 2024-04-21 20:30:03.200026156 +0200 @@ -1,0 +2,11 @@ +Sat Apr 20 21:09:04 UTC 2024 - Dirk Müller <[email protected]> + +- update to 0.4: + * ENH: Add require argument to load() to accept version + specifiers (#48). + * Add version as __version__ (#97). + * Avoid exception when __frame_data["code_context"] is None + (#83). + * Make `lazy_load.load` partially thread-safe (#90). + +------------------------------------------------------------------- Old: ---- lazy_loader-0.3.tar.gz New: ---- lazy_loader-0.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-lazy-loader.spec ++++++ --- /var/tmp/diff_new_pack.J4dpaL/_old 2024-04-21 20:30:03.704044658 +0200 +++ /var/tmp/diff_new_pack.J4dpaL/_new 2024-04-21 20:30:03.704044658 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-lazy-loader # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-lazy-loader -Version: 0.3 +Version: 0.4 Release: 0 Summary: Populate library namespace without incurring immediate import costs License: BSD-3-Clause @@ -26,6 +26,7 @@ BuildRequires: %{python_module flit-core} BuildRequires: %{python_module pip} BuildRequires: %{python_module pytest} +BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros Provides: python-lazy_loader = %{version}-%{release} ++++++ lazy_loader-0.3.tar.gz -> lazy_loader-0.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/.github/dependabot.yml new/lazy_loader-0.4/.github/dependabot.yml --- old/lazy_loader-0.3/.github/dependabot.yml 2023-06-28 03:54:21.971546200 +0200 +++ new/lazy_loader-0.4/.github/dependabot.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "monthly" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/.github/workflows/coverage.yml new/lazy_loader-0.4/.github/workflows/coverage.yml --- old/lazy_loader-0.3/.github/workflows/coverage.yml 2023-06-08 20:17:17.686092000 +0200 +++ new/lazy_loader-0.4/.github/workflows/coverage.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,37 +0,0 @@ -name: coverage - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - report: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10"] - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install packages - run: | - python -m pip install --upgrade pip wheel setuptools - python -m pip install ".[test]" - python -m pip install --upgrade numpy - python -m pip uninstall --yes scipy - pip list - - - name: Measure test coverage - run: | - python -m pytest --cov=lazy_loader --durations=10 - # Tests fail if using `--doctest-modules`. I.e., - # python -m pytest --cov=lazy_loader --doctest-modules --durations=20 - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/.github/workflows/lint.yml new/lazy_loader-0.4/.github/workflows/lint.yml --- old/lazy_loader-0.3/.github/workflows/lint.yml 2023-06-08 20:17:17.686092000 +0200 +++ new/lazy_loader-0.4/.github/workflows/lint.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,33 +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@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/.github/workflows/test.yml new/lazy_loader-0.4/.github/workflows/test.yml --- old/lazy_loader-0.3/.github/workflows/test.yml 2023-06-27 21:28:19.549616300 +0200 +++ new/lazy_loader-0.4/.github/workflows/test.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,44 +0,0 @@ -name: test - -on: [push, pull_request] - -jobs: - default: - runs-on: ${{ matrix.os }}-latest - strategy: - 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", - ] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install ".[test]" - - - name: Test - run: | - python -m pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/.gitignore new/lazy_loader-0.4/.gitignore --- old/lazy_loader-0.3/.gitignore 2023-06-08 20:17:17.686092000 +0200 +++ new/lazy_loader-0.4/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,29 +0,0 @@ -*~ -# Byte-compiled / optimized / DLL files -**/__pycache__ -*.py[cod] - -# Distribution / packaging -dist/ - -# Unit test / coverage reports -.pytest_cache/ - -# General -.DS_Store - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# IntelliJ project files -.idea - -# Spyder project settings -.spyderproject -.spyproject diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/.pre-commit-config.yaml new/lazy_loader-0.4/.pre-commit-config.yaml --- old/lazy_loader-0.3/.pre-commit-config.yaml 2023-06-28 03:54:21.971546200 +0200 +++ new/lazy_loader-0.4/.pre-commit-config.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -# Install pre-commit hooks via -# pre-commit install - -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: f71fa2c1f9cf5cb705f73dffe4b21f7c61470ba9 # v4.4.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: debug-statements - - id: check-ast - - id: mixed-line-ending - - id: check-yaml - args: [--allow-multiple-documents] - - id: check-json - - id: check-toml - - id: check-added-large-files - - - repo: https://github.com/pre-commit/mirrors-prettier - rev: 50c5478ed9e10bf360335449280cf2a67f4edb7a # v2.7.1 - hooks: - - id: prettier - files: \.(html|md|yml|yaml|toml) - args: [--prose-wrap=preserve] - - - repo: https://github.com/psf/black - rev: bf7a16254ec96b084a6caf3d435ec18f0f245cc7 # 23.3.0 - hooks: - - id: black - - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 1ac904bbe451ef0b5a437d1d3b331a244c1f272c # v0.0.275 - hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/CHANGELOG.md new/lazy_loader-0.4/CHANGELOG.md --- old/lazy_loader-0.3/CHANGELOG.md 2023-06-30 23:04:45.684111000 +0200 +++ new/lazy_loader-0.4/CHANGELOG.md 2024-04-05 15:02:52.000000000 +0200 @@ -1,3 +1,70 @@ +# 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)). + +## 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)). + +## 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)). + +## 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)). + +## Contributors + +4 authors added to this release (alphabetically): + +- Chris Markiewicz ([@effigies](https://github.com/effigies)) +- Dan Schult ([@dschult](https://github.com/dschult)) +- Jarrod Millman ([@jarrodmillman](https://github.com/jarrodmillman)) +- Stefan van der Walt ([@stefanv](https://github.com/stefanv)) + +5 reviewers added to this release (alphabetically): + +- Brigitta SipÅcz ([@bsipocz](https://github.com/bsipocz)) +- Chris Markiewicz ([@effigies](https://github.com/effigies)) +- Dan Schult ([@dschult](https://github.com/dschult)) +- Jarrod Millman ([@jarrodmillman](https://github.com/jarrodmillman)) +- Stefan van der Walt ([@stefanv](https://github.com/stefanv)) + +_These lists are automatically generated, and may not be complete or may contain +duplicates._ + # Changelog ## [v0.3](https://github.com/scientific-python/lazy_loader/tree/v0.3) (2023-06-30) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/MANIFEST.in new/lazy_loader-0.4/MANIFEST.in --- old/lazy_loader-0.3/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/MANIFEST.in 2024-04-05 15:02:52.000000000 +0200 @@ -0,0 +1 @@ +include CHANGELOG.md diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/PKG-INFO new/lazy_loader-0.4/PKG-INFO --- old/lazy_loader-0.3/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/PKG-INFO 2024-04-05 15:03:04.227013000 +0200 @@ -1,10 +1,40 @@ Metadata-Version: 2.1 Name: lazy_loader -Version: 0.3 -Summary: lazy_loader +Version: 0.4 +Summary: Makes it easy to load subpackages and functions on demand. Author: Scientific Python Developers -Requires-Python: >=3.7 -Description-Content-Type: text/markdown +License: BSD 3-Clause License + + Copyright (c) 2022--2023, Scientific Python project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Project-URL: Home, https://scientific-python.org/specs/spec-0001/ +Project-URL: Source, https://github.com/scientific-python/lazy_loader Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 3 @@ -14,13 +44,18 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 -Requires-Dist: pre-commit >= 3.3 ; extra == "lint" -Requires-Dist: pytest >= 7.4 ; extra == "test" -Requires-Dist: pytest-cov >= 4.1 ; extra == "test" -Project-URL: Home, https://scientific-python.org/specs/spec-0001/ -Project-URL: Source, https://github.com/scientific-python/lazy_loader -Provides-Extra: lint +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE.md +Requires-Dist: packaging +Requires-Dist: importlib_metadata; python_version < "3.8" Provides-Extra: test +Requires-Dist: pytest>=7.4; extra == "test" +Requires-Dist: pytest-cov>=4.1; extra == "test" +Provides-Extra: lint +Requires-Dist: pre-commit==3.7.0; extra == "lint" +Provides-Extra: dev +Requires-Dist: changelist==0.5; extra == "dev" [](https://pypi.org/project/lazy_loader/) [](https://github.com/scientific-python/lazy_loader/actions?query=workflow%3A%22test%22) @@ -41,6 +76,11 @@ pip install -U lazy_loader ``` +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). + ## Usage ### Lazily load subpackages @@ -139,7 +179,29 @@ You can ask `lazy.load` to raise import errors as soon as it is called: -``` +```python linalg = lazy.load('scipy.linalg', error_on_import=True) ``` +#### Optional requirements + +One use for lazy loading is for loading optional dependencies, with +`ImportErrors` only arising when optional functionality is accessed. If optional +functionality depends on a specific version, a version requirement can +be set: + +```python +np = lazy.load("numpy", require="numpy >=1.24") +``` + +In this case, if `numpy` is installed, but the version is less than 1.24, +the `np` module returned will raise an error on attribute access. Using +this feature is not all-or-nothing: One module may rely on one version of +numpy, while another module may not set any requirement. + +_Note that the requirement must use the package [distribution name][] instead +of the module [import name][]. For example, the `pyyaml` distribution provides +the `yaml` module for import._ + +[distribution name]: https://packaging.python.org/en/latest/glossary/#term-Distribution-Package +[import name]: https://packaging.python.org/en/latest/glossary/#term-Import-Package diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/README.md new/lazy_loader-0.4/README.md --- old/lazy_loader-0.3/README.md 2023-06-28 03:54:21.971546200 +0200 +++ new/lazy_loader-0.4/README.md 2024-04-05 15:02:52.000000000 +0200 @@ -17,6 +17,11 @@ pip install -U lazy_loader ``` +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). + ## Usage ### Lazily load subpackages @@ -115,6 +120,29 @@ You can ask `lazy.load` to raise import errors as soon as it is called: -``` +```python linalg = lazy.load('scipy.linalg', error_on_import=True) ``` + +#### Optional requirements + +One use for lazy loading is for loading optional dependencies, with +`ImportErrors` only arising when optional functionality is accessed. If optional +functionality depends on a specific version, a version requirement can +be set: + +```python +np = lazy.load("numpy", require="numpy >=1.24") +``` + +In this case, if `numpy` is installed, but the version is less than 1.24, +the `np` module returned will raise an error on attribute access. Using +this feature is not all-or-nothing: One module may rely on one version of +numpy, while another module may not set any requirement. + +_Note that the requirement must use the package [distribution name][] instead +of the module [import name][]. For example, the `pyyaml` distribution provides +the `yaml` module for import._ + +[distribution name]: https://packaging.python.org/en/latest/glossary/#term-Distribution-Package +[import name]: https://packaging.python.org/en/latest/glossary/#term-Import-Package diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/RELEASE.md new/lazy_loader-0.4/RELEASE.md --- old/lazy_loader-0.3/RELEASE.md 2023-06-30 22:21:20.615294000 +0200 +++ new/lazy_loader-0.4/RELEASE.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,58 +0,0 @@ -# Release process for `lazy_loader` - -## Introduction - -Example `version` - -- 1.8.dev0 # development version of 1.8 (release candidate 1) -- 1.8rc1 # 1.8 release candidate 1 -- 1.8rc2.dev0 # development version of 1.8 (release candidate 2) -- 1.8 # 1.8 release -- 1.9.dev0 # development version of 1.9 (release candidate 1) - -## Process - -- Update and review `CHANGELOG.md`: - - gem install github_changelog_generator - github_changelog_generator -u scientific-python -p lazy_loader --since-tag=<last tag> - -- Update `version` in `pyproject.toml`. - -- Commit changes: - - git add pyproject.toml CHANGELOG.md - git commit -m 'Designate <version> release' - -- Add the version number (e.g., `1.2.0`) as a tag in git: - - git tag -s [-u <key-id>] v<version> -m 'signed <version> tag' - - If you do not have a gpg key, use -u instead; it is important for - Debian packaging that the tags are annotated - -- Push the new meta-data to github: - - git push --tags origin main - - where `origin` is the name of the `github.com:scientific-python/lazy_loader` - repository - -- Review the github release page: - - https://github.com/scientific-python/lazy_loader/releases - -- Publish on PyPi: - - git clean -fxd - pip install flit - flit build - flit publish - -- Update `version` in `pyproject.toml`. - -- Commit changes: - - git add pyproject.toml - git commit -m 'Bump version' - git push origin main diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader/__init__.py new/lazy_loader-0.4/lazy_loader/__init__.py --- old/lazy_loader-0.3/lazy_loader/__init__.py 2023-06-28 03:54:21.972546000 +0200 +++ new/lazy_loader-0.4/lazy_loader/__init__.py 2024-04-05 15:02:52.000000000 +0200 @@ -4,18 +4,23 @@ Makes it easy to load subpackages and functions on demand. """ + import ast import importlib import importlib.util -import inspect import os import sys +import threading import types import warnings +__version__ = "0.4" __all__ = ["attach", "load", "attach_stub"] +threadlock = threading.Lock() + + def attach(package_name, submodules=None, submod_attrs=None): """Attach lazily loaded submodules, functions, or other attributes. @@ -99,24 +104,25 @@ class DelayedImportErrorModule(types.ModuleType): - def __init__(self, frame_data, *args, **kwargs): + def __init__(self, frame_data, *args, message, **kwargs): self.__frame_data = frame_data + self.__message = message super().__init__(*args, **kwargs) def __getattr__(self, x): - if x in ("__class__", "__file__", "__frame_data"): + if x in ("__class__", "__file__", "__frame_data", "__message"): super().__getattr__(x) else: fd = self.__frame_data raise ModuleNotFoundError( - f"No module named '{fd['spec']}'\n\n" + 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"]).strip()}' + f'----> {"".join(fd["code_context"] or "").strip()}' ) -def load(fullname, error_on_import=False): +def load(fullname, *, require=None, error_on_import=False): """Return a lazily imported proxy for a module. We often see the following pattern:: @@ -160,6 +166,14 @@ sp = lazy.load('scipy') # import scipy as sp + require : str + A dependency requirement as defined in PEP-508. For example:: + + "numpy >=1.24" + + If defined, the proxy module will raise an error if the installed + version does not satisfy the requirement. + error_on_import : bool 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. @@ -171,46 +185,104 @@ Actual loading of the module occurs upon first attribute request. """ - try: - return sys.modules[fullname] - except KeyError: - pass - - if "." 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." - "So, you probably shouldn't use subpackages with this lazy feature." - ) - warnings.warn(msg, RuntimeWarning) - - spec = importlib.util.find_spec(fullname) - if spec is None: - if error_on_import: - raise ModuleNotFoundError(f"No module named '{fullname}'") - else: + with threadlock: + module = sys.modules.get(fullname) + have_module = module is not None + + # Most common, short-circuit + if have_module and require is None: + return module + + if "." 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." + "So, you probably shouldn't use subpackages with this lazy feature." + ) + warnings.warn(msg, RuntimeWarning) + + spec = None + + if not have_module: + spec = importlib.util.find_spec(fullname) + have_module = spec is not None + + if not have_module: + not_found_message = f"No module named '{fullname}'" + elif require is not None: + try: + have_module = _check_requirement(require) + except ModuleNotFoundError as e: + raise ValueError( + f"Found module '{fullname}' but cannot test " + "requirement '{require}'. " + "Requirements must match distribution name, not module name." + ) from e + + not_found_message = f"No distribution can be found matching '{require}'" + + if not have_module: + if error_on_import: + raise ModuleNotFoundError(not_found_message) + import inspect + try: parent = inspect.stack()[1] frame_data = { - "spec": fullname, "filename": parent.filename, "lineno": parent.lineno, "function": parent.function, "code_context": parent.code_context, } - return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule") + return DelayedImportErrorModule( + frame_data, + "DelayedImportErrorModule", + message=not_found_message, + ) finally: del parent - module = importlib.util.module_from_spec(spec) - sys.modules[fullname] = module + if spec is not None: + module = importlib.util.module_from_spec(spec) + sys.modules[fullname] = module - loader = importlib.util.LazyLoader(spec.loader) - loader.exec_module(module) + loader = importlib.util.LazyLoader(spec.loader) + loader.exec_module(module) return module +def _check_requirement(require: str) -> bool: + """Verify that a package requirement is satisfied + + If the package is required, a ``ModuleNotFoundError`` is raised + by ``importlib.metadata``. + + Parameters + ---------- + require : str + A dependency requirement as defined in PEP-508 + + Returns + ------- + satisfied : bool + True if the installed version of the dependency matches + the specified version, False otherwise. + """ + import packaging.requirements + + try: + import importlib.metadata as importlib_metadata + except ImportError: # PY37 + import importlib_metadata + + req = packaging.requirements.Requirement(require) + return req.specifier.contains( + importlib_metadata.version(req.name), + prereleases=True, + ) + + class _StubVisitor(ast.NodeVisitor): """AST visitor to parse a stub file for submodules and submod_attrs.""" @@ -225,7 +297,13 @@ ) if node.module: attrs: list = self._submod_attrs.setdefault(node.module, []) - attrs.extend(alias.name for alias in node.names) + aliases = [alias.name for alias in node.names] + if "*" in aliases: + raise ValueError( + "lazy stub loader does not support star import " + f"`from {node.module} import *`" + ) + attrs.extend(aliases) else: self._submodules.update(alias.name for alias in node.names) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader/tests/import_np_parallel.py new/lazy_loader-0.4/lazy_loader/tests/import_np_parallel.py --- old/lazy_loader-0.3/lazy_loader/tests/import_np_parallel.py 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/lazy_loader/tests/import_np_parallel.py 2024-04-05 15:02:52.000000000 +0200 @@ -0,0 +1,13 @@ +import threading +import time + +import lazy_loader as lazy + + +def import_np(): + time.sleep(0.5) + lazy.load("numpy") + + +for _ in range(10): + threading.Thread(target=import_np).start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader/tests/test_lazy_loader.py new/lazy_loader-0.4/lazy_loader/tests/test_lazy_loader.py --- old/lazy_loader-0.3/lazy_loader/tests/test_lazy_loader.py 2023-06-28 03:54:21.972546000 +0200 +++ new/lazy_loader-0.4/lazy_loader/tests/test_lazy_loader.py 2024-04-05 15:02:52.000000000 +0200 @@ -1,6 +1,9 @@ import importlib +import os +import subprocess import sys import types +from unittest import mock import pytest @@ -149,3 +152,41 @@ with pytest.raises(ValueError, match="Cannot load imports from non-existent stub"): lazy.attach_stub("name", "not a file") + + stub2 = tmp_path / "stub2.pyi" + stub2.write_text("from .mod import *\n") + with pytest.raises(ValueError, match=".*does not support star import"): + lazy.attach_stub("name", str(stub2)) + + +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: + version.return_value = "1.0.0" + math = lazy.load("math", require="somepkg >= 2.0") + assert isinstance(math, lazy.DelayedImportErrorModule) + + math = lazy.load("math", require="somepkg >= 1.0") + assert math.sin(math.pi) == pytest.approx(0, 1e-6) + + # We can fail even after a successful import + math = lazy.load("math", require="somepkg >= 2.0") + assert isinstance(math, lazy.DelayedImportErrorModule) + + # When a module can be loaded but the version can't be checked, + # raise a ValueError + with pytest.raises(ValueError): + lazy.load("math", require="somepkg >= 1.0") + + +def test_parallel_load(): + pytest.importorskip("numpy") + + subprocess.run( + [ + sys.executable, + os.path.join(os.path.dirname(__file__), "import_np_parallel.py"), + ] + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader.egg-info/PKG-INFO new/lazy_loader-0.4/lazy_loader.egg-info/PKG-INFO --- old/lazy_loader-0.3/lazy_loader.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/lazy_loader.egg-info/PKG-INFO 2024-04-05 15:03:04.000000000 +0200 @@ -0,0 +1,207 @@ +Metadata-Version: 2.1 +Name: lazy_loader +Version: 0.4 +Summary: Makes it easy to load subpackages and functions on demand. +Author: Scientific Python Developers +License: BSD 3-Clause License + + Copyright (c) 2022--2023, Scientific Python project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Project-URL: Home, https://scientific-python.org/specs/spec-0001/ +Project-URL: Source, https://github.com/scientific-python/lazy_loader +Classifier: Development Status :: 4 - Beta +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE.md +Requires-Dist: packaging +Requires-Dist: importlib_metadata; python_version < "3.8" +Provides-Extra: test +Requires-Dist: pytest>=7.4; extra == "test" +Requires-Dist: pytest-cov>=4.1; extra == "test" +Provides-Extra: lint +Requires-Dist: pre-commit==3.7.0; extra == "lint" +Provides-Extra: dev +Requires-Dist: changelist==0.5; extra == "dev" + +[](https://pypi.org/project/lazy_loader/) +[](https://github.com/scientific-python/lazy_loader/actions?query=workflow%3A%22test%22) +[](https://app.codecov.io/gh/scientific-python/lazy_loader/branch/main) + +`lazy_loader` makes it easy to load subpackages and functions on demand. + +## Motivation + +1. Allow subpackages to be made visible to users without incurring import costs. +2. Allow external libraries to be imported only when used, improving import times. + +For a more detailed discussion, see [the SPEC](https://scientific-python.org/specs/spec-0001/). + +## Installation + +``` +pip install -U lazy_loader +``` + +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). + +## Usage + +### Lazily load subpackages + +Consider the `__init__.py` from [scikit-image](https://scikit-image.org): + +```python +subpackages = [ + ..., + 'filters', + ... +] + +import lazy_loader as lazy +__getattr__, __dir__, _ = lazy.attach(__name__, subpackages) +``` + +You can now do: + +```python +import skimage as ski +ski.filters.gaussian(...) +``` + +The `filters` subpackages will only be loaded once accessed. + +### Lazily load subpackages and functions + +Consider `skimage/filters/__init__.py`: + +```python +from ..util import lazy + +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=['rank'], + submod_attrs={ + '_gaussian': ['gaussian', 'difference_of_gaussians'], + 'edges': ['sobel', 'scharr', 'prewitt', 'roberts', + 'laplace', 'farid'] + } +) +``` + +The above is equivalent to: + +```python +from . import rank +from ._gaussian import gaussian, difference_of_gaussians +from .edges import (sobel, scharr, prewitt, roberts, + laplace, farid) +``` + +Except that all subpackages (such as `rank`) and functions (such as `sobel`) are loaded upon access. + +### Type checkers + +Static type checkers and IDEs cannot infer type information from +lazily loaded imports. As a workaround you can load [type +stubs](https://mypy.readthedocs.io/en/stable/stubs.html) (`.pyi` +files) with `lazy.attach_stub`: + +```python +import lazy_loader as lazy +__getattr__, __dir__, _ = lazy.attach_stub(__name__, "subpackages.pyi") +``` + +Note that, since imports are now defined in `.pyi` files, those +are not only necessary for type checking but also at runtime. + +The SPEC [describes this workaround in more +detail](https://scientific-python.org/specs/spec-0001/#type-checkers). + +### Early failure + +With lazy loading, missing imports no longer fail upon loading the +library. During development and testing, you can set the `EAGER_IMPORT` +environment variable to disable lazy loading. + +### External libraries + +The `lazy.attach` function discussed above is used to set up package +internal imports. + +Use `lazy.load` to lazily import external libraries: + +```python +sp = lazy.load('scipy') # `sp` will only be loaded when accessed +sp.linalg.norm(...) +``` + +_Note that lazily importing *sub*packages, +i.e. `load('scipy.linalg')` will cause the package containing the +subpackage to be imported immediately; thus, this usage is +discouraged._ + +You can ask `lazy.load` to raise import errors as soon as it is called: + +```python +linalg = lazy.load('scipy.linalg', error_on_import=True) +``` + +#### Optional requirements + +One use for lazy loading is for loading optional dependencies, with +`ImportErrors` only arising when optional functionality is accessed. If optional +functionality depends on a specific version, a version requirement can +be set: + +```python +np = lazy.load("numpy", require="numpy >=1.24") +``` + +In this case, if `numpy` is installed, but the version is less than 1.24, +the `np` module returned will raise an error on attribute access. Using +this feature is not all-or-nothing: One module may rely on one version of +numpy, while another module may not set any requirement. + +_Note that the requirement must use the package [distribution name][] instead +of the module [import name][]. For example, the `pyyaml` distribution provides +the `yaml` module for import._ + +[distribution name]: https://packaging.python.org/en/latest/glossary/#term-Distribution-Package +[import name]: https://packaging.python.org/en/latest/glossary/#term-Import-Package diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader.egg-info/SOURCES.txt new/lazy_loader-0.4/lazy_loader.egg-info/SOURCES.txt --- old/lazy_loader-0.3/lazy_loader.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/lazy_loader.egg-info/SOURCES.txt 2024-04-05 15:03:04.000000000 +0200 @@ -0,0 +1,17 @@ +CHANGELOG.md +LICENSE.md +MANIFEST.in +README.md +pyproject.toml +lazy_loader/__init__.py +lazy_loader.egg-info/PKG-INFO +lazy_loader.egg-info/SOURCES.txt +lazy_loader.egg-info/dependency_links.txt +lazy_loader.egg-info/requires.txt +lazy_loader.egg-info/top_level.txt +lazy_loader/tests/__init__.py +lazy_loader/tests/import_np_parallel.py +lazy_loader/tests/test_lazy_loader.py +lazy_loader/tests/fake_pkg/__init__.py +lazy_loader/tests/fake_pkg/__init__.pyi +lazy_loader/tests/fake_pkg/some_func.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader.egg-info/dependency_links.txt new/lazy_loader-0.4/lazy_loader.egg-info/dependency_links.txt --- old/lazy_loader-0.3/lazy_loader.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/lazy_loader.egg-info/dependency_links.txt 2024-04-05 15:03:04.000000000 +0200 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader.egg-info/requires.txt new/lazy_loader-0.4/lazy_loader.egg-info/requires.txt --- old/lazy_loader-0.3/lazy_loader.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/lazy_loader.egg-info/requires.txt 2024-04-05 15:03:04.000000000 +0200 @@ -0,0 +1,14 @@ +packaging + +[:python_version < "3.8"] +importlib_metadata + +[dev] +changelist==0.5 + +[lint] +pre-commit==3.7.0 + +[test] +pytest>=7.4 +pytest-cov>=4.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/lazy_loader.egg-info/top_level.txt new/lazy_loader-0.4/lazy_loader.egg-info/top_level.txt --- old/lazy_loader-0.3/lazy_loader.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/lazy_loader.egg-info/top_level.txt 2024-04-05 15:03:04.000000000 +0200 @@ -0,0 +1 @@ +lazy_loader diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/pyproject.toml new/lazy_loader-0.4/pyproject.toml --- old/lazy_loader-0.3/pyproject.toml 2023-06-30 23:08:43.595186200 +0200 +++ new/lazy_loader-0.4/pyproject.toml 2024-04-05 15:02:52.000000000 +0200 @@ -1,14 +1,14 @@ [build-system] -requires = ["flit_core >=3.8,<4"] -build-backend = "flit_core.buildapi" +requires = ["setuptools>=61.2"] +build-backend = "setuptools.build_meta" [project] name = "lazy_loader" -version = "0.3" requires-python = ">=3.7" authors = [{name = "Scientific Python Developers"}] readme = "README.md" license = {file = "LICENSE.md"} +dynamic = ['version'] classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: BSD License", @@ -20,19 +20,27 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -dynamic = ["description"] - +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.3"] +lint = ["pre-commit == 3.7.0"] +dev = ["changelist == 0.5"] [project.urls] Home = "https://scientific-python.org/specs/spec-0001/" Source = "https://github.com/scientific-python/lazy_loader" -[tool.flit.sdist] -exclude = ["tests/*"] + +[tool.changelist] +ignored_user_logins = ["dependabot[bot]", "pre-commit-ci[bot]", "web-flow"] + +[tool.setuptools.dynamic.version] +attr = 'lazy_loader.__version__' [tool.ruff] line-length = 88 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lazy_loader-0.3/setup.cfg new/lazy_loader-0.4/setup.cfg --- old/lazy_loader-0.3/setup.cfg 1970-01-01 01:00:00.000000000 +0100 +++ new/lazy_loader-0.4/setup.cfg 2024-04-05 15:03:04.227013000 +0200 @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 +
