From e83866a161a74608230a763d79f7b4a8372cec98 Mon Sep 17 00:00:00 2001 From: Alex Rubinsteyn Date: Thu, 1 Oct 2026 10:13:40 -0400 Subject: [PATCH] Install tqdm by default and prepare 1.16.1 release --- CHANGELOG.md | 6 ++++++ README.md | 16 +++++++++------- datacache/download.py | 6 +++--- datacache/progress.py | 6 +++--- datacache/version.py | 2 +- docs/api.md | 4 ++-- docs/integration.md | 2 +- docs/progress.md | 9 +++++---- pyproject.toml | 5 +++-- requirements.txt | 1 + tests/test_import_cost.py | 2 +- tests/test_packaging.py | 19 +++++++++++++++++++ tests/test_progress.py | 4 ++-- 13 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 tests/test_packaging.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 818dcd3..3eb2f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.16.1 + +- Always install `tqdm>=4.64` as a required dependency. Progress displays remain + opt-in with `show_progress=True` and tqdm stays lazily imported. Keep the + `progress` extra as a compatibility alias for existing installation commands. + ## 1.16.0 - Add `install_archive` and `inspect_archive` for safely installing a complete diff --git a/README.md b/README.md index f7884fe..58adc24 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,14 @@ Python 3.9 or newer is required. ```sh python -m pip install datacache -# Optional progress bars and HTML-table conversion: -python -m pip install "datacache[progress,html]" +# Optional HTML-table conversion: +python -m pip install "datacache[html]" ``` -Progress is opt-in with `show_progress=True`. Normal use does not import tqdm -or configure your application's logging. +tqdm is installed by default; progress displays are opt-in with +`show_progress=True`. Normal use does not import tqdm or configure your +application's logging. The old `datacache[progress]` install syntax remains +supported as a compatibility alias. The existing pandas dependency range is unchanged; upgrading DataCache does not introduce a pandas 1.5 requirement. CI covers pandas 1.4.4, 1.5.3, and @@ -44,8 +46,8 @@ assert cache.fetch(url, filename="LICENSE") == path Replace the URL and filename with your dataset. Existing files are reused until you explicitly refresh them with `force=True`; DataCache does not check whether -the remote file has changed. Add `show_progress=True` to display a download bar -after installing `datacache[progress]`. +the remote file has changed. Add `show_progress=True` to display a download bar; +no extra installation is needed. ### Find, inspect, or clear your cache @@ -131,7 +133,7 @@ path = fetch_file( destination="references/v1/records.tsv", decompress=True, timeout=30, - show_progress=True, # Requires datacache[progress]. + show_progress=True, ) ``` diff --git a/datacache/download.py b/datacache/download.py index 507a587..6fdc5f7 100644 --- a/datacache/download.py +++ b/datacache/download.py @@ -94,7 +94,7 @@ def _stream_to_file( ``progress_callback(bytes_downloaded, total_bytes)`` after each chunk is written, where `total_bytes` is taken from the server's Content-Length header (or None when the server doesn't report a size). This lets callers - drive e.g. a tqdm progress bar without datacache depending on tqdm. + drive an application's own progress display instead of the built-in bar. Returns the total number of bytes written. """ @@ -586,11 +586,11 @@ def fetch_file( If provided, called as ``progress_callback(bytes_downloaded, total_bytes)`` after each chunk is written, where `total_bytes` is the server-reported size or None if unknown. Lets callers render a progress - bar (e.g. tqdm) without datacache taking on that dependency. + display in their application's own UI instead of the built-in bar. show_progress : bool, optional Show tqdm download, decompression, and hash-verification progress. - Requires datacache[progress]. Defaults to False; cache hits are quiet. + tqdm is installed with datacache. Defaults to False; cache hits are quiet. Can be combined with progress_callback. Each retry starts a fresh bar. destination : str or os.PathLike, optional diff --git a/datacache/progress.py b/datacache/progress.py index f00aef7..9505466 100644 --- a/datacache/progress.py +++ b/datacache/progress.py @@ -1,4 +1,4 @@ -"""Optional progress displays. Importing datacache never imports tqdm.""" +"""Opt-in progress displays using the required, lazily imported tqdm dependency.""" class Progress: @@ -17,8 +17,8 @@ def __enter__(self): from tqdm.auto import tqdm except ImportError as error: raise ImportError( - "Progress displays require tqdm; install 'datacache[progress]' " - "or use show_progress=False") from error + "tqdm is a required datacache dependency; install 'tqdm>=4.64' " + "or reinstall datacache") from error self.bar = tqdm( desc=self.description, total=self.total, unit=self.unit, unit_scale=self.unit == "B", unit_divisor=1024, diff --git a/datacache/version.py b/datacache/version.py index 638c121..d388022 100644 --- a/datacache/version.py +++ b/datacache/version.py @@ -1 +1 @@ -__version__ = "1.16.0" +__version__ = "1.16.1" diff --git a/docs/api.md b/docs/api.md index c02ed42..1ae4052 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,7 +1,7 @@ # Public API reference This reference covers every name exported in `datacache.__all__` and every -public `Cache` method in DataCache 1.16.0. Import these names from `datacache`. +public `Cache` method in DataCache 1.16.1. Import these names from `datacache`. Signatures below show all defaults; arguments after `*` are keyword-only. Method signatures omit `self` and are called on a `Cache` instance. @@ -103,7 +103,7 @@ replacement leaves the previous file intact. | `max_retries` | Non-negative integer number of additional attempts after transient HTTP failures. Default `2` allows three total attempts; `0` disables retries. File and FTP downloads are not retried. | | `retry_backoff` | Initial retry delay in seconds, doubled for subsequent retries. Must be finite and non-negative. | | `retry_max_delay` | Maximum retry delay in seconds, finite and non-negative. A server `Retry-After` exceeding this limit stops retries. | -| `show_progress` | Boolean enabling optional tqdm download, decompression, and hash bars. Requires `datacache[progress]` when a bar is needed. Cache hits are quiet. | +| `show_progress` | Boolean enabling tqdm download, decompression, and hash bars. tqdm is installed with datacache; no extra is needed. Displays are opt-in and cache hits are quiet. | | `resume` | Boolean retaining private partials for raw HTTP transfers. Requires size and a POSIX local filesystem. Without an expected SHA-256, every accepted response must supply a strong ETag. Size-only cache hits check only byte count. See [resumable downloads](downloads.md#resumable-http-downloads). | | `record_provenance` | Boolean; after publishing a download, also write a hidden `..datacache.json` record of the source URL (without user name, password, query string, or fragment; the path is kept as is, so avoid recording URLs with secrets in their path), the fetch time, the size, and the SHA-256 when `expected_sha256` verified it. [inspect_file](#inspect_file) reports these offline. The record has the file's permissions. Default `False`: a caller that downloads to a temporary name and then moves the file would leave the record behind. Cache hits never write one, any new download removes a previous record first, and a failure to write one never fails the download. | | `allow_empty` | Boolean accepting an empty installed file; default `False`. A complete but empty response, such as a withdrawn upstream record, is otherwise never published: HTTP retries it as transient, then raises `FileValidationError`. An empty cached file is likewise an invalid hit. `expected_size=0` also allows an empty file. | diff --git a/docs/integration.md b/docs/integration.md index e708756..060b78c 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -96,7 +96,7 @@ DataCache caches by path or database version, not by tracking remote content changes. Refreshing a URL does not invalidate arbitrary downstream artifacts. Libraries own dataset versions and their dependency relationships. -Progress is optional; install the `progress` extra when enabling tqdm. HTML +tqdm is installed by default; enable displays with `show_progress=True`. HTML table conversion requires the `html` extra. The library does not install logging handlers or change the process umask. Download retry counts and waits are bounded, while `timeout` limits connection/read inactivity per attempt, diff --git a/docs/progress.md b/docs/progress.md index fbd946d..26a84fe 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -2,7 +2,8 @@ See the [API reference](api.md) for signatures, defaults, return values, and errors. -Install the optional dependency with `python -m pip install "datacache[progress]"`. +tqdm is a required dependency, installed by `python -m pip install datacache`. +The old `datacache[progress]` extra remains a supported compatibility alias. Set `show_progress=True` on `fetch_file`, `Cache.fetch`, or the CSV and database helpers. DataCache uses [tqdm.auto](https://tqdm.github.io/docs/shortcuts/), which selects a terminal or notebook display. Terminal bars use stderr; stdout remains @@ -25,9 +26,9 @@ can show a checksum pass over an already cached file. ## Application callbacks The existing `progress_callback(completed, total)` API remains supported and -can be used with or without tqdm. `completed` is cumulative for the current -download attempt; `total` may be `None`. Retries restart from zero, so counts -may decrease. Callbacks cover transfer bytes only, not transformation or +can be used with or without built-in progress bars. `completed` is cumulative +for the current download attempt; `total` may be `None`. Retries restart from +zero, so counts may decrease. Callbacks cover transfer bytes only, not transformation or database work. Empty downloads do not invoke the callback. ```python diff --git a/pyproject.toml b/pyproject.toml index 0fa4f59..b46702b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,9 +20,10 @@ readme = "README.md" dynamic = ["version", "dependencies"] [project.optional-dependencies] -progress = ["tqdm>=4.64"] +# Compatibility alias: tqdm is now installed with the base package. +progress = [] html = ["lxml>=4.9"] -test = ["pytest>=7", "pytest-cov", "ruff", "tqdm>=4.64", "lxml>=4.9"] +test = ["pytest>=7", "pytest-cov", "ruff", "lxml>=4.9"] [tool.setuptools.dynamic] version = {attr = "datacache.version.__version__"} diff --git a/requirements.txt b/requirements.txt index c276f54..f28803a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ appdirs>=1.4.0 requests>=2.5.1 typechecks>=0.0.2 filelock>=3.13 +tqdm>=4.64 diff --git a/tests/test_import_cost.py b/tests/test_import_cost.py index ac5675b..398514e 100644 --- a/tests/test_import_cost.py +++ b/tests/test_import_cost.py @@ -14,7 +14,7 @@ def test_importing_datacache_loads_no_heavy_dependencies(): loaded = subprocess.run( [sys.executable, "-c", "import sys, datacache; " - "print(sorted(m for m in ('pandas', 'numpy', 'requests', 'urllib3') if m in sys.modules))"], + "print(sorted(m for m in ('pandas', 'numpy', 'requests', 'urllib3', 'tqdm') if m in sys.modules))"], capture_output=True, text=True, check=True).stdout.strip() assert loaded == "[]" diff --git a/tests/test_packaging.py b/tests/test_packaging.py new file mode 100644 index 0000000..ef8f575 --- /dev/null +++ b/tests/test_packaging.py @@ -0,0 +1,19 @@ +"""Check the dependencies consumers receive from a normal installation.""" + +from importlib.metadata import metadata, requires + +from packaging.requirements import Requirement + + +def test_tqdm_is_an_unconditional_runtime_dependency(): + dependencies = [Requirement(value) for value in requires("datacache")] + tqdm_dependencies = [item for item in dependencies if item.name == "tqdm"] + assert len(tqdm_dependencies) == 1 + dependency = tqdm_dependencies[0] + assert dependency.marker is None + assert "4.64.0" in dependency.specifier + assert "4.63.0" not in dependency.specifier + + +def test_progress_extra_remains_a_compatible_alias(): + assert "progress" in metadata("datacache").get_all("Provides-Extra", []) diff --git a/tests/test_progress.py b/tests/test_progress.py index 600090e..e17a104 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -143,7 +143,7 @@ def test_database_progress_reports_completed_batches(tmp_path, bars): assert bars[0].closed -def test_tqdm_is_optional_and_missing_extra_has_actionable_error(tmp_path, monkeypatch): +def test_missing_required_tqdm_has_actionable_error(tmp_path, monkeypatch): original_import = builtins.__import__ def no_tqdm(name, *args, **kwargs): @@ -155,7 +155,7 @@ def no_tqdm(name, *args, **kwargs): source = tmp_path / "source" source.write_bytes(b"data") fetch_file(source.as_uri(), destination=tmp_path / "quiet") - with pytest.raises(ImportError, match=r"datacache\[progress\]"): + with pytest.raises(ImportError, match=r"tqdm is a required datacache dependency"): fetch_file(source.as_uri(), destination=tmp_path / "progress", show_progress=True) assert not (tmp_path / "progress").exists() assert not list(tmp_path.glob(".datacache-*"))