Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 99 additions & 27 deletions databusclient/api/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,7 @@ def _download_artifact(
uri: str,
localDir: str,
all_versions: bool = False,
version: str | None = None,
vault_token_file: str = None,
databus_key: str = None,
auth_url: str = None,
Expand All @@ -1203,6 +1204,7 @@ def _download_artifact(
uri: The full databus artifact URI.
localDir: Local directory to download files to. If None, the databus folder structure is created in the current working directory.
all_versions: If True, download all versions of the artifact; otherwise, only download the latest version.
version: Optional version selector ('latest', 'all', or specific version ID/URI).
vault_token_file: Path to Vault refresh token file for protected downloads.
databus_key: Databus API key for protected downloads.
auth_url: Keycloak token endpoint URL.
Expand All @@ -1217,7 +1219,9 @@ def _download_artifact(
the versioned databus-python-client default.
"""
json_str = fetch_databus_jsonld(uri, databus_key=databus_key)
versions = _get_databus_versions_of_artifact(json_str, all_versions=all_versions)
versions = _get_databus_versions_of_artifact(
json_str, all_versions=all_versions, version=version
)
if isinstance(versions, str):
versions = [versions]
for version_uri in versions:
Expand Down Expand Up @@ -1251,17 +1255,21 @@ def _download_artifact(


def _get_databus_versions_of_artifact(
json_str: str, all_versions: bool
json_str: str,
all_versions: bool = False,
version: str | None = None,
) -> str | List[str]:
"""Parse the JSON-LD of a databus artifact to extract URLs of its versions.

Args:
json_str: JSON-LD string of the databus artifact.
all_versions: If True, return all version URLs; otherwise, return only the latest version URL.
version: Optional version selector: 'latest', 'all', or a specific version ID / URI.

Returns:
If all_versions is True: List of all version URLs.
If all_versions is False: URL of the latest version.
If all_versions is True or version is 'all': List of all version URLs.
If version is a specific identifier: URL of the matching version.
If all_versions is False and version is None or 'latest': URL of the latest version.
"""
json_dict = json.loads(json_str)
versions = json_dict.get("databus:hasVersion")
Expand All @@ -1283,6 +1291,29 @@ def _get_databus_versions_of_artifact(

version_urls.sort(reverse=True) # Sort versions in descending order

if version is not None:
v_clean = version.strip()
if v_clean.lower() == "all":
return version_urls
if v_clean.lower() == "latest":
return version_urls if all_versions else version_urls[0]

if "://" in v_clean:
matched = [u for u in version_urls if u.rstrip("/") == v_clean.rstrip("/")]
else:
target_id = v_clean.rstrip("/")
matched = [
u
for u in version_urls
if u.rstrip("/").split("/")[-1] == target_id
]
if not matched:
available = ", ".join(u.rstrip("/").split("/")[-1] for u in version_urls)
raise ValueError(
f"Version '{version}' not found for artifact. Available versions: {available}"
)
return matched[0]

if all_versions:
return version_urls
return version_urls[0]
Expand Down Expand Up @@ -1315,6 +1346,7 @@ def _download_group(
uri: str,
localDir: str,
all_versions: bool = False,
version: str | None = None,
vault_token_file: str = None,
databus_key: str = None,
auth_url: str = None,
Expand All @@ -1334,6 +1366,7 @@ def _download_group(
uri: The full databus group URI.
localDir: Local directory to download files to. If None, the databus folder structure is created in the current working directory.
all_versions: If True, download all versions of each artifact in the group; otherwise, only download the latest version.
version: Optional version selector ('latest', 'all', or specific version ID/URI).
vault_token_file: Path to Vault refresh token file for protected downloads.
databus_key: Databus API key for protected downloads.
auth_url: Keycloak token endpoint URL.
Expand All @@ -1351,23 +1384,38 @@ def _download_group(
artifacts = _get_databus_artifacts_of_group(json_str)
for artifact_uri in artifacts:
print(f"Download artifact: {artifact_uri}")
_download_artifact(
artifact_uri,
localDir,
all_versions=all_versions,
vault_token_file=vault_token_file,
databus_key=databus_key,
auth_url=auth_url,
client_id=client_id,
compression=compression,
convert_format=convert_format,
graph_name=graph_name,
base_uri=base_uri,
graph_mode=graph_mode,
manifest_context=manifest_context,
validate_checksum=validate_checksum,
user_agent=user_agent,
)
try:
_download_artifact(
artifact_uri,
localDir,
all_versions=all_versions,
version=version,
vault_token_file=vault_token_file,
databus_key=databus_key,
auth_url=auth_url,
client_id=client_id,
compression=compression,
convert_format=convert_format,
graph_name=graph_name,
base_uri=base_uri,
graph_mode=graph_mode,
manifest_context=manifest_context,
validate_checksum=validate_checksum,
user_agent=user_agent,
)
except ValueError as err:
# If a specific version was requested and is not found for this artifact, log and continue with others
if (
version is not None
and version.strip().lower() not in ("latest", "all")
and (
"not found for artifact" in str(err)
or "No versions found in artifact JSON-LD" in str(err)
)
):
print(f"Skipping artifact {artifact_uri}: {err}")
continue
raise


def _get_databus_artifacts_of_group(json_str: str) -> List[str]:
Expand Down Expand Up @@ -1421,6 +1469,8 @@ def download(
validate_checksum: bool = False,
manifest_context=None,
user_agent: str | None = None,
version: str | None = None,
target_version: str | None = None,
) -> None:
"""Download datasets from databus.

Expand All @@ -1432,6 +1482,7 @@ def download(
databusURIs: Databus identifiers to specify datasets to download.
token: Path to Vault refresh token file for protected downloads.
databus_key: Databus API key for protected downloads.
all_versions: If True, download all versions of artifacts in group/artifact URIs.
auth_url: Keycloak token endpoint URL. Default is "https://auth.dbpedia.org/realms/dbpedia/protocol/openid-connect/token".
client_id: Client ID for token exchange. Default is "vault-token-exchange".
compression: Target compression format for on-the-fly conversion (supported: bz2, gz, xz).
Expand All @@ -1441,12 +1492,21 @@ def download(
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
validate_checksum: Whether to validate checksums after downloading.
manifest_context: Optional context for recording operation manifest.
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
the versioned databus-python-client default.
version: Target version selector ('latest', 'all', or specific version ID/URI).
target_version: Alias for version.
"""
_validate_graph_mode(graph_mode)
resolved_version = target_version if target_version is not None else version
if resolved_version and resolved_version.strip().lower() == "all":
all_versions = True
elif all_versions and resolved_version is None:
resolved_version = "all"

for databusURI in databusURIs:
host, account, group, artifact, version, file = get_databus_id_parts_from_file_url(databusURI)
host, account, group, artifact, uri_version, file = get_databus_id_parts_from_file_url(databusURI)

# Determine endpoint per-URI if not explicitly provided
uri_endpoint = endpoint
Expand Down Expand Up @@ -1504,7 +1564,7 @@ def download(
expected_checksum=expected,
user_agent=user_agent,
)
elif version is not None:
elif uri_version is not None:
print(f"Downloading version: {databusURI}")
_download_version(
databusURI,
Expand All @@ -1523,13 +1583,19 @@ def download(
user_agent=user_agent,
)
elif artifact is not None:
v_desc = (
resolved_version
if resolved_version and resolved_version.strip().lower() not in ("latest", "all")
else ("all" if all_versions else "latest")
)
print(
f"Downloading {'all' if all_versions else 'latest'} version(s) of artifact: {databusURI}"
f"Downloading {v_desc} version(s) of artifact: {databusURI}"
)
_download_artifact(
databusURI,
localDir,
all_versions=all_versions,
all_versions=bool(all_versions),
version=resolved_version,
vault_token_file=token,
databus_key=databus_key,
auth_url=auth_url,
Expand All @@ -1544,13 +1610,19 @@ def download(
user_agent=user_agent,
)
elif group is not None and group != "collections":
v_desc = (
resolved_version
if resolved_version and resolved_version.strip().lower() not in ("latest", "all")
else ("all" if all_versions else "latest")
)
print(
f"Downloading group and all its artifacts and versions: {databusURI}"
f"Downloading group ({v_desc} versions): {databusURI}"
)
_download_group(
databusURI,
localDir,
all_versions=all_versions,
all_versions=bool(all_versions),
version=resolved_version,
vault_token_file=token,
databus_key=databus_key,
auth_url=auth_url,
Expand Down
15 changes: 15 additions & 0 deletions databusclient/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ def _write_manifest():
is_flag=True,
help="When downloading artifacts, download all versions instead of only the latest",
)
@click.option(
"--version",
"--target-version",
"target_version",
default=None,
help="Target version to download: 'latest' (default), 'all', or a specific version ID (e.g. 2025-10-14).",
)
@click.option(
"--authurl",
default="https://auth.dbpedia.org/realms/dbpedia/protocol/openid-connect/token",
Expand Down Expand Up @@ -332,6 +339,7 @@ def download(
vault_token,
databus_key,
all_versions,
target_version,
authurl,
clientid,
compression,
Expand All @@ -347,6 +355,11 @@ def download(
Download datasets from databus, optionally using vault access if vault options are provided.
Supports on-the-fly compression format conversion using the --compression option.
"""
if all_versions and not target_version:
target_version = "all"
elif target_version and target_version.strip().lower() == "all":
all_versions = True

# Determine auth method for manifest (never store the token itself)
auth_method = None
if vault_token:
Expand All @@ -370,6 +383,7 @@ def download(
"base_uri": base_uri,
"graph_mode": graph_mode,
"all_versions": all_versions,
"version": target_version,
"validate_checksum": validate_checksum,
"user_agent": user_agent,
"authurl": authurl,
Expand All @@ -383,6 +397,7 @@ def download(
token=vault_token,
databus_key=databus_key,
all_versions=all_versions,
version=target_version,
auth_url=authurl,
client_id=clientid,
compression=compression,
Expand Down
17 changes: 17 additions & 0 deletions databusclient/manifest/replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ def _build_download_kwargs(
replay_params: Dict[str, Any],
overrides: Dict[str, Any],
) -> Dict[str, Any]:
"""Build keyword arguments for api_download from manifest replay parameters.

Args:
manifest: Operation manifest dictionary.
replay_params: Parameters recorded during the original operation.
overrides: Execution overrides provided at replay time.

Returns:
Keyword argument dictionary ready for api_download.
"""
databus_uris = replay_params.get("databusURIs")
if not isinstance(databus_uris, list) or not databus_uris:
raise ManifestReplayError(
Expand All @@ -63,13 +73,20 @@ def _build_download_kwargs(

endpoint = overrides.get("endpoint", manifest.get("dbus:endpoint"))

version = replay_params.get("version")
if version is not None and not isinstance(version, str):
raise ManifestReplayError(
"Manifest field dbus:replayParams.version must be a string."
)

return {
"localDir": overrides.get("localDir"),
"endpoint": endpoint,
"databusURIs": databus_uris,
"token": token,
"databus_key": databus_key,
"all_versions": replay_params.get("all_versions"),
"version": version,
"auth_url": replay_params.get(
"authurl",
"https://auth.dbpedia.org/realms/dbpedia/protocol/openid-connect/token",
Expand Down
1 change: 1 addition & 0 deletions databusclient/workflow/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def run(self, step_config: Dict[str, Any], context: StepContext) -> None:
token=resolved.get("vault_token"),
databus_key=resolved.get("databus_key"),
all_versions=resolved.get("all_versions", False),
version=resolved.get("version"),
compression=resolved.get("convert_to") or resolved.get("compression"),
convert_format=resolved.get("format"),
graph_name=resolved.get("graph_name"),
Expand Down
19 changes: 17 additions & 2 deletions doc/cli-usage.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## CLI Usage
## CLI Usage

To get started with the command-line interface (CLI) of the databus-python-client, you can use either the Python installation or the Docker image. The examples below show python installation method.

Expand Down Expand Up @@ -28,8 +28,13 @@ databusclient download $DOWNLOADTARGET
Note: Vault tokens are only required for certain protected Databus hosts (for example: `data.dbpedia.io`, `data.dev.dbpedia.link`). The client now detects those hosts and will fail early with a clear message if a token is required but not provided. Do not pass `--vault-token` for public downloads.
- `--databus-key`
- If the databus is protected and needs API key authentication, you can provide the API key with `--databus-key YOUR_API_KEY`.
- `--version`, `--target-version`
- Specifies which version(s) to download when downloading an artifact or group:
- `--version latest`: downloads only the latest version (default behavior).
- `--version all`: downloads all versions of artifacts (equivalent to `--all-versions`).
- `--version <version-id>`: downloads a specific version if available (e.g. `--version 2022.12.01`).
- `--all-versions`
- When downloading artifacts, downloads all versions instead of only the latest.
- When downloading artifacts, downloads all versions instead of only the latest (equivalent to `--version all`).
- `--compression`
- Enables on-the-fly compression format conversion during download. Supported formats: `bz2`, `gz`, `xz`, `none`. The source compression is auto-detected from the file extension. Use `none` to decompress files without recompressing. Example: `--compression gz` converts all downloaded compressed files to gzip format.
- `--format`
Expand Down Expand Up @@ -65,6 +70,16 @@ databusclient download https://databus.dbpedia.org/dbpedia/mappings/mappingbased
databusclient download https://databus.dbpedia.org/dbpedia/mappings/mappingbased-literals
```

**Download Specific Version of Artifact or Group**: download a specific version using `--version` (or `--target-version`)
```bash
databusclient download --version 2022.12.01 https://databus.dbpedia.org/dbpedia/mappings/mappingbased-literals
```

**Download All Versions of Artifact or Group**: download all historical versions using `--version all` or `--all-versions`
```bash
databusclient download --version all https://databus.dbpedia.org/dbpedia/mappings/mappingbased-literals
```

**Download Group**: download of all files with the latest version of all artifacts of a group
```bash
databusclient download https://databus.dbpedia.org/dbpedia/mappings
Expand Down
Loading