Skip to content

Commit 0dc1982

Browse files
committed
feat(download): add default and configurable User-Agent for CLI, API, and workflows
1 parent f9f28f3 commit 0dc1982

8 files changed

Lines changed: 205 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [Unreleased]
6+
7+
### Added
8+
- Workflow download steps support an optional `user_agent` field.
9+
- Download `--user-agent` CLI option and `user_agent` Python API argument to override the header on file HEAD and GET requests, including redirects and authentication retries. Download manifests preserve the override for replay.
10+
11+
### Changed
12+
- File downloads now identify as `databus-python-client/<version> (https://github.com/dbpedia/databus-python-client)` instead of the Requests library default.
13+
514
## [0.15] - 2025-12-31
615

716
### Added

‎databusclient/api/download.py‎

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from tqdm import tqdm
1515
from datetime import datetime, timezone
1616

17+
from databusclient.version import __version__
18+
1719
from databusclient.api.utils import (
1820
fetch_databus_jsonld,
1921
get_databus_id_parts_from_file_url,
@@ -41,6 +43,11 @@
4143
"xz": lzma,
4244
}
4345

46+
DEFAULT_USER_AGENT = (
47+
f"databus-python-client/{__version__} "
48+
"(https://github.com/dbpedia/databus-python-client)"
49+
)
50+
4451
GRAPH_MODES = {"download-url"}
4552

4653

