docs: add runtime docs to CLI reference (#3445)

* bumps to version 1.20.0

* update the hub reference docs, add CI check

* use dependency specifier in hub for plugin version check

* minimum dlt runtime cli check

* rollaback to old fsspec min version

* fixes test_hub ci workflow

* fixes flaky test

* bumps hub extra

* updates cli docs linting

* fixes docs lock

---------

Co-authored-by: Marcin Rudolf <rudolfix@rudolfix.org>
Co-authored-by: ivasio <ivan@dlthub.com>
This commit is contained in:
ivasio
2025-12-09 17:30:53 +01:00
committed by GitHub
parent d0dc21bd45
commit 99207237fe
13 changed files with 1280 additions and 78 deletions

View File

@@ -94,6 +94,17 @@ jobs:
run: pytest tests/hub run: pytest tests/hub
# if: matrix.os != 'macos-latest' # if: matrix.os != 'macos-latest'
- name: Test runtime client
run: |
mkdir .dlt && touch .dlt/.workspace
dlt runtime --help
# DISABLED: because docs rendering happens in non-deterministic order (of plugin discovery)
# must be fixed
# - name: Check that dlthub cli docs are up to date
# run: cd docs/tools/dlthub_cli && make check-cli-docs
# if: ${{ matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest' }}
matrix_job_required_check: matrix_job_required_check:
name: hub | dlthub features tests name: hub | dlthub features tests
needs: run_hub_features needs: run_hub_features

View File

@@ -34,7 +34,7 @@ COPY dist/dlt-${IMAGE_VERSION}.tar.gz .
RUN mkdir -p /app RUN mkdir -p /app
WORKDIR /app WORKDIR /app
RUN uv venv && uv pip install /tmp/pydlt/dlt-${IMAGE_VERSION}.tar.gz --resolution lowest-direct && uv pip install typing-extensions==4.8.0 RUN uv venv && uv pip install /tmp/pydlt/dlt-${IMAGE_VERSION}.tar.gz --resolution lowest-direct && uv pip install typing-extensions==4.8.0
RUN rm -r /tmp/pydlt # RUN rm -r /tmp/pydlt
# make sure dlt can be actually imported # make sure dlt can be actually imported
RUN uv run python -c 'import dlt;import pendulum;' RUN uv run python -c 'import dlt;import pendulum;'
@@ -50,7 +50,12 @@ RUN uv run dlt pipeline fruit_pipeline info
# enable workspace # enable workspace
RUN mkdir -p .dlt && touch .dlt/.workspace RUN mkdir -p .dlt && touch .dlt/.workspace
# RUN dlt pipeline fruit_pipeline info RUN uv run dlt workspace -v info
RUN uv run dlt workspace info
RUN uv run python minimal_pipeline.py RUN uv run python minimal_pipeline.py
RUN uv run dlt pipeline fruit_pipeline info RUN uv run dlt pipeline fruit_pipeline info
# install hub
RUN uv pip install /tmp/pydlt/dlt-${IMAGE_VERSION}.tar.gz[hub] --resolution lowest-direct && uv pip install typing-extensions==4.8.0
RUN uv run python minimal_pipeline.py
RUN uv run dlt --non-interactive license issue dlthub.transformation
RUN uv run dlt runtime --help

View File

@@ -6,7 +6,7 @@ from types import ModuleType
from typing import Any, Dict, Iterator, List, Optional from typing import Any, Dict, Iterator, List, Optional
from urllib.parse import urlencode from urllib.parse import urlencode
from packaging.version import Version from packaging.specifiers import SpecifierSet
from dlt.common import known_env from dlt.common import known_env
from dlt.common.configuration.container import Container from dlt.common.configuration.container import Container
@@ -246,9 +246,14 @@ def ensure_plugin_version_match(
plugin_version: str, plugin_version: str,
plugin_module_name: str, plugin_module_name: str,
dlt_extra: str, dlt_extra: str,
dlt_version_specifier: Optional[SpecifierSet] = None,
) -> None: ) -> None:
"""Ensures that installed dlt version matches plugin version. Plugins are tightly bound to `dlt` """Ensures that installed plugin version matches dlt requirements. Plugins are tightly bound
and released together. Both major and minor version must match. For alpha plugins version may be 0. to `dlt` and released together.
If `dlt_version_specifier` is provided, it is used to check if the plugin version satisfies
the specifier. Otherwise, the specifier is read from dlt's package metadata (Requires-Dist).
If specifier cannot be determined, the function returns without checking.
Args: Args:
pkg_name: Name of the plugin package (e.g., "dlthub") pkg_name: Name of the plugin package (e.g., "dlthub")
@@ -256,30 +261,37 @@ def ensure_plugin_version_match(
plugin_version: The installed plugin version string plugin_version: The installed plugin version string
plugin_module_name: The module name for MissingDependencyException (e.g., "dlthub") plugin_module_name: The module name for MissingDependencyException (e.g., "dlthub")
dlt_extra: The dlt extra to install the plugin (e.g., "hub") dlt_extra: The dlt extra to install the plugin (e.g., "hub")
dlt_version_specifier: Optional version specifier for the plugin. If not provided,
reads from dlt's package metadata.
Raises: Raises:
MissingDependencyException: If version mismatch is detected MissingDependencyException: If version mismatch is detected
""" """
installed = Version(plugin_version) # Get specifier from dlt's package metadata if not provided
dlt_installed = Version(dlt_version) if dlt_version_specifier is None:
from dlt.version import get_dependency_requirement
# currently packages must match on minor version req = get_dependency_requirement(pkg_name)
if installed.minor != dlt_installed.minor or ( if req is not None:
installed.major != dlt_installed.major and installed.major != 0 dlt_version_specifier = req.specifier
):
# If specifier still not available, exit without checking
if dlt_version_specifier is None or len(dlt_version_specifier) == 0:
return
# Use specifier.contains() for proper version check (allowing prereleases)
if not dlt_version_specifier.contains(plugin_version, prereleases=True):
from dlt.common.exceptions import MissingDependencyException from dlt.common.exceptions import MissingDependencyException
custom_msg = ( custom_msg = (
f"`{pkg_name}` is a `dlt` plugin and must be installed together with `dlt` with a " f"`{pkg_name}` is a `dlt` plugin and must satisfy version requirement "
f"matching version. `dlt` {dlt_installed.major}.{dlt_installed.minor}.x requires " f"`{dlt_version_specifier}` but you have {plugin_version}. "
f"`{pkg_name}` 0.{dlt_installed.minor}.x but you have " f"Please install the right version of {pkg_name} with:\n\n"
f"{plugin_version}. Please install the right version of {pkg_name} with:\n\n"
f'pip install "dlt[{dlt_extra}]=={dlt_version}"\n\n' f'pip install "dlt[{dlt_extra}]=={dlt_version}"\n\n'
"or if you are upgrading the plugin:\n\n" "or if you are upgrading the plugin:\n\n"
f'pip install "dlt[{dlt_extra}]=={dlt_version}" -U {pkg_name}' f'pip install "dlt[{dlt_extra}]=={dlt_version}" -U {pkg_name}'
) )
missing_dep_ex = MissingDependencyException(plugin_module_name, []) missing_dep_ex = MissingDependencyException(plugin_module_name, [])
# ImportError uses `msg` attribute for __str__, not just args
missing_dep_ex.args = (custom_msg,) missing_dep_ex.args = (custom_msg,)
missing_dep_ex.msg = custom_msg missing_dep_ex.msg = custom_msg
raise missing_dep_ex raise missing_dep_ex

View File

@@ -65,10 +65,16 @@ MTIME_DISPATCH = {
"az": lambda f: ensure_pendulum_datetime_utc(f["last_modified"]), "az": lambda f: ensure_pendulum_datetime_utc(f["last_modified"]),
"gcs": lambda f: ensure_pendulum_datetime_utc(f["updated"]), "gcs": lambda f: ensure_pendulum_datetime_utc(f["updated"]),
"https": lambda f: cast( "https": lambda f: cast(
pendulum.DateTime, pendulum.parse(f["Last-Modified"], exact=True, strict=False) pendulum.DateTime,
pendulum.parse(
f.get("Last-Modified", pendulum.now().isoformat()), exact=True, strict=False
),
), ),
"http": lambda f: cast( "http": lambda f: cast(
pendulum.DateTime, pendulum.parse(f["Last-Modified"], exact=True, strict=False) pendulum.DateTime,
pendulum.parse(
f.get("Last-Modified", pendulum.now().isoformat()), exact=True, strict=False
),
), ),
"file": lambda f: ensure_pendulum_datetime_utc(f["mtime"]), "file": lambda f: ensure_pendulum_datetime_utc(f["mtime"]),
"memory": lambda f: ensure_pendulum_datetime_utc(f["created"]), "memory": lambda f: ensure_pendulum_datetime_utc(f["created"]),

View File

@@ -1,6 +1,8 @@
from importlib.metadata import version as pkg_version, distribution as pkg_distribution from importlib.metadata import version as pkg_version, distribution as pkg_distribution
from typing import Optional
from urllib.request import url2pathname from urllib.request import url2pathname
from urllib.parse import urlparse from urllib.parse import urlparse
from packaging.requirements import Requirement
DLT_IMPORT_NAME = "dlt" DLT_IMPORT_NAME = "dlt"
PKG_NAME = DLT_PKG_NAME = "dlt" PKG_NAME = DLT_PKG_NAME = "dlt"
@@ -30,3 +32,19 @@ def get_installed_requirement_string(
else: else:
package_requirement = f"{package}{ver_selector}{pkg_version(package)}" package_requirement = f"{package}{ver_selector}{pkg_version(package)}"
return package_requirement return package_requirement
def get_dependency_requirement(
dependency_name: str, package: str = DLT_PKG_NAME
) -> Optional[Requirement]:
"""Find a specific dependency requirement from package metadata"""
dist = pkg_distribution(package)
if dist.requires is None:
return None
for req_str in dist.requires:
req = Requirement(req_str)
if req.name == dependency_name:
return req
return None

View File

@@ -1,11 +1,14 @@
.PHONY: install-dlthub, update-cli-docs, check-cli-docs .PHONY: update-cli-docs, check-cli-docs, dev
# this must be run from `dlthub_cli` to see workspace commands # this must be run from `dlthub_cli` to see workspace commands
# it will use dlthub and dlt-runtime versions from dlt/docs/pyproject.toml to generate docs
install-dlthub: dev:
uv pip install dlthub uv sync
update-cli-docs: install-dlthub update-cli-docs: dev
uv run dlt --debug render-docs ../../website/docs/hub/command-line-interface.md --commands license workspace profile # generate as there's no license
RUNTIME__LICENSE="" uv run dlt --debug render-docs ../../website/docs/hub/command-line-interface.md --commands license workspace profile runtime
check-cli-docs: install-dlthub check-cli-docs: dev
uv run dlt --debug render-docs ../../website/docs/hub/command-line-interface.md --compare --commands license workspace profile # generate as there's no license
RUNTIME__LICENSE="" uv run dlt --debug render-docs ../../website/docs/hub/command-line-interface.md --compare --commands license workspace profile runtime

18
docs/uv.lock generated
View File

@@ -834,7 +834,7 @@ wheels = [
[[package]] [[package]]
name = "dlt" name = "dlt"
version = "1.20.0a1" version = "1.20.0"
source = { editable = "../" } source = { editable = "../" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
@@ -928,13 +928,13 @@ requires-dist = [
{ name = "db-dtypes", marker = "extra == 'bigquery'", specifier = ">=1.2.0" }, { name = "db-dtypes", marker = "extra == 'bigquery'", specifier = ">=1.2.0" },
{ name = "db-dtypes", marker = "extra == 'gcp'", specifier = ">=1.2.0" }, { name = "db-dtypes", marker = "extra == 'gcp'", specifier = ">=1.2.0" },
{ name = "deltalake", marker = "extra == 'deltalake'", specifier = ">=0.25.1" }, { name = "deltalake", marker = "extra == 'deltalake'", specifier = ">=0.25.1" },
{ name = "dlt-runtime", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.19.0a0,<0.21" }, { name = "dlt-runtime", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.20.0a0,<0.21" },
{ name = "dlthub", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.20.0a1,<0.21" }, { name = "dlthub", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.20.0a1,<0.21" },
{ name = "duckdb", marker = "extra == 'duckdb'", specifier = ">=0.9" }, { name = "duckdb", marker = "extra == 'duckdb'", specifier = ">=0.9" },
{ name = "duckdb", marker = "extra == 'ducklake'", specifier = ">=1.2.0" }, { name = "duckdb", marker = "extra == 'ducklake'", specifier = ">=1.2.0" },
{ name = "duckdb", marker = "extra == 'motherduck'", specifier = ">=0.9" }, { name = "duckdb", marker = "extra == 'motherduck'", specifier = ">=0.9" },
{ name = "duckdb", marker = "extra == 'workspace'", specifier = ">=0.9" }, { name = "duckdb", marker = "extra == 'workspace'", specifier = ">=0.9" },
{ name = "fsspec", specifier = ">=2025.9.0" }, { name = "fsspec", specifier = ">=2022.4.0" },
{ name = "gcsfs", marker = "extra == 'bigquery'", specifier = ">=2022.4.0" }, { name = "gcsfs", marker = "extra == 'bigquery'", specifier = ">=2022.4.0" },
{ name = "gcsfs", marker = "extra == 'clickhouse'", specifier = ">=2022.4.0" }, { name = "gcsfs", marker = "extra == 'clickhouse'", specifier = ">=2022.4.0" },
{ name = "gcsfs", marker = "extra == 'gcp'", specifier = ">=2022.4.0" }, { name = "gcsfs", marker = "extra == 'gcp'", specifier = ">=2022.4.0" },
@@ -1189,7 +1189,7 @@ requires-dist = [
[[package]] [[package]]
name = "dlt-runtime" name = "dlt-runtime"
version = "0.20.0a0" version = "0.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "attrs" }, { name = "attrs" },
@@ -1199,22 +1199,22 @@ dependencies = [
{ name = "python-jose" }, { name = "python-jose" },
{ name = "tabulate" }, { name = "tabulate" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/1d/f5/d5c74ba2560507493b9d5d0c98a8482e15f036f338bb2832730065679e22/dlt_runtime-0.20.0a0.tar.gz", hash = "sha256:3e9d5df91f03152c251e5f874e5a13ac1bd66d5fbac0357c11996bf4e8279c8c", size = 48300, upload-time = "2025-12-04T15:51:51.192Z" } sdist = { url = "https://files.pythonhosted.org/packages/d0/86/d7f057d8bdf2f3ada28bf1277b7f24a7abbb221d72788bd682176126a75c/dlt_runtime-0.20.0.tar.gz", hash = "sha256:753c7522bc01c92a453459640e482f87b647b14cc5734d754133a91968acc79f", size = 49532, upload-time = "2025-12-09T14:32:33.708Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/21/1f/ebcfea0c69a697d64f836140bfdb0f73d0be2880547dc00ff267eaad7569/dlt_runtime-0.20.0a0-py3-none-any.whl", hash = "sha256:d6498d4078980c833ea9c5cbfc8b7146488beddb77146ae6d3a2e7d7345dfb5a", size = 118233, upload-time = "2025-12-04T15:51:49.67Z" }, { url = "https://files.pythonhosted.org/packages/4c/b0/02a6c846d89e3c27a592a929d95526036be1d607e48fca214dcbf3b7bf58/dlt_runtime-0.20.0-py3-none-any.whl", hash = "sha256:0969165672b2b3938a618ddd263e0cf8ec356d289253f58134e325e222753056", size = 119573, upload-time = "2025-12-09T14:32:32.119Z" },
] ]
[[package]] [[package]]
name = "dlthub" name = "dlthub"
version = "0.20.0a1" version = "0.20.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "python-jose" }, { name = "python-jose" },
{ name = "ruamel-yaml" }, { name = "ruamel-yaml" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/13/20/eb0246c88dd80102a4b02ad807b3459c31855ac0c983f69a2b8fe027a88c/dlthub-0.20.0a1.tar.gz", hash = "sha256:bf8e90bd22bad85ed67d497f97d763678e74a41c70bb3c17e3ae2da7897a0bde", size = 158441, upload-time = "2025-12-07T11:26:45.312Z" } sdist = { url = "https://files.pythonhosted.org/packages/77/1b/2c079f22243462e914026172094411ed7ef1fc96c8089e0ca66d1a14038a/dlthub-0.20.1.tar.gz", hash = "sha256:7b3a188abc28601fd4bdf8f17e7925ef729d4f91fb67a6b4eb5c5dc5a04ac3a2", size = 158432, upload-time = "2025-12-09T15:18:10.813Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/40/7b/307126b81ecefbf2dd7569e2da81b2a7b9124dbdcfa6039f3e350bd097c3/dlthub-0.20.0a1-py3-none-any.whl", hash = "sha256:de1a20567da74943931e8fd3551babd345c9f7dcb1f3782d200b308a79c660ce", size = 209794, upload-time = "2025-12-07T11:26:43.808Z" }, { url = "https://files.pythonhosted.org/packages/21/94/b2a87853102c6aa08606b2708d8f678b1e39855e8227fe111e37c32631b6/dlthub-0.20.1-py3-none-any.whl", hash = "sha256:c4d4e0c4515cd68f316ccd02c9ecc007332c861ae6f92a488f7e961935e7f1a0", size = 209767, upload-time = "2025-12-09T15:18:09.067Z" },
] ]
[[package]] [[package]]

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "dlt" name = "dlt"
version = "1.20.0a1" version = "1.20.0"
description = "dlt is an open-source python-first scalable data loading library that does not require any backend to run." description = "dlt is an open-source python-first scalable data loading library that does not require any backend to run."
authors = [{ name = "dltHub Inc.", email = "services@dlthub.com" }] authors = [{ name = "dltHub Inc.", email = "services@dlthub.com" }]
requires-python = ">=3.9.2, <3.15" requires-python = ">=3.9.2, <3.15"
@@ -52,7 +52,7 @@ dependencies = [
"orjson>=3.11.0 ; python_version > '3.13'", "orjson>=3.11.0 ; python_version > '3.13'",
"tenacity>=8.0.2", "tenacity>=8.0.2",
"jsonpath-ng>=1.5.3", "jsonpath-ng>=1.5.3",
"fsspec>=2025.9.0", "fsspec>=2022.4.0",
"packaging>=21.1", "packaging>=21.1",
"pluggy>=1.3.0", "pluggy>=1.3.0",
"win-precise-time>=1.4.2 ; os_name == 'nt' and python_version < '3.13'", "win-precise-time>=1.4.2 ; os_name == 'nt' and python_version < '3.13'",
@@ -189,7 +189,7 @@ workspace = [
] ]
hub = [ hub = [
"dlthub>=0.20.0a1,<0.21 ; python_version >= '3.10'", "dlthub>=0.20.0a1,<0.21 ; python_version >= '3.10'",
"dlt-runtime>=0.19.0a0,<0.21 ; python_version >= '3.10'", "dlt-runtime>=0.20.0a0,<0.21 ; python_version >= '3.10'",
] ]
dbml = [ dbml = [

View File

@@ -174,31 +174,73 @@ def test_context_with_xdg_dir(mocker) -> None:
def test_ensure_plugin_version_match_same_versions() -> None: def test_ensure_plugin_version_match_same_versions() -> None:
"""test that matching versions pass without error.""" """test that matching versions pass without error."""
from packaging.specifiers import SpecifierSet
# Use explicit specifier to test specific version matching scenarios
# PEP 440 ordering: .devN < .aN < .bN < .rcN < final < .postN
# So we use .dev0 as lower bound to include all pre-releases
specifier_1_19 = SpecifierSet(">=1.19.0.dev0,<1.20.0") # includes all prereleases
specifier_2_5 = SpecifierSet(">=2.5.0.dev0,<2.6.0")
# exact same version # exact same version
ensure_plugin_version_match("dlthub", "1.19.0", "1.19.0", "dlthub", "hub") ensure_plugin_version_match(
ensure_plugin_version_match("dlthub", "1.19.5", "1.19.2", "dlthub", "hub") "fake-plugin", "1.19.0", "1.19.0", "fake-plugin", "hub", specifier_1_19
)
ensure_plugin_version_match(
"fake-plugin", "1.19.5", "1.19.2", "fake-plugin", "hub", specifier_1_19
)
# different patch versions are ok # different patch versions are ok
ensure_plugin_version_match("dlthub", "2.5.0", "2.5.10", "dlthub", "hub") ensure_plugin_version_match(
# alpha specifiers (e.g. 1.19.0a1) "fake-plugin", "2.5.0", "2.5.10", "fake-plugin", "hub", specifier_2_5
ensure_plugin_version_match("dlthub", "1.19.0a1", "1.19.0a2", "dlthub", "hub") )
ensure_plugin_version_match("dlthub", "1.19.0a1", "1.19.0", "dlthub", "hub") # alpha specifiers (e.g. 1.19.0a1) - these are LESS than 1.19.0
# dev specifiers (e.g. 1.19.0.dev1) ensure_plugin_version_match(
ensure_plugin_version_match("dlthub", "1.19.0.dev1", "1.19.0.dev2", "dlthub", "hub") "fake-plugin", "1.19.0a1", "1.19.0a2", "fake-plugin", "hub", specifier_1_19
ensure_plugin_version_match("dlthub", "1.19.0.dev1", "1.19.0", "dlthub", "hub") )
ensure_plugin_version_match(
"fake-plugin", "1.19.0a1", "1.19.0", "fake-plugin", "hub", specifier_1_19
)
# dev specifiers (e.g. 1.19.0.dev1) - these are LESS than 1.19.0a0
ensure_plugin_version_match(
"fake-plugin", "1.19.0.dev1", "1.19.0.dev2", "fake-plugin", "hub", specifier_1_19
)
ensure_plugin_version_match(
"fake-plugin", "1.19.0.dev1", "1.19.0", "fake-plugin", "hub", specifier_1_19
)
# post release specifiers # post release specifiers
ensure_plugin_version_match("dlthub", "1.19.0.post1", "1.19.0.post2", "dlthub", "hub") ensure_plugin_version_match(
ensure_plugin_version_match("dlthub", "1.19.0.post1", "1.19.0", "dlthub", "hub") "fake-plugin", "1.19.0.post1", "1.19.0.post2", "fake-plugin", "hub", specifier_1_19
)
ensure_plugin_version_match(
"fake-plugin", "1.19.0.post1", "1.19.0", "fake-plugin", "hub", specifier_1_19
)
def test_ensure_plugin_version_match_alpha_plugin() -> None: def test_ensure_plugin_version_match_alpha_plugin() -> None:
"""test that alpha plugins (major=0) match any dlt major version with same minor.""" """test that alpha plugins (major=0) match specifier."""
# alpha plugin (0.x.y) should match dlt 1.x.y with same minor from packaging.specifiers import SpecifierSet
ensure_plugin_version_match("dlthub", "1.19.0", "0.19.0", "dlthub", "hub")
ensure_plugin_version_match("dlthub", "1.19.5", "0.19.2", "dlthub", "hub") # specifier for 0.19.x versions (including all pre-releases)
ensure_plugin_version_match("dlthub", "2.19.0", "0.19.0", "dlthub", "hub") # PEP 440 ordering: .devN < .aN < .bN < .rcN < final < .postN
specifier_0_19 = SpecifierSet(">=0.19.0.dev0,<0.20.0")
# alpha plugin (0.x.y) should match specifier
ensure_plugin_version_match(
"fake-plugin", "1.19.0", "0.19.0", "fake-plugin", "hub", specifier_0_19
)
ensure_plugin_version_match(
"fake-plugin", "1.19.5", "0.19.2", "fake-plugin", "hub", specifier_0_19
)
ensure_plugin_version_match(
"fake-plugin", "2.19.0", "0.19.0", "fake-plugin", "hub", specifier_0_19
)
# alpha plugin with alpha/dev specifiers # alpha plugin with alpha/dev specifiers
ensure_plugin_version_match("dlthub", "1.19.0a1", "0.19.0a2", "dlthub", "hub") ensure_plugin_version_match(
ensure_plugin_version_match("dlthub", "1.19.0.dev1", "0.19.0.dev2", "dlthub", "hub") "fake-plugin", "1.19.0a1", "0.19.0a2", "fake-plugin", "hub", specifier_0_19
)
ensure_plugin_version_match(
"fake-plugin", "1.19.0.dev1", "0.19.0.dev2", "fake-plugin", "hub", specifier_0_19
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -217,6 +259,13 @@ def test_ensure_plugin_version_match_alpha_plugin() -> None:
) )
def test_ensure_plugin_version_match_mismatch(dlt_version: str, plugin_version: str) -> None: def test_ensure_plugin_version_match_mismatch(dlt_version: str, plugin_version: str) -> None:
"""test that mismatched versions raise MissingDependencyException.""" """test that mismatched versions raise MissingDependencyException."""
from packaging.specifiers import SpecifierSet
# Use explicit specifier that requires 1.19.x versions
specifier = SpecifierSet(">=1.19.0,<1.20.0")
with pytest.raises(MissingDependencyException) as exc_info: with pytest.raises(MissingDependencyException) as exc_info:
ensure_plugin_version_match("dlthub", dlt_version, plugin_version, "dlthub", "hub") ensure_plugin_version_match(
assert "dlthub" in str(exc_info.value) "fake-plugin", dlt_version, plugin_version, "fake-plugin", "hub", specifier
)
assert "fake-plugin" in str(exc_info.value)

View File

@@ -1,8 +1,9 @@
import os import os
import pytest import pytest
from importlib.metadata import PackageNotFoundError from importlib.metadata import PackageNotFoundError
from packaging.requirements import Requirement
from dlt.version import get_installed_requirement_string from dlt.version import get_installed_requirement_string, get_dependency_requirement
def test_installed_requirement_string() -> None: def test_installed_requirement_string() -> None:
@@ -15,3 +16,24 @@ def test_installed_requirement_string() -> None:
# this is not installed # this is not installed
with pytest.raises(PackageNotFoundError): with pytest.raises(PackageNotFoundError):
get_installed_requirement_string("requests-X") get_installed_requirement_string("requests-X")
def test_get_dependency_requirement() -> None:
# dlt depends on dlthub, so this should return a Requirement
req = get_dependency_requirement("dlthub")
assert req is not None
assert isinstance(req, Requirement)
assert req.name == "dlthub"
# click has a version specifier
assert str(req.specifier) != ""
# dlt depends on fsspec with a version constraint
req = get_dependency_requirement("fsspec")
assert req is not None
assert req.name == "fsspec"
# verify we can check version satisfaction
assert "2022.4.0" in req.specifier
# non-existent dependency returns None
req = get_dependency_requirement("non-existent-package-xyz")
assert req is None

View File

@@ -4463,14 +4463,14 @@ def test_pending_package_exception_warning() -> None:
with pytest.raises(PipelineStepFailed) as pip_ex: with pytest.raises(PipelineStepFailed) as pip_ex:
pipeline.run() pipeline.run()
# none of the jobs passed so we have pending package but not partial # one of the job failed and package is aborted. sometimes the other
# job completed, sometimes is still pending so we disable pending test
assert pip_ex.value.step == "load" assert pip_ex.value.step == "load"
print(str(pip_ex.value)) # assert "Pending packages" not in str(pip_ex.value)
assert "Pending packages" not in str(pip_ex.value)
assert "partially loaded" in str(pip_ex.value) assert "partially loaded" in str(pip_ex.value)
assert pip_ex.value.load_id is not None assert pip_ex.value.load_id is not None
assert pip_ex.value.is_package_partially_loaded is True assert pip_ex.value.is_package_partially_loaded is True
assert pip_ex.value.has_pending_data is False # assert pip_ex.value.has_pending_data is False
def test_cleanup() -> None: def test_cleanup() -> None:

18
uv.lock generated
View File

@@ -2058,7 +2058,7 @@ wheels = [
[[package]] [[package]]
name = "dlt" name = "dlt"
version = "1.20.0a1" version = "1.20.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
@@ -2356,13 +2356,13 @@ requires-dist = [
{ name = "db-dtypes", marker = "extra == 'bigquery'", specifier = ">=1.2.0" }, { name = "db-dtypes", marker = "extra == 'bigquery'", specifier = ">=1.2.0" },
{ name = "db-dtypes", marker = "extra == 'gcp'", specifier = ">=1.2.0" }, { name = "db-dtypes", marker = "extra == 'gcp'", specifier = ">=1.2.0" },
{ name = "deltalake", marker = "extra == 'deltalake'", specifier = ">=0.25.1" }, { name = "deltalake", marker = "extra == 'deltalake'", specifier = ">=0.25.1" },
{ name = "dlt-runtime", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.19.0a0,<0.21" }, { name = "dlt-runtime", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.20.0a0,<0.21" },
{ name = "dlthub", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.20.0a1,<0.21" }, { name = "dlthub", marker = "python_full_version >= '3.10' and extra == 'hub'", specifier = ">=0.20.0a1,<0.21" },
{ name = "duckdb", marker = "extra == 'duckdb'", specifier = ">=0.9" }, { name = "duckdb", marker = "extra == 'duckdb'", specifier = ">=0.9" },
{ name = "duckdb", marker = "extra == 'ducklake'", specifier = ">=1.2.0" }, { name = "duckdb", marker = "extra == 'ducklake'", specifier = ">=1.2.0" },
{ name = "duckdb", marker = "extra == 'motherduck'", specifier = ">=0.9" }, { name = "duckdb", marker = "extra == 'motherduck'", specifier = ">=0.9" },
{ name = "duckdb", marker = "extra == 'workspace'", specifier = ">=0.9" }, { name = "duckdb", marker = "extra == 'workspace'", specifier = ">=0.9" },
{ name = "fsspec", specifier = ">=2025.9.0" }, { name = "fsspec", specifier = ">=2022.4.0" },
{ name = "gcsfs", marker = "extra == 'bigquery'", specifier = ">=2022.4.0" }, { name = "gcsfs", marker = "extra == 'bigquery'", specifier = ">=2022.4.0" },
{ name = "gcsfs", marker = "extra == 'clickhouse'", specifier = ">=2022.4.0" }, { name = "gcsfs", marker = "extra == 'clickhouse'", specifier = ">=2022.4.0" },
{ name = "gcsfs", marker = "extra == 'gcp'", specifier = ">=2022.4.0" }, { name = "gcsfs", marker = "extra == 'gcp'", specifier = ">=2022.4.0" },
@@ -2535,7 +2535,7 @@ streamlit = [{ name = "streamlit", marker = "python_full_version >= '3.9' and py
[[package]] [[package]]
name = "dlt-runtime" name = "dlt-runtime"
version = "0.20.0a0" version = "0.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "attrs", version = "25.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "attrs", version = "25.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
@@ -2545,22 +2545,22 @@ dependencies = [
{ name = "python-jose", marker = "python_full_version >= '3.10'" }, { name = "python-jose", marker = "python_full_version >= '3.10'" },
{ name = "tabulate", marker = "python_full_version >= '3.10'" }, { name = "tabulate", marker = "python_full_version >= '3.10'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/1d/f5/d5c74ba2560507493b9d5d0c98a8482e15f036f338bb2832730065679e22/dlt_runtime-0.20.0a0.tar.gz", hash = "sha256:3e9d5df91f03152c251e5f874e5a13ac1bd66d5fbac0357c11996bf4e8279c8c", size = 48300, upload-time = "2025-12-04T15:51:51.192Z" } sdist = { url = "https://files.pythonhosted.org/packages/d0/86/d7f057d8bdf2f3ada28bf1277b7f24a7abbb221d72788bd682176126a75c/dlt_runtime-0.20.0.tar.gz", hash = "sha256:753c7522bc01c92a453459640e482f87b647b14cc5734d754133a91968acc79f", size = 49532, upload-time = "2025-12-09T14:32:33.708Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/21/1f/ebcfea0c69a697d64f836140bfdb0f73d0be2880547dc00ff267eaad7569/dlt_runtime-0.20.0a0-py3-none-any.whl", hash = "sha256:d6498d4078980c833ea9c5cbfc8b7146488beddb77146ae6d3a2e7d7345dfb5a", size = 118233, upload-time = "2025-12-04T15:51:49.67Z" }, { url = "https://files.pythonhosted.org/packages/4c/b0/02a6c846d89e3c27a592a929d95526036be1d607e48fca214dcbf3b7bf58/dlt_runtime-0.20.0-py3-none-any.whl", hash = "sha256:0969165672b2b3938a618ddd263e0cf8ec356d289253f58134e325e222753056", size = 119573, upload-time = "2025-12-09T14:32:32.119Z" },
] ]
[[package]] [[package]]
name = "dlthub" name = "dlthub"
version = "0.20.0a1" version = "0.20.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "python-jose", marker = "python_full_version >= '3.10'" }, { name = "python-jose", marker = "python_full_version >= '3.10'" },
{ name = "ruamel-yaml", marker = "python_full_version >= '3.10'" }, { name = "ruamel-yaml", marker = "python_full_version >= '3.10'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/13/20/eb0246c88dd80102a4b02ad807b3459c31855ac0c983f69a2b8fe027a88c/dlthub-0.20.0a1.tar.gz", hash = "sha256:bf8e90bd22bad85ed67d497f97d763678e74a41c70bb3c17e3ae2da7897a0bde", size = 158441, upload-time = "2025-12-07T11:26:45.312Z" } sdist = { url = "https://files.pythonhosted.org/packages/77/1b/2c079f22243462e914026172094411ed7ef1fc96c8089e0ca66d1a14038a/dlthub-0.20.1.tar.gz", hash = "sha256:7b3a188abc28601fd4bdf8f17e7925ef729d4f91fb67a6b4eb5c5dc5a04ac3a2", size = 158432, upload-time = "2025-12-09T15:18:10.813Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/40/7b/307126b81ecefbf2dd7569e2da81b2a7b9124dbdcfa6039f3e350bd097c3/dlthub-0.20.0a1-py3-none-any.whl", hash = "sha256:de1a20567da74943931e8fd3551babd345c9f7dcb1f3782d200b308a79c660ce", size = 209794, upload-time = "2025-12-07T11:26:43.808Z" }, { url = "https://files.pythonhosted.org/packages/21/94/b2a87853102c6aa08606b2708d8f678b1e39855e8227fe111e37c32631b6/dlthub-0.20.1-py3-none-any.whl", hash = "sha256:c4d4e0c4515cd68f316ccd02c9ecc007332c861ae6f92a488f7e961935e7f1a0", size = 209767, upload-time = "2025-12-09T15:18:09.067Z" },
] ]
[[package]] [[package]]