cached-hub 0.1.0__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.
@@ -0,0 +1,50 @@
1
+ # Build and publish the package to PyPI when a GitHub release is created.
2
+ # Uses PyPI trusted publishing (OIDC): no API token needed.
3
+
4
+ name: Upload Python Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ deploy:
12
+ permissions:
13
+ contents: write # Required to update release notes
14
+ id-token: write # Required for PyPI trusted publishing
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0 # Needed for hatch-vcs to determine version
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.x"
26
+
27
+ - name: Install hatch
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install hatch
31
+
32
+ - name: Generate changelog for this release
33
+ uses: orhun/git-cliff-action@v4
34
+ with:
35
+ config: cliff.toml
36
+ args: --latest --strip header
37
+ env:
38
+ OUTPUT: release-notes.md
39
+
40
+ - name: Update release notes
41
+ uses: softprops/action-gh-release@v2
42
+ with:
43
+ body_path: release-notes.md
44
+
45
+ - name: Build python package
46
+ run: |
47
+ hatch build
48
+
49
+ - name: Publish package to PyPI
50
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,47 @@
1
+ name: Python package
2
+
3
+ concurrency:
4
+ group: ${{ github.workflow }}-${{ github.ref }}
5
+ cancel-in-progress: true
6
+
7
+ on:
8
+ push:
9
+ branches: [main]
10
+ pull_request:
11
+ branches: [main]
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0 # Needed for hatch-vcs to determine version
24
+
25
+ - name: Set up Python ${{ matrix.python-version }}
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v5
32
+ with:
33
+ enable-cache: true
34
+
35
+ - name: Install dependencies
36
+ run: |
37
+ uv sync --extra dev
38
+
39
+ - name: Lint with ruff
40
+ run: |
41
+ uv run ruff check cached_hub tests
42
+ uv run ruff format --check cached_hub tests
43
+
44
+ - name: Test with pytest
45
+ timeout-minutes: 10
46
+ run: |
47
+ uv run pytest
@@ -0,0 +1,16 @@
1
+ dist/
2
+ build/
3
+ *.pyc
4
+ __pycache__/
5
+ *.egg-info/
6
+ .venv/
7
+ .uv/
8
+ uv.lock
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .vscode/
13
+ **/.DS_Store
14
+
15
+ # Generated by hatch-vcs
16
+ cached_hub/_version.py
@@ -0,0 +1,36 @@
1
+ default_install_hook_types:
2
+ - pre-commit
3
+ - commit-msg
4
+
5
+ repos:
6
+ - hooks:
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: end-of-file-fixer
10
+ - id: trailing-whitespace
11
+ repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v6.0.0
13
+ - hooks:
14
+ - id: python-check-blanket-noqa
15
+ - id: python-no-eval
16
+ repo: https://github.com/pre-commit/pygrep-hooks
17
+ rev: v1.10.0
18
+ - hooks:
19
+ - id: no-fixme-todo
20
+ name: No FIXME or TODO markers
21
+ entry: '(FIXME:|TODO:)'
22
+ language: pygrep
23
+ types: [python]
24
+ repo: local
25
+ - hooks:
26
+ - id: ruff
27
+ args: [--fix]
28
+ - id: ruff-format
29
+ repo: https://github.com/astral-sh/ruff-pre-commit
30
+ rev: v0.14.10
31
+ - hooks:
32
+ - id: conventional-pre-commit
33
+ stages:
34
+ - commit-msg
35
+ repo: https://github.com/compilerla/conventional-pre-commit
36
+ rev: v4.3.0
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benjamin Piwowarski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.5
2
+ Name: cached-hub
3
+ Version: 0.1.0
4
+ Summary: Load HuggingFace models/datasets (and other course resources) from a shared local cache, falling back to the Hub; declare and pre-download them for classrooms
5
+ Project-URL: Homepage, https://github.com/bpiwowar/cached-hub
6
+ Project-URL: Repository, https://github.com/bpiwowar/cached-hub
7
+ Author-email: Benjamin Piwowarski <benjamin@piwowarski.fr>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: cache,datasets,huggingface,teaching,transformers
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Education
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Requires-Python: >=3.10
24
+ Provides-Extra: datamaestro
25
+ Requires-Dist: datamaestro>=1.5; extra == 'datamaestro'
26
+ Provides-Extra: dev
27
+ Requires-Dist: pre-commit>=3.5; extra == 'dev'
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: ruff>=0.8; extra == 'dev'
30
+ Provides-Extra: hf
31
+ Requires-Dist: datasets>=2.7; extra == 'hf'
32
+ Requires-Dist: transformers>=4.30; extra == 'hf'
33
+ Provides-Extra: pyterrier
34
+ Requires-Dist: python-terrier>=0.10; extra == 'pyterrier'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # cached-hub
38
+
39
+ **Load HuggingFace models and datasets from a shared local cache, fall back to
40
+ the Hub, and pre-populate that cache from a declarative list of what a course
41
+ needs.**
42
+
43
+ ## Why
44
+
45
+ In a classroom, every student pulling `gpt2`, `SmolLM2` and `imdb` from the Hub
46
+ at the same minute is slow, fragile, and sometimes impossible (no home directory
47
+ quota, shaky proxy, offline lab). The usual answer is a shared read-only
48
+ directory pre-filled by the instructor. `cached-hub` makes that directory a
49
+ first-class thing:
50
+
51
+ - notebook code calls `load_hf_model("gpt2")` and gets the cached copy when it
52
+ exists, the Hub otherwise, with a one-line note saying which;
53
+ - the instructor declares the resources once, and `cached-hub download` fills
54
+ the cache (each shared resource once, optional ones on demand);
55
+ - an *enforce* mode turns any cache miss into an error, so you can check that
56
+ every notebook runs fully from the cache before the session.
57
+
58
+ The package has **no required dependency**: `transformers`, `datasets`,
59
+ `pyterrier` and `datamaestro` are imported only by the functions that use them.
60
+ It was extracted from the Sorbonne Master MIND `master-mind` tool so that a
61
+ course only needs this package (plus
62
+ [jupytext-notebook-helper](https://github.com/bpiwowar/jupytext-notebook-helper)
63
+ for building notebooks).
64
+
65
+ ## Install
66
+
67
+ ```sh
68
+ pip install cached-hub # loaders only (bring your own transformers/datasets)
69
+ pip install "cached-hub[hf]" # + transformers, datasets
70
+ ```
71
+
72
+ ## Supported libraries
73
+
74
+ | Library | cached-hub function | Equivalent to | Cached under `$CACHED_HUB_PATH` | Pre-download with |
75
+ |------------------------|------------------------------------|-------------------------------------------------|----------------------------------------|--------------------------------|
76
+ | transformers | `load_hf_model(id, cls=AutoModel, **kw)` | `cls.from_pretrained(id, **kw)` | `huggingface/models/<id>/` | `make_hf_model_resource` |
77
+ | transformers | `load_hf_tokenizer(id, cls=AutoTokenizer, **kw)`| `cls.from_pretrained(id, **kw)` | `huggingface/tokenizers/<id>/` | `make_hf_tokenizer_resource` |
78
+ | transformers | `load_hf_processor(id, cls=AutoProcessor, **kw)`| `cls.from_pretrained(id, **kw)` | `huggingface/processors/<id>/` | `make_hf_processor_resource` |
79
+ | datasets | `load_hf_dataset(id, name=None, split=None, **kw)` | `datasets.load_dataset(id, name, split=split, **kw)` | `huggingface/datasets/<id>[-<name>]/<split>/` | `make_hf_dataset_resource` |
80
+ | transformers | `HFModel(id, tok_cls, model_cls, **kw)` | lazy `.tokenizer` / `.model` via the two loaders above | as above | model + tokenizer resources |
81
+ | pyterrier / ir-datasets| (use `pt.get_dataset` directly) | `pt.get_dataset(id)` | pyterrier's own home (`PYTERRIER_HOME`, `IR_DATASETS_HOME`) | `make_pyterrier_dataset_resource` |
82
+ | datamaestro | (use `datamaestro.prepare_dataset` directly) | `prepare_dataset(id)` | datamaestro's own store (`DATAMAESTRO_DIR`) | `make_datamaestro_resource` |
83
+
84
+ The loaders differ from their equivalents in one way only: with
85
+ `CACHED_HUB_PATH` set, they first look for the resource in the cache layout
86
+ above, log where it came from, and on a miss forward to the equivalent call
87
+ with `cache_dir=$CACHED_HUB_PATH/huggingface` added (unless you passed one), or
88
+ raise `CacheMissError` in enforce mode. PyTerrier and datamaestro manage their
89
+ own caches, so there is no loader for them: `cached-hub` only declares them as
90
+ resources so that `cached-hub download` fetches everything a course needs in
91
+ one go, and you keep calling those libraries as usual.
92
+
93
+ ## In notebooks
94
+
95
+ ```python
96
+ from cached_hub import load_hf_model, load_hf_tokenizer, load_hf_dataset, HFModel
97
+ from transformers import AutoModelForCausalLM
98
+
99
+ tokenizer = load_hf_tokenizer("HuggingFaceTB/SmolLM2-1.7B-Instruct")
100
+ model = load_hf_model("HuggingFaceTB/SmolLM2-1.7B-Instruct", AutoModelForCausalLM, device_map="auto")
101
+ train = load_hf_dataset("imdb", split="train")
102
+ sst2 = load_hf_dataset("glue", name="sst2") # DatasetDict of the cached splits
103
+
104
+ hf = HFModel("gpt2") # lazy: nothing is loaded yet
105
+ hf.tokenizer, hf.model # AutoTokenizer / AutoModel, loaded on first access
106
+ ```
107
+
108
+ Each loader checks the local cache first, then falls back to the Hub with a
109
+ warning. Extra keyword arguments go to `from_pretrained` / `load_dataset`.
110
+
111
+ ## Configuration
112
+
113
+ | Variable | Effect |
114
+ |----------------------|------------------------------------------------------------------------|
115
+ | `CACHED_HUB_PATH` | Root of the shared cache. Unset: the library does nothing (see below). |
116
+ | `CACHED_HUB_ENFORCE` | If set (any value), a cache miss raises `CacheMissError` instead of falling back. |
117
+
118
+ Layout under the root (`org/name` becomes `org-name`):
119
+
120
+ ```
121
+ $CACHED_HUB_PATH/huggingface/models/<id>/ model.save_pretrained()
122
+ $CACHED_HUB_PATH/huggingface/tokenizers/<id>/
123
+ $CACHED_HUB_PATH/huggingface/processors/<id>/
124
+ $CACHED_HUB_PATH/huggingface/datasets/<id>[-<name>]/<split>/ dataset.save_to_disk()
125
+ $CACHED_HUB_PATH/huggingface/ HF cache_dir used for fallbacks
126
+ ```
127
+
128
+ A directory is used only when it contains the marker `.downloaded.ok`, written
129
+ after a successful download, so a half-copied model is never picked up.
130
+
131
+ **Without `CACHED_HUB_PATH`, `cached-hub` adds no caching of its own.**
132
+ `load_hf_model("gpt2", cls, **kw)` is then exactly `cls.from_pretrained("gpt2", **kw)`,
133
+ and `load_hf_dataset(...)` exactly `datasets.load_dataset(...)`: the usual
134
+ HuggingFace cache (`~/.cache/huggingface`, `HF_HOME`) applies as it always does,
135
+ and `cached-hub download` merely warms it. Notebooks can therefore import from
136
+ `cached_hub` unconditionally and run unchanged on a laptop or on Colab; only the
137
+ classroom machines set the variable.
138
+
139
+ ## Declaring and downloading resources
140
+
141
+ A course lists what it needs as `{section: [resources]}`:
142
+
143
+ ```python
144
+ # mycourse/resources.py
145
+ from cached_hub import (
146
+ make_hf_model_resource, make_hf_tokenizer_resource, make_hf_processor_resource,
147
+ make_hf_dataset_resource, make_pyterrier_dataset_resource, make_datamaestro_resource,
148
+ )
149
+
150
+ RESOURCES = {
151
+ "practical1": [
152
+ make_hf_model_resource("gpt2", model_class="GPT2LMHeadModel"),
153
+ make_hf_tokenizer_resource("gpt2", tokenizer_class="GPT2Tokenizer"),
154
+ make_hf_dataset_resource("imdb", ["train", "test"]),
155
+ ],
156
+ "practical2": [
157
+ make_hf_model_resource("Qwen/Qwen2.5-7B-Instruct", model_class="AutoModelForCausalLM", optional=True),
158
+ make_pyterrier_dataset_resource("irds:lotte/technology/dev/search", "LoTTE technology"),
159
+ ],
160
+ }
161
+ ```
162
+
163
+ then, on the machine that hosts the cache:
164
+
165
+ ```sh
166
+ export CACHED_HUB_PATH=/shared/cache
167
+ cached-hub info
168
+ cached-hub list --from mycourse.resources:RESOURCES
169
+ cached-hub download --from mycourse.resources:RESOURCES # everything but optional
170
+ cached-hub download --from mycourse.resources:RESOURCES --section practical2 --optional
171
+ cached-hub download --from mycourse.resources:RESOURCES --key gpt2
172
+ ```
173
+
174
+ `--from MODULE:ATTR` imports `MODULE` and reads `ATTR` from it: a
175
+ `{section: [resources]}` mapping, or a zero-argument callable returning one
176
+ (dotted attributes such as `plugin.Course.resources` are followed). No source
177
+ scanning is involved; `RESOURCES` above is only a naming convention. The option
178
+ can be repeated. Resources are identified by `(type, key)`, so a model shared
179
+ by several practicals is downloaded once. `HF_HUB_OFFLINE` is lifted for the
180
+ duration of a download.
181
+
182
+ The same helpers are available from Python (`download_resources`,
183
+ `select_resources`, `format_resources`, `merge_resources`), and any object with
184
+ `resource_type`, `key`, `description`, `optional` and `download()` is a valid
185
+ resource (`FunctionalResource` wraps a plain function).
186
+
187
+ ## Checking a cache before class
188
+
189
+ ```sh
190
+ CACHED_HUB_PATH=/shared/cache CACHED_HUB_ENFORCE=1 python practical1.py
191
+ ```
192
+
193
+ fails at the first resource that would have gone to the Hub.
@@ -0,0 +1,157 @@
1
+ # cached-hub
2
+
3
+ **Load HuggingFace models and datasets from a shared local cache, fall back to
4
+ the Hub, and pre-populate that cache from a declarative list of what a course
5
+ needs.**
6
+
7
+ ## Why
8
+
9
+ In a classroom, every student pulling `gpt2`, `SmolLM2` and `imdb` from the Hub
10
+ at the same minute is slow, fragile, and sometimes impossible (no home directory
11
+ quota, shaky proxy, offline lab). The usual answer is a shared read-only
12
+ directory pre-filled by the instructor. `cached-hub` makes that directory a
13
+ first-class thing:
14
+
15
+ - notebook code calls `load_hf_model("gpt2")` and gets the cached copy when it
16
+ exists, the Hub otherwise, with a one-line note saying which;
17
+ - the instructor declares the resources once, and `cached-hub download` fills
18
+ the cache (each shared resource once, optional ones on demand);
19
+ - an *enforce* mode turns any cache miss into an error, so you can check that
20
+ every notebook runs fully from the cache before the session.
21
+
22
+ The package has **no required dependency**: `transformers`, `datasets`,
23
+ `pyterrier` and `datamaestro` are imported only by the functions that use them.
24
+ It was extracted from the Sorbonne Master MIND `master-mind` tool so that a
25
+ course only needs this package (plus
26
+ [jupytext-notebook-helper](https://github.com/bpiwowar/jupytext-notebook-helper)
27
+ for building notebooks).
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ pip install cached-hub # loaders only (bring your own transformers/datasets)
33
+ pip install "cached-hub[hf]" # + transformers, datasets
34
+ ```
35
+
36
+ ## Supported libraries
37
+
38
+ | Library | cached-hub function | Equivalent to | Cached under `$CACHED_HUB_PATH` | Pre-download with |
39
+ |------------------------|------------------------------------|-------------------------------------------------|----------------------------------------|--------------------------------|
40
+ | transformers | `load_hf_model(id, cls=AutoModel, **kw)` | `cls.from_pretrained(id, **kw)` | `huggingface/models/<id>/` | `make_hf_model_resource` |
41
+ | transformers | `load_hf_tokenizer(id, cls=AutoTokenizer, **kw)`| `cls.from_pretrained(id, **kw)` | `huggingface/tokenizers/<id>/` | `make_hf_tokenizer_resource` |
42
+ | transformers | `load_hf_processor(id, cls=AutoProcessor, **kw)`| `cls.from_pretrained(id, **kw)` | `huggingface/processors/<id>/` | `make_hf_processor_resource` |
43
+ | datasets | `load_hf_dataset(id, name=None, split=None, **kw)` | `datasets.load_dataset(id, name, split=split, **kw)` | `huggingface/datasets/<id>[-<name>]/<split>/` | `make_hf_dataset_resource` |
44
+ | transformers | `HFModel(id, tok_cls, model_cls, **kw)` | lazy `.tokenizer` / `.model` via the two loaders above | as above | model + tokenizer resources |
45
+ | pyterrier / ir-datasets| (use `pt.get_dataset` directly) | `pt.get_dataset(id)` | pyterrier's own home (`PYTERRIER_HOME`, `IR_DATASETS_HOME`) | `make_pyterrier_dataset_resource` |
46
+ | datamaestro | (use `datamaestro.prepare_dataset` directly) | `prepare_dataset(id)` | datamaestro's own store (`DATAMAESTRO_DIR`) | `make_datamaestro_resource` |
47
+
48
+ The loaders differ from their equivalents in one way only: with
49
+ `CACHED_HUB_PATH` set, they first look for the resource in the cache layout
50
+ above, log where it came from, and on a miss forward to the equivalent call
51
+ with `cache_dir=$CACHED_HUB_PATH/huggingface` added (unless you passed one), or
52
+ raise `CacheMissError` in enforce mode. PyTerrier and datamaestro manage their
53
+ own caches, so there is no loader for them: `cached-hub` only declares them as
54
+ resources so that `cached-hub download` fetches everything a course needs in
55
+ one go, and you keep calling those libraries as usual.
56
+
57
+ ## In notebooks
58
+
59
+ ```python
60
+ from cached_hub import load_hf_model, load_hf_tokenizer, load_hf_dataset, HFModel
61
+ from transformers import AutoModelForCausalLM
62
+
63
+ tokenizer = load_hf_tokenizer("HuggingFaceTB/SmolLM2-1.7B-Instruct")
64
+ model = load_hf_model("HuggingFaceTB/SmolLM2-1.7B-Instruct", AutoModelForCausalLM, device_map="auto")
65
+ train = load_hf_dataset("imdb", split="train")
66
+ sst2 = load_hf_dataset("glue", name="sst2") # DatasetDict of the cached splits
67
+
68
+ hf = HFModel("gpt2") # lazy: nothing is loaded yet
69
+ hf.tokenizer, hf.model # AutoTokenizer / AutoModel, loaded on first access
70
+ ```
71
+
72
+ Each loader checks the local cache first, then falls back to the Hub with a
73
+ warning. Extra keyword arguments go to `from_pretrained` / `load_dataset`.
74
+
75
+ ## Configuration
76
+
77
+ | Variable | Effect |
78
+ |----------------------|------------------------------------------------------------------------|
79
+ | `CACHED_HUB_PATH` | Root of the shared cache. Unset: the library does nothing (see below). |
80
+ | `CACHED_HUB_ENFORCE` | If set (any value), a cache miss raises `CacheMissError` instead of falling back. |
81
+
82
+ Layout under the root (`org/name` becomes `org-name`):
83
+
84
+ ```
85
+ $CACHED_HUB_PATH/huggingface/models/<id>/ model.save_pretrained()
86
+ $CACHED_HUB_PATH/huggingface/tokenizers/<id>/
87
+ $CACHED_HUB_PATH/huggingface/processors/<id>/
88
+ $CACHED_HUB_PATH/huggingface/datasets/<id>[-<name>]/<split>/ dataset.save_to_disk()
89
+ $CACHED_HUB_PATH/huggingface/ HF cache_dir used for fallbacks
90
+ ```
91
+
92
+ A directory is used only when it contains the marker `.downloaded.ok`, written
93
+ after a successful download, so a half-copied model is never picked up.
94
+
95
+ **Without `CACHED_HUB_PATH`, `cached-hub` adds no caching of its own.**
96
+ `load_hf_model("gpt2", cls, **kw)` is then exactly `cls.from_pretrained("gpt2", **kw)`,
97
+ and `load_hf_dataset(...)` exactly `datasets.load_dataset(...)`: the usual
98
+ HuggingFace cache (`~/.cache/huggingface`, `HF_HOME`) applies as it always does,
99
+ and `cached-hub download` merely warms it. Notebooks can therefore import from
100
+ `cached_hub` unconditionally and run unchanged on a laptop or on Colab; only the
101
+ classroom machines set the variable.
102
+
103
+ ## Declaring and downloading resources
104
+
105
+ A course lists what it needs as `{section: [resources]}`:
106
+
107
+ ```python
108
+ # mycourse/resources.py
109
+ from cached_hub import (
110
+ make_hf_model_resource, make_hf_tokenizer_resource, make_hf_processor_resource,
111
+ make_hf_dataset_resource, make_pyterrier_dataset_resource, make_datamaestro_resource,
112
+ )
113
+
114
+ RESOURCES = {
115
+ "practical1": [
116
+ make_hf_model_resource("gpt2", model_class="GPT2LMHeadModel"),
117
+ make_hf_tokenizer_resource("gpt2", tokenizer_class="GPT2Tokenizer"),
118
+ make_hf_dataset_resource("imdb", ["train", "test"]),
119
+ ],
120
+ "practical2": [
121
+ make_hf_model_resource("Qwen/Qwen2.5-7B-Instruct", model_class="AutoModelForCausalLM", optional=True),
122
+ make_pyterrier_dataset_resource("irds:lotte/technology/dev/search", "LoTTE technology"),
123
+ ],
124
+ }
125
+ ```
126
+
127
+ then, on the machine that hosts the cache:
128
+
129
+ ```sh
130
+ export CACHED_HUB_PATH=/shared/cache
131
+ cached-hub info
132
+ cached-hub list --from mycourse.resources:RESOURCES
133
+ cached-hub download --from mycourse.resources:RESOURCES # everything but optional
134
+ cached-hub download --from mycourse.resources:RESOURCES --section practical2 --optional
135
+ cached-hub download --from mycourse.resources:RESOURCES --key gpt2
136
+ ```
137
+
138
+ `--from MODULE:ATTR` imports `MODULE` and reads `ATTR` from it: a
139
+ `{section: [resources]}` mapping, or a zero-argument callable returning one
140
+ (dotted attributes such as `plugin.Course.resources` are followed). No source
141
+ scanning is involved; `RESOURCES` above is only a naming convention. The option
142
+ can be repeated. Resources are identified by `(type, key)`, so a model shared
143
+ by several practicals is downloaded once. `HF_HUB_OFFLINE` is lifted for the
144
+ duration of a download.
145
+
146
+ The same helpers are available from Python (`download_resources`,
147
+ `select_resources`, `format_resources`, `merge_resources`), and any object with
148
+ `resource_type`, `key`, `description`, `optional` and `download()` is a valid
149
+ resource (`FunctionalResource` wraps a plain function).
150
+
151
+ ## Checking a cache before class
152
+
153
+ ```sh
154
+ CACHED_HUB_PATH=/shared/cache CACHED_HUB_ENFORCE=1 python practical1.py
155
+ ```
156
+
157
+ fails at the first resource that would have gone to the Hub.
@@ -0,0 +1,82 @@
1
+ """cached-hub: HuggingFace (and other) resources from a shared local cache.
2
+
3
+ Notebook side::
4
+
5
+ from cached_hub import load_hf_model, load_hf_tokenizer, load_hf_dataset, HFModel
6
+
7
+ Course side (declare what to pre-download)::
8
+
9
+ from cached_hub import make_hf_model_resource, make_hf_dataset_resource
10
+ RESOURCES = {"practical1": [make_hf_model_resource("gpt2"), ...]}
11
+
12
+ then ``cached-hub download --from mycourse.resources:RESOURCES``.
13
+
14
+ Configure the cache root with ``CACHED_HUB_PATH`` (see :mod:`cached_hub.config`).
15
+ """
16
+
17
+ from .config import (
18
+ ENV_ENFORCE,
19
+ ENV_PATH,
20
+ CacheMissError,
21
+ get_cache_path,
22
+ is_enforce_mode,
23
+ )
24
+ from .datamaestro import make_datamaestro_resource
25
+ from .hf import (
26
+ HFModel,
27
+ load_hf_dataset,
28
+ load_hf_model,
29
+ load_hf_processor,
30
+ load_hf_tokenizer,
31
+ make_hf_dataset_resource,
32
+ make_hf_model_resource,
33
+ make_hf_processor_resource,
34
+ make_hf_tokenizer_resource,
35
+ )
36
+ from .pyterrier import make_pyterrier_dataset_resource
37
+ from .resources import (
38
+ DownloadableResource,
39
+ FunctionalResource,
40
+ Resource,
41
+ Resources,
42
+ download_resources,
43
+ format_resources,
44
+ merge_resources,
45
+ select_resources,
46
+ )
47
+
48
+ try:
49
+ from ._version import __version__
50
+ except ImportError: # pragma: no cover - not installed from a build
51
+ __version__ = "0.0.0+unknown"
52
+
53
+ __all__ = [
54
+ "__version__",
55
+ # config
56
+ "ENV_PATH",
57
+ "ENV_ENFORCE",
58
+ "CacheMissError",
59
+ "get_cache_path",
60
+ "is_enforce_mode",
61
+ # loading
62
+ "HFModel",
63
+ "load_hf_dataset",
64
+ "load_hf_model",
65
+ "load_hf_processor",
66
+ "load_hf_tokenizer",
67
+ # resources
68
+ "Resource",
69
+ "DownloadableResource",
70
+ "FunctionalResource",
71
+ "Resources",
72
+ "download_resources",
73
+ "format_resources",
74
+ "merge_resources",
75
+ "select_resources",
76
+ "make_hf_dataset_resource",
77
+ "make_hf_model_resource",
78
+ "make_hf_processor_resource",
79
+ "make_hf_tokenizer_resource",
80
+ "make_pyterrier_dataset_resource",
81
+ "make_datamaestro_resource",
82
+ ]
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None