@@ -420,6 +427,7 @@ def _download_file(
420427
validate_checksum: bool = False,
421428
expected_checksum: str | None = None,
422429
manifest_context=None,
430+
user_agent: str | None = None,
423431
) -> None:
424432
"""Download a file from the internet with a progress bar using tqdm.
425433
@@ -437,6 +445,8 @@ def _download_file(
437445
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
438446
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
439447
validate_checksum: Whether to validate checksums after downloading.
448+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
449+
the versioned databus-python-client default.
440450
expected_checksum: The expected checksum of the file.
441451
"""
442452
_validate_graph_mode(graph_mode)
@@ -454,10 +464,10 @@ def _download_file(
454464
os.makedirs(dirpath, exist_ok=True) # Create the necessary directories
455465

456466
# --- 1. Get redirect URL by requesting HEAD ---
457-
headers = {}
467+
headers = {"User-Agent": DEFAULT_USER_AGENT if user_agent is None else user_agent}
458468

459469
# --- 1a. public databus ---
460-
response = requests.head(url, timeout=30, allow_redirects=False)
470+
response = requests.head(url, headers=headers, timeout=30, allow_redirects=False)
461471

462472
# Check for redirect and update URL if necessary
463473
if response.headers.get("Location") and response.status_code in [
@@ -470,7 +480,7 @@ def _download_file(
470480
url = response.headers.get("Location")
471481
print("Redirects url: ", url)
472482
# Re-do HEAD request on redirect URL
473-
response = requests.head(url, timeout=30)
483+
response = requests.head(url, headers=headers, timeout=30)
474484

475485
# Extract hostname from final URL (after redirect) to check if vault token needed.
476486
# This is the actual download location that may require authentication.
@@ -493,7 +503,7 @@ def _download_file(
493503
raise DownloadAuthError(
494504
"Databus API key not given for protected download"
495505
)
496-
headers = {"X-API-KEY": databus_key}
506+
headers["X-API-KEY"] = databus_key
497507
response = requests.head(url, headers=headers, timeout=30)
498508

499509
# --- 2. Try direct GET to redirected URL ---
@@ -866,6 +876,7 @@ def _download_files(
866876
manifest_context=None,
867877
validate_checksum: bool = False,
868878
checksums: dict | None = None,
879+
user_agent: str | None = None,
869880
) -> None:
870881
"""Download multiple files from the databus.
871882
@@ -882,6 +893,8 @@ def _download_files(
882893
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
883894
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
884895
validate_checksum: Whether to validate checksums after downloading.
896+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
897+
the versioned databus-python-client default.
885898
checksums: Dictionary mapping URLs to their expected checksums.
886899
"""
887900
for url in urls:
@@ -903,6 +916,7 @@ def _download_files(
903916
validate_checksum=validate_checksum,
904917
expected_checksum=expected,
905918
manifest_context=manifest_context,
919+
user_agent=user_agent,
906920
)
907921

908922

@@ -1054,6 +1068,7 @@ def _download_collection(
10541068
graph_mode: str = None,
10551069
manifest_context=None,
10561070
validate_checksum: bool = False,
1071+
user_agent: str | None = None,
10571072
) -> None:
10581073
"""Download all files in a databus collection.
10591074
@@ -1071,6 +1086,8 @@ def _download_collection(
10711086
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
10721087
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
10731088
validate_checksum: Whether to validate checksums after downloading.
1089+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
1090+
the versioned databus-python-client default.
10741091
"""
10751092
query = _get_sparql_query_of_collection(uri, databus_key=databus_key)
10761093
file_urls = _get_file_download_urls_from_sparql_query(
@@ -1097,6 +1114,7 @@ def _download_collection(
10971114
manifest_context=manifest_context,
10981115
validate_checksum=validate_checksum,
10991116
checksums=checksums if checksums else None,
1117+
user_agent=user_agent,
11001118
)
11011119

11021120

@@ -1114,6 +1132,7 @@ def _download_version(
11141132
graph_mode: str = None,
11151133
manifest_context=None,
11161134
validate_checksum: bool = False,
1135+
user_agent: str | None = None,
11171136
) -> None:
11181137
"""Download all files in a databus artifact version.
11191138
@@ -1130,6 +1149,8 @@ def _download_version(
11301149
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
11311150
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
11321151
validate_checksum: Whether to validate checksums after downloading.
1152+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
1153+
the versioned databus-python-client default.
11331154
"""
11341155
json_str = fetch_databus_jsonld(uri, databus_key=databus_key)
11351156
file_urls = _get_file_download_urls_from_artifact_jsonld(json_str)
@@ -1155,6 +1176,7 @@ def _download_version(
11551176
manifest_context=manifest_context,
11561177
validate_checksum=validate_checksum,
11571178
checksums=checksums,
1179+
user_agent=user_agent,
11581180
)
11591181

11601182

@@ -1173,6 +1195,7 @@ def _download_artifact(
11731195
graph_mode: str = None,
11741196
manifest_context=None,
11751197
validate_checksum: bool = False,
1198+
user_agent: str | None = None,
11761199
) -> None:
11771200
"""Download files in a databus artifact.
11781201
@@ -1190,6 +1213,8 @@ def _download_artifact(
11901213
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
11911214
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
11921215
validate_checksum: Whether to validate checksums after downloading.
1216+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
1217+
the versioned databus-python-client default.
11931218
"""
11941219
json_str = fetch_databus_jsonld(uri, databus_key=databus_key)
11951220
versions = _get_databus_versions_of_artifact(json_str, all_versions=all_versions)
@@ -1221,6 +1246,7 @@ def _download_artifact(
12211246
manifest_context=manifest_context,
12221247
validate_checksum=validate_checksum,
12231248
checksums=checksums,
1249+
user_agent=user_agent,
12241250
)
12251251

12261252

@@ -1300,6 +1326,7 @@ def _download_group(
13001326
graph_mode: str = None,
13011327
manifest_context=None,
13021328
validate_checksum: bool = False,
1329+
user_agent: str | None = None,
13031330
) -> None:
13041331
"""Download files in a databus group.
13051332
@@ -1317,6 +1344,8 @@ def _download_group(
13171344
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
13181345
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
13191346
validate_checksum: Whether to validate checksums after downloading.
1347+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
1348+
the versioned databus-python-client default.
13201349
"""
13211350
json_str = fetch_databus_jsonld(uri, databus_key=databus_key)
13221351
artifacts = _get_databus_artifacts_of_group(json_str)
@@ -1337,6 +1366,7 @@ def _download_group(
13371366
graph_mode=graph_mode,
13381367
manifest_context=manifest_context,
13391368
validate_checksum=validate_checksum,
1369+
user_agent=user_agent,
13401370
)
13411371

13421372

@@ -1390,6 +1420,7 @@ def download(
13901420
graph_mode=None,
13911421
validate_checksum: bool = False,
13921422
manifest_context=None,
1423+
user_agent: str | None = None,
13931424
) -> None:
13941425
"""Download datasets from databus.
13951426
@@ -1410,6 +1441,8 @@ def download(
14101441
base_uri: Base URI for CSV -> Triple conversion (Layer 3).
14111442
graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
14121443
validate_checksum: Whether to validate checksums after downloading.
1444+
user_agent: HTTP User-Agent for file HEAD and GET requests. None uses
1445+
the versioned databus-python-client default.
14131446
"""
14141447
_validate_graph_mode(graph_mode)
14151448
for databusURI in databusURIs:
@@ -1442,6 +1475,7 @@ def download(
14421475
graph_mode=graph_mode,
14431476
manifest_context=manifest_context,
14441477
validate_checksum=validate_checksum,
1478+
user_agent=user_agent,
14451479
)
14461480
elif file is not None:
14471481
print(f"Downloading file: {databusURI}")
@@ -1468,6 +1502,7 @@ def download(
14681502
manifest_context=manifest_context,
14691503
validate_checksum=validate_checksum,
14701504
expected_checksum=expected,
1505+
user_agent=user_agent,
14711506
)
14721507
elif version is not None:
14731508
print(f"Downloading version: {databusURI}")
@@ -1485,6 +1520,7 @@ def download(
14851520
graph_mode=graph_mode,
14861521
manifest_context=manifest_context,
14871522
validate_checksum=validate_checksum,
1523+
user_agent=user_agent,
14881524
)
14891525
elif artifact is not None:
14901526
print(
@@ -1505,6 +1541,7 @@ def download(
15051541
graph_mode=graph_mode,
15061542
manifest_context=manifest_context,
15071543
validate_checksum=validate_checksum,
1544+
user_agent=user_agent,
15081545
)
15091546
elif group is not None and group != "collections":
15101547
print(
@@ -1525,6 +1562,7 @@ def download(
15251562
graph_mode=graph_mode,
15261563
manifest_context=manifest_context,
15271564
validate_checksum=validate_checksum,
1565+
user_agent=user_agent,
15281566
)
15291567
elif account is not None:
15301568
print("accountId not supported yet") # TODO
@@ -1568,4 +1606,5 @@ def download(
15681606
manifest_context=manifest_context,
15691607
validate_checksum=validate_checksum,
15701608
checksums=checksums if checksums else None,
1609+
user_agent=user_agent,
15711610
)

‎databusclient/cli.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ def _write_manifest():
321321
@click.option(
322322
"--validate-checksum", is_flag=True, help="Validate checksums of downloaded files"
323323
)
324+
@click.option(
325+
"--user-agent",
326+
help="Override the HTTP User-Agent header for file download requests.",
327+
)
324328
def download(
325329
databusuris: List[str],
326330
localdir,
@@ -337,6 +341,7 @@ def download(
337341
graph_mode,
338342
validate_checksum,
339343
manifest_path,
344+
user_agent,
340345
):
341346
"""
342347
Download datasets from databus, optionally using vault access if vault options are provided.
@@ -366,6 +371,7 @@ def download(
366371
"graph_mode": graph_mode,
367372
"all_versions": all_versions,
368373
"validate_checksum": validate_checksum,
374+
"user_agent": user_agent,
369375
"authurl": authurl,
370376
"clientid": clientid,
371377
})
@@ -385,6 +391,7 @@ def download(
385391
base_uri=base_uri,
386392
graph_mode=graph_mode,
387393
validate_checksum=validate_checksum,
394+
user_agent=user_agent,
388395
manifest_context=manifest_context,
389396
)
390397
except DownloadAuthError as e:

‎databusclient/manifest/replay.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def _build_download_kwargs(
8080
"graph_name": replay_params.get("graph_name"),
8181
"base_uri": replay_params.get("base_uri"),
8282
"graph_mode": replay_params.get("graph_mode"),
83+
"user_agent": replay_params.get("user_agent"),
8384
"validate_checksum": bool(replay_params.get("validate_checksum", False)),
8485
"manifest_context": None,
8586
}

‎databusclient/workflow/steps.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def run(self, step_config: Dict[str, Any], context: StepContext) -> None:
8181
graph_name=resolved.get("graph_name"),
8282
base_uri=resolved.get("base_uri"),
8383
graph_mode=resolved.get("graph_mode"),
84+
user_agent=resolved.get("user_agent"),
8485
validate_checksum=resolved.get("validate_checksum", False),
8586
manifest_context=capture_context,
8687
)

‎doc/cli-usage.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ databusclient download $DOWNLOADTARGET
4040
- Required when converting CSV/TSV to RDF triples. Used as the base for constructing subject URIs from CSV row identifiers. Example: `--format ntriples --base-uri https://example.org/data/`.
4141
- `--validate-checksum`
4242
- Validates the checksums of downloaded files against the checksums provided by the Databus. If a checksum does not match, an error is raised and the file is deleted.
43+
- `--user-agent`
44+
- Overrides the HTTP User-Agent header for file download requests. Default: `databus-python-client/<version> (https://github.com/dbpedia/databus-python-client)`. Example: `--user-agent "DatabusDownloadBot/1.0 (mailto:you@example.org)"`.
4345

4446
**Help and further information on download command:**
4547
```bash
@@ -406,6 +408,7 @@ steps:
406408
uri: https://databus.dbpedia.org/dbpedia/mappings/mappingbased-literals/2022.12.01/mappingbased-literals_lang=az.ttl.bz2
407409
localdir: ./data
408410
graph_mode: download-url
411+
user_agent: "DatabusDownloadBot/1.0 (mailto:you@example.org)"
409412

410413
- name: publish_dataset
411414
command: deploy
@@ -419,6 +422,8 @@ steps:
419422
on_error: fail
420423
```
421424
425+
Download steps accept an optional `user_agent` field, equivalent to `download --user-agent`. If omitted, the client default is used.
426+
422427
**Environment variables:** any value written as `${VARIABLE_NAME}` is resolved from the environment when the workflow starts. If the variable is not set, the workflow fails immediately with a clear error before any step runs — credentials should always be passed this way, never written directly in the file.
423428

424429
**Step chaining:** a step's outputs can be referenced by later steps using `${steps.step_name.output_key}`:

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "databusclient"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
description = "A simple client for submitting, downloading, and deleting data on the DBpedia Databus"
55
authors = ["DBpedia Association"]
66
license = "Apache-2.0 License"

0 commit comments

Comments
 (0)