lazy-bear 0.0.2.post1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lazy_bear-0.0.2.post1/.copier-answers.yml +19 -0
- lazy_bear-0.0.2.post1/.github/workflows/ci.yml +45 -0
- lazy_bear-0.0.2.post1/.github/workflows/release.yml +46 -0
- lazy_bear-0.0.2.post1/.gitignore +219 -0
- lazy_bear-0.0.2.post1/.python_version +1 -0
- lazy_bear-0.0.2.post1/CHANGELOG.md +8 -0
- lazy_bear-0.0.2.post1/PKG-INFO +91 -0
- lazy_bear-0.0.2.post1/README.md +82 -0
- lazy_bear-0.0.2.post1/config/coverage.ini +25 -0
- lazy_bear-0.0.2.post1/config/git-changelog.toml +9 -0
- lazy_bear-0.0.2.post1/config/lazy_bear/test.toml +4 -0
- lazy_bear-0.0.2.post1/config/pytest.ini +20 -0
- lazy_bear-0.0.2.post1/config/ruff.toml +142 -0
- lazy_bear-0.0.2.post1/directory_structure.xml +46 -0
- lazy_bear-0.0.2.post1/hatch_build.py +122 -0
- lazy_bear-0.0.2.post1/maskfile.md +121 -0
- lazy_bear-0.0.2.post1/noxfile.py +67 -0
- lazy_bear-0.0.2.post1/pyproject.toml +87 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/__init__.py +13 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/__main__.py +14 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/__init__.py +0 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/_cmds.py +49 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/_info.py +200 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/_version.pyi +6 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/_versioning.py +272 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/cli.py +45 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/_internal/debug.py +101 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/lazy_attribute.py +109 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/lazy_imports.py +132 -0
- lazy_bear-0.0.2.post1/src/lazy_bear/py.typed +0 -0
- lazy_bear-0.0.2.post1/tests/__init__.py +7 -0
- lazy_bear-0.0.2.post1/tests/conftest.py +7 -0
- lazy_bear-0.0.2.post1/tests/test_api.py +140 -0
- lazy_bear-0.0.2.post1/tests/test_cli.py +94 -0
- lazy_bear-0.0.2.post1/tests/test_config.py +16 -0
- lazy_bear-0.0.2.post1/tests/test_lazy_attr.py +61 -0
- lazy_bear-0.0.2.post1/tests/test_lazy_imports.py +124 -0
- lazy_bear-0.0.2.post1/tests/test_misc_funcs.py +57 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changes here will be overwritten by Copier.
|
|
2
|
+
_commit: v0.3.3
|
|
3
|
+
_src_path: gh:sicksubroutine/python-template
|
|
4
|
+
author_email: bright.lid5647@fastmail.com
|
|
5
|
+
author_fullname: chaz
|
|
6
|
+
include_cli: true
|
|
7
|
+
include_docker: false
|
|
8
|
+
include_docs: false
|
|
9
|
+
include_fastapi: false
|
|
10
|
+
interactive_cli: false
|
|
11
|
+
minimum_python_version: '3.13'
|
|
12
|
+
project_description: A lazy import package for both modules and items within modules.
|
|
13
|
+
project_name: Lazy Bear
|
|
14
|
+
python_package_command_line_name: lazy-bear
|
|
15
|
+
python_package_distribution_name: lazy-bear
|
|
16
|
+
python_package_import_name: lazy_bear
|
|
17
|
+
setup_project: true
|
|
18
|
+
starting_version: 0.0.1
|
|
19
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
defaults:
|
|
10
|
+
run:
|
|
11
|
+
shell: bash
|
|
12
|
+
|
|
13
|
+
env:
|
|
14
|
+
LANG: en_US.utf-8
|
|
15
|
+
LC_ALL: en_US.utf-8
|
|
16
|
+
PYTHONIOENCODING: UTF-8
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
quality:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Setup UV
|
|
24
|
+
uses: astral-sh/setup-uv@v5
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
cache-dependency-glob: pyproject.toml
|
|
28
|
+
python-version: "3.13"
|
|
29
|
+
|
|
30
|
+
- name: Install Dependencies
|
|
31
|
+
run: |
|
|
32
|
+
uv sync
|
|
33
|
+
source .venv/bin/activate
|
|
34
|
+
|
|
35
|
+
- name: Install Nox
|
|
36
|
+
run: uv tool install nox
|
|
37
|
+
|
|
38
|
+
- name: Check the Code Quality
|
|
39
|
+
run: nox -s ruff_fix
|
|
40
|
+
|
|
41
|
+
- name: Check if the code is correctly typed
|
|
42
|
+
run: nox -s pyright
|
|
43
|
+
|
|
44
|
+
- name: Run Tests
|
|
45
|
+
run: pytest -v
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on: push
|
|
4
|
+
permissions:
|
|
5
|
+
contents: write
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
release:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
fetch-tags: true
|
|
17
|
+
#
|
|
18
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
19
|
+
#
|
|
20
|
+
name: Setup Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.13"
|
|
24
|
+
- name: Setup uv
|
|
25
|
+
uses: astral-sh/setup-uv@v5
|
|
26
|
+
- name: Update changelog
|
|
27
|
+
run: uv tool run git-changelog --config-file config/git-changelog.toml
|
|
28
|
+
- name: Commit updated changelog
|
|
29
|
+
run: |
|
|
30
|
+
git config --local user.email "action@github.com"
|
|
31
|
+
git config --local user.name "GitHub Action"
|
|
32
|
+
git add CHANGELOG.md
|
|
33
|
+
if git diff --staged --quiet; then
|
|
34
|
+
echo "No changelog changes to commit"
|
|
35
|
+
else
|
|
36
|
+
git commit -m "chore: update changelog for $(git describe --tags --abbrev=0)"
|
|
37
|
+
#
|
|
38
|
+
git push origin HEAD:${{ github.event.repository.default_branch }} || echo "Failed to push changelog update, but continuing with release"
|
|
39
|
+
#
|
|
40
|
+
fi
|
|
41
|
+
- name: Prepare release notes
|
|
42
|
+
run: uv tool run git-changelog --release-notes > release-notes.md
|
|
43
|
+
- name: Create release
|
|
44
|
+
uses: softprops/action-gh-release@v2
|
|
45
|
+
with:
|
|
46
|
+
body_path: release-notes.md
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python,macos,shell
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python,macos,shell
|
|
3
|
+
|
|
4
|
+
### macOS ###
|
|
5
|
+
# General
|
|
6
|
+
.DS_Store
|
|
7
|
+
.AppleDouble
|
|
8
|
+
.LSOverride
|
|
9
|
+
|
|
10
|
+
# Icon must end with two \r
|
|
11
|
+
Icon
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Thumbnails
|
|
15
|
+
._*
|
|
16
|
+
|
|
17
|
+
# Files that might appear in the root of a volume
|
|
18
|
+
.DocumentRevisions-V100
|
|
19
|
+
.fseventsd
|
|
20
|
+
.Spotlight-V100
|
|
21
|
+
.TemporaryItems
|
|
22
|
+
.Trashes
|
|
23
|
+
.VolumeIcon.icns
|
|
24
|
+
.com.apple.timemachine.donotpresent
|
|
25
|
+
|
|
26
|
+
# Directories potentially created on remote AFP share
|
|
27
|
+
.AppleDB
|
|
28
|
+
.AppleDesktop
|
|
29
|
+
Network Trash Folder
|
|
30
|
+
Temporary Items
|
|
31
|
+
.apdisk
|
|
32
|
+
|
|
33
|
+
### macOS Patch ###
|
|
34
|
+
# iCloud generated files
|
|
35
|
+
*.icloud
|
|
36
|
+
|
|
37
|
+
### Python ###
|
|
38
|
+
# Byte-compiled / optimized / DLL files
|
|
39
|
+
__pycache__/
|
|
40
|
+
*.py[cod]
|
|
41
|
+
*$py.class
|
|
42
|
+
|
|
43
|
+
# C extensions
|
|
44
|
+
*.so
|
|
45
|
+
|
|
46
|
+
# Distribution / packaging
|
|
47
|
+
.Python
|
|
48
|
+
build/
|
|
49
|
+
develop-eggs/
|
|
50
|
+
dist/
|
|
51
|
+
downloads/
|
|
52
|
+
eggs/
|
|
53
|
+
.eggs/
|
|
54
|
+
lib/
|
|
55
|
+
lib64/
|
|
56
|
+
parts/
|
|
57
|
+
sdist/
|
|
58
|
+
var/
|
|
59
|
+
wheels/
|
|
60
|
+
share/python-wheels/
|
|
61
|
+
*.egg-info/
|
|
62
|
+
.installed.cfg
|
|
63
|
+
*.egg
|
|
64
|
+
MANIFEST
|
|
65
|
+
|
|
66
|
+
# PyInstaller
|
|
67
|
+
# Usually these files are written by a python script from a template
|
|
68
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
69
|
+
*.manifest
|
|
70
|
+
*.spec
|
|
71
|
+
|
|
72
|
+
# Installer logs
|
|
73
|
+
pip-log.txt
|
|
74
|
+
pip-delete-this-directory.txt
|
|
75
|
+
|
|
76
|
+
# Unit test / coverage reports
|
|
77
|
+
htmlcov/
|
|
78
|
+
.tox/
|
|
79
|
+
.nox/
|
|
80
|
+
.coverage
|
|
81
|
+
.coverage.*
|
|
82
|
+
.cache
|
|
83
|
+
nosetests.xml
|
|
84
|
+
coverage.xml
|
|
85
|
+
*.cover
|
|
86
|
+
*.py,cover
|
|
87
|
+
.hypothesis/
|
|
88
|
+
.pytest_cache/
|
|
89
|
+
cover/
|
|
90
|
+
|
|
91
|
+
# Translations
|
|
92
|
+
*.mo
|
|
93
|
+
*.pot
|
|
94
|
+
|
|
95
|
+
# Django stuff:
|
|
96
|
+
*.log
|
|
97
|
+
local_settings.py
|
|
98
|
+
db.sqlite3
|
|
99
|
+
db.sqlite3-journal
|
|
100
|
+
|
|
101
|
+
# Flask stuff:
|
|
102
|
+
instance/
|
|
103
|
+
.webassets-cache
|
|
104
|
+
|
|
105
|
+
# Scrapy stuff:
|
|
106
|
+
.scrapy
|
|
107
|
+
|
|
108
|
+
# Sphinx documentation
|
|
109
|
+
docs/_build/
|
|
110
|
+
|
|
111
|
+
# PyBuilder
|
|
112
|
+
.pybuilder/
|
|
113
|
+
target/
|
|
114
|
+
|
|
115
|
+
# Jupyter Notebook
|
|
116
|
+
.ipynb_checkpoints
|
|
117
|
+
|
|
118
|
+
# IPython
|
|
119
|
+
profile_default/
|
|
120
|
+
ipython_config.py
|
|
121
|
+
|
|
122
|
+
# pyenv
|
|
123
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
124
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
125
|
+
.python-version
|
|
126
|
+
|
|
127
|
+
# pipenv
|
|
128
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
129
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
130
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
131
|
+
# install all needed dependencies.
|
|
132
|
+
#Pipfile.lock
|
|
133
|
+
|
|
134
|
+
# poetry
|
|
135
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
136
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
137
|
+
# commonly ignored for libraries.
|
|
138
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
139
|
+
#poetry.lock
|
|
140
|
+
|
|
141
|
+
# pdm
|
|
142
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
143
|
+
#pdm.lock
|
|
144
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
145
|
+
# in version control.
|
|
146
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
147
|
+
.pdm.toml
|
|
148
|
+
|
|
149
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
150
|
+
__pypackages__/
|
|
151
|
+
|
|
152
|
+
# Celery stuff
|
|
153
|
+
celerybeat-schedule
|
|
154
|
+
celerybeat.pid
|
|
155
|
+
|
|
156
|
+
# SageMath parsed files
|
|
157
|
+
*.sage.py
|
|
158
|
+
|
|
159
|
+
# Environments
|
|
160
|
+
.env
|
|
161
|
+
.venv
|
|
162
|
+
env/
|
|
163
|
+
venv/
|
|
164
|
+
ENV/
|
|
165
|
+
env.bak/
|
|
166
|
+
venv.bak/
|
|
167
|
+
|
|
168
|
+
# Spyder project settings
|
|
169
|
+
.spyderproject
|
|
170
|
+
.spyproject
|
|
171
|
+
|
|
172
|
+
# Rope project settings
|
|
173
|
+
.ropeproject
|
|
174
|
+
|
|
175
|
+
# mkdocs documentation
|
|
176
|
+
/site
|
|
177
|
+
|
|
178
|
+
# mypy
|
|
179
|
+
.mypy_cache/
|
|
180
|
+
.dmypy.json
|
|
181
|
+
dmypy.json
|
|
182
|
+
``
|
|
183
|
+
# Pyre type checker
|
|
184
|
+
.pyre/
|
|
185
|
+
|
|
186
|
+
# pytype static type analyzer
|
|
187
|
+
.pytype/
|
|
188
|
+
|
|
189
|
+
# Cython debug symbols
|
|
190
|
+
cython_debug/
|
|
191
|
+
|
|
192
|
+
# PyCharm
|
|
193
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
194
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
195
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
196
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
197
|
+
.idea/
|
|
198
|
+
|
|
199
|
+
### Python Patch ###
|
|
200
|
+
# Poetry local configuration file `
|
|
201
|
+
|
|
202
|
+
# ruff
|
|
203
|
+
.ruff_cache/
|
|
204
|
+
|
|
205
|
+
# LSP config files
|
|
206
|
+
pyrightconfig.json
|
|
207
|
+
|
|
208
|
+
.vscode/
|
|
209
|
+
|
|
210
|
+
*.db
|
|
211
|
+
*.log
|
|
212
|
+
**/_internal/_version.py
|
|
213
|
+
uv.lock
|
|
214
|
+
|
|
215
|
+
!/project/.vscode
|
|
216
|
+
|
|
217
|
+
**/prod.toml
|
|
218
|
+
|
|
219
|
+
**/reports
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
<!-- insertion marker -->
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lazy-bear
|
|
3
|
+
Version: 0.0.2.post1
|
|
4
|
+
Summary: A lazy import package for both modules and items within modules.
|
|
5
|
+
Author-email: chaz <bright.lid5647@fastmail.com>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: pydantic>=2.11.5
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Lazy Bear
|
|
11
|
+
|
|
12
|
+
[](https://pypi.org/project/lazy-bear/)
|
|
13
|
+
[](https://www.python.org/)
|
|
14
|
+
|
|
15
|
+
Lazy Bear is a lightweight toolkit for deferring Python imports until the first time you actually need them. Keep startup fast, trim optional dependencies, and expose a clean API without fighting `import` order.
|
|
16
|
+
|
|
17
|
+
## Highlights
|
|
18
|
+
|
|
19
|
+
- Drop-in `lazy("package.module")` helper that mirrors a regular module object once touched.
|
|
20
|
+
- `LazyLoader.to()` and `LazyLoader.to_many()` for lazily binding one or more attributes without importing the whole module up front.
|
|
21
|
+
- `LazyAttr.value`/`LazyAttr.unwrap()` for grabbing the underlying attribute (dicts, constants, classes) without changing call semantics.
|
|
22
|
+
- Thread-safe module caching that updates `sys.modules` and the caller's globals exactly once.
|
|
23
|
+
- Debug-friendly repr and `dir()` support so interactive sessions stay intuitive even before loading.
|
|
24
|
+
- Batteries-included CLI with version info, environment diagnostics, and release bump helpers.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Requires Python 3.13 or newer.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install lazy-bear
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
With [`uv`](https://docs.astral.sh/uv/):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv tool install lazy-bear
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from lazy_bear import lazy
|
|
44
|
+
|
|
45
|
+
json = lazy("json") # nothing imported yet
|
|
46
|
+
|
|
47
|
+
payload = {"hello": "lazy bear"}
|
|
48
|
+
print(json.dumps(payload, sort_keys=True))
|
|
49
|
+
# Module is imported the first time .dumps is accessed.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Under the hood `lazy()` returns a `LazyLoader` (a `types.ModuleType` subclass). Once you touch an attribute it:
|
|
53
|
+
|
|
54
|
+
1. Imports the target module.
|
|
55
|
+
2. Registers it in both `sys.modules` and the globals of the caller.
|
|
56
|
+
3. Proxies the module so future access is indistinguishable from a normal import.
|
|
57
|
+
|
|
58
|
+
## Lazily Accessing Attributes
|
|
59
|
+
|
|
60
|
+
Sometimes you only need a specific callable or attribute, not the whole module namespace:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from lazy_bear import LazyAttr, lazy
|
|
64
|
+
|
|
65
|
+
console = lazy("rich.console")
|
|
66
|
+
Console: LazyAttr = console.to("Console")
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
rich_console = Console(width=100) # `rich.console` is imported here
|
|
70
|
+
rich_console.print("[bold green]Hello from Lazy Bear![/]")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Need multiple attributes or callables? Use `to_many`:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
math = lazy("math")
|
|
77
|
+
sqrt_attr, pow_attr = math.to_many("sqrt", "pow")
|
|
78
|
+
|
|
79
|
+
print(sqrt_attr(25)) # LazyAttr stays callable
|
|
80
|
+
pow_func = pow_attr.value # or pow_attr.unwrap()
|
|
81
|
+
print(pow_func(2, 3))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`LazyAttr` proxies most dunder methods (`__call__`, `__iter__`, `__getitem__`, etc.), so lazily loaded attributes behave just like the originals.
|
|
85
|
+
|
|
86
|
+
When you need the underlying object—say a dictionary of settings—forgo calling and reach for `.value` (or its alias `.unwrap()`):
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
env = lazy("os").to("environ").value # returns the real os._Environ mapping
|
|
90
|
+
letters = lazy("string").to("ascii_letters").unwrap() # works for constants too
|
|
91
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Lazy Bear
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/lazy-bear/)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
|
|
6
|
+
Lazy Bear is a lightweight toolkit for deferring Python imports until the first time you actually need them. Keep startup fast, trim optional dependencies, and expose a clean API without fighting `import` order.
|
|
7
|
+
|
|
8
|
+
## Highlights
|
|
9
|
+
|
|
10
|
+
- Drop-in `lazy("package.module")` helper that mirrors a regular module object once touched.
|
|
11
|
+
- `LazyLoader.to()` and `LazyLoader.to_many()` for lazily binding one or more attributes without importing the whole module up front.
|
|
12
|
+
- `LazyAttr.value`/`LazyAttr.unwrap()` for grabbing the underlying attribute (dicts, constants, classes) without changing call semantics.
|
|
13
|
+
- Thread-safe module caching that updates `sys.modules` and the caller's globals exactly once.
|
|
14
|
+
- Debug-friendly repr and `dir()` support so interactive sessions stay intuitive even before loading.
|
|
15
|
+
- Batteries-included CLI with version info, environment diagnostics, and release bump helpers.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Requires Python 3.13 or newer.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install lazy-bear
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
With [`uv`](https://docs.astral.sh/uv/):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv tool install lazy-bear
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from lazy_bear import lazy
|
|
35
|
+
|
|
36
|
+
json = lazy("json") # nothing imported yet
|
|
37
|
+
|
|
38
|
+
payload = {"hello": "lazy bear"}
|
|
39
|
+
print(json.dumps(payload, sort_keys=True))
|
|
40
|
+
# Module is imported the first time .dumps is accessed.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Under the hood `lazy()` returns a `LazyLoader` (a `types.ModuleType` subclass). Once you touch an attribute it:
|
|
44
|
+
|
|
45
|
+
1. Imports the target module.
|
|
46
|
+
2. Registers it in both `sys.modules` and the globals of the caller.
|
|
47
|
+
3. Proxies the module so future access is indistinguishable from a normal import.
|
|
48
|
+
|
|
49
|
+
## Lazily Accessing Attributes
|
|
50
|
+
|
|
51
|
+
Sometimes you only need a specific callable or attribute, not the whole module namespace:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from lazy_bear import LazyAttr, lazy
|
|
55
|
+
|
|
56
|
+
console = lazy("rich.console")
|
|
57
|
+
Console: LazyAttr = console.to("Console")
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
rich_console = Console(width=100) # `rich.console` is imported here
|
|
61
|
+
rich_console.print("[bold green]Hello from Lazy Bear![/]")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Need multiple attributes or callables? Use `to_many`:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
math = lazy("math")
|
|
68
|
+
sqrt_attr, pow_attr = math.to_many("sqrt", "pow")
|
|
69
|
+
|
|
70
|
+
print(sqrt_attr(25)) # LazyAttr stays callable
|
|
71
|
+
pow_func = pow_attr.value # or pow_attr.unwrap()
|
|
72
|
+
print(pow_func(2, 3))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`LazyAttr` proxies most dunder methods (`__call__`, `__iter__`, `__getitem__`, etc.), so lazily loaded attributes behave just like the originals.
|
|
76
|
+
|
|
77
|
+
When you need the underlying object—say a dictionary of settings—forgo calling and reach for `.value` (or its alias `.unwrap()`):
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
env = lazy("os").to("environ").value # returns the real os._Environ mapping
|
|
81
|
+
letters = lazy("string").to("ascii_letters").unwrap() # works for constants too
|
|
82
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[coverage:run]
|
|
2
|
+
branch = true
|
|
3
|
+
parallel = true
|
|
4
|
+
source =
|
|
5
|
+
src/
|
|
6
|
+
tests/
|
|
7
|
+
|
|
8
|
+
[coverage:paths]
|
|
9
|
+
equivalent =
|
|
10
|
+
src/
|
|
11
|
+
.venv/lib/*/site-packages/
|
|
12
|
+
.venvs/*/lib/*/site-packages/
|
|
13
|
+
|
|
14
|
+
[coverage:report]
|
|
15
|
+
precision = 2
|
|
16
|
+
omit =
|
|
17
|
+
src/*/__init__.py
|
|
18
|
+
src/*/__main__.py
|
|
19
|
+
tests/__init__.py
|
|
20
|
+
exclude_lines =
|
|
21
|
+
pragma: no cover
|
|
22
|
+
if TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
[coverage:json]
|
|
25
|
+
output = htmlcov/coverage.json
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[pytest]
|
|
2
|
+
python_files =
|
|
3
|
+
test_*.py
|
|
4
|
+
asyncio_mode = auto
|
|
5
|
+
pythonpath = src
|
|
6
|
+
addopts =
|
|
7
|
+
-v
|
|
8
|
+
-s
|
|
9
|
+
--cov
|
|
10
|
+
--cov-config config/coverage.ini
|
|
11
|
+
--cov-report=html:reports/coverage_html
|
|
12
|
+
testpaths =
|
|
13
|
+
tests
|
|
14
|
+
filterwarnings =
|
|
15
|
+
ignore::DeprecationWarning
|
|
16
|
+
ignore::PendingDeprecationWarning
|
|
17
|
+
ignore::FutureWarning
|
|
18
|
+
ignore::ImportWarning
|
|
19
|
+
ignore::ResourceWarning
|
|
20
|
+
ignore::UserWarning
|