Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2fb3f06
Fix --version-id -> --versionid in CLI
Tahoora-Tabassum Dec 23, 2025
d4bb454
Fix --version-id -> --versionid in test script
Tahoora-Tabassum Dec 23, 2025
189458e
cli: add mkdist validations, completion helper, tests and docs
Tahoora-Tabassum Dec 26, 2025
f6f67c0
chore: refactored readme
Integer-Ctrl Dec 4, 2025
49fa461
cli: delete datasets (#38)
Integer-Ctrl Dec 4, 2025
0303454
refactor: split client into deploy and download
Integer-Ctrl Dec 5, 2025
525ba75
refactor: iteration over download.py
Integer-Ctrl Dec 5, 2025
9a95550
refactor: iteration over deploy.py
Integer-Ctrl Dec 7, 2025
0cd8c5e
refactor: webdav
Integer-Ctrl Dec 7, 2025
634ffd4
feat: ruff linter & formatter
Integer-Ctrl Dec 7, 2025
c1b8430
feat: python 3.11 migration
Integer-Ctrl Dec 9, 2025
a945b27
init: issue templates
Integer-Ctrl Dec 9, 2025
2fc99d1
init: pr template
Integer-Ctrl Dec 9, 2025
8c4cfeb
fix: wrong encoding caused wrong content lenght
Integer-Ctrl Dec 11, 2025
8ff62c3
feat: batch deletions to allow cancellation before execution
Integer-Ctrl Dec 16, 2025
03964ae
Restrict Vault token exchange to specific hosts; improve auth errors;…
DhanashreePetare Dec 21, 2025
b4bbfaa
chore: README download links
Integer-Ctrl Dec 21, 2025
f8aa663
Use api_deploy.create_distribution to avoid circular import
Tahoora-Tabassum Dec 26, 2025
0437e1a
Add verbose CLI flag with redacted HTTP logging
Tahoora-Tabassum Jan 4, 2026
5b4badd
Add verbose (-v) flag using logging; redact Authorization and X-API-K…
Tahoora-Tabassum Jan 6, 2026
3cad314
Add verbose logging support and stabilize auth tests
Tahoora-Tabassum Jan 7, 2026
3de6e16
* Restrict Vault token exchange to specific hosts; improve auth error…
DhanashreePetare Jan 7, 2026
0471ecf
Merge branch 'main' into pr-verbose-clean
Tahoora-Tabassum Feb 9, 2026
cef1580
test: skip live download integration tests
Tahoora-Tabassum Feb 9, 2026
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
19 changes: 19 additions & 0 deletions PR_BODY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Title: Add verbose CLI flag (-v) with redacted HTTP logging

Short description:
- Add a global `-v/--verbose` CLI flag to enable redacted HTTP request/response logging to help debug interactions with the Databus and Vault.

What changed:
- Add global `-v/--verbose` option to `databusclient` CLI and propagate it to API calls.
- Implement redacted HTTP logging helper (redacts `Authorization` and `X-API-KEY` headers).
- Instrument `download` and Vault token exchange flows to print HTTP request/response details when `-v` is enabled.
- Add unit tests ensuring verbose logs are printed and sensitive tokens are redacted.
- Update `README.md` and add a `CHANGELOG.md` entry.

Why:
- Provides safe, actionable debugging output for issues involving HTTP communication and auth problems without exposing secrets.

Security note:
- Authorization and API-key headers are redacted in verbose output. Avoid enabling verbose output in public CI logs.

Closes #27
4 changes: 1 addition & 3 deletions databusclient/api/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ class DownloadAuthError(Exception):
"""Raised when an authorization problem occurs during download."""



def _extract_checksums_from_jsonld(json_str: str) -> dict:
"""
Parse a JSON-LD string and return a mapping of file URI (and @id) -> checksum.
Expand Down Expand Up @@ -466,7 +465,6 @@ def _download_file(
target_filepath = os.path.join(localDir, target_filename)
_convert_compression_format(filename, target_filepath, source_format, convert_to)


def _download_files(
urls: List[str],
localDir: str,
Expand Down Expand Up @@ -679,7 +677,6 @@ def _download_collection(
checksums: dict = {}
if validate_checksum:
checksums = _resolve_checksums_for_urls(list(file_urls), databus_key)

_download_files(
list(file_urls),
localDir,
Expand Down Expand Up @@ -1010,6 +1007,7 @@ def download(
print(f"WARNING: Could not fetch checksum for single file: {e}")

# Call the worker to download the single file (passes expected checksum)

_download_file(
databusURI,
localDir,
Expand Down
47 changes: 46 additions & 1 deletion databusclient/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from typing import List

import click

import databusclient.api.deploy as api_deploy
from databusclient.api.delete import delete as api_delete
from databusclient.api.download import download as api_download, DownloadAuthError
Expand Down Expand Up @@ -244,5 +243,51 @@ def delete(databusuris: List[str], databus_key: str, dry_run: bool, force: bool)
)


@app.command()
@click.argument("url")
@click.option("--cv", "cvs", multiple=True, help="Content variant like key=value (repeatable). Keys must not contain '|' or '_'")
@click.option("--format", "file_format", help="Format extension (e.g. ttl)")
@click.option("--compression", help="Compression (e.g. gzip)")
@click.option("--sha-length", help="sha256:length (64 hex chars followed by ':' and integer length)")
@click.option("--json-output", is_flag=True, help="Output JSON distribution object instead of plain string")
def mkdist(url, cvs, file_format, compression, sha_length, json_output):
"""Create a distribution string from components."""
# Validate CVs
cvs_dict = {}
for cv in cvs:
if "=" not in cv:
raise click.BadParameter(f"Invalid content variant '{cv}': expected key=value")
key, val = cv.split("=", 1)
if any(ch in key for ch in ("|", "_")):
raise click.BadParameter("Invalid characters in content-variant key (forbidden: '|' and '_')")
if key in cvs_dict:
raise click.BadParameter(f"Duplicate content-variant key '{key}'")
cvs_dict[key] = val

# Validate sha-length
sha_tuple = None
if sha_length:
if not re.match(r'^[A-Fa-f0-9]{64}:\d+$', sha_length):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

NameError: re module is not imported.

re.match() is used here but re is never imported in this file. This will crash at runtime whenever --sha-length is provided.

Proposed fix — add import at top of file

Add to the imports at the top of the file:

import re
🤖 Prompt for AI Agents
In `@databusclient/cli.py` at line 270, The code uses re.match on the variable
sha_length (see the conditional using re.match(r'^[A-Fa-f0-9]{64}:\d+$',
sha_length)) but the re module is not imported; fix this by adding "import re"
to the module-level imports in databusclient/cli.py so re.match resolves
correctly.

raise click.BadParameter("Invalid --sha-length; expected SHA256HEX:length")
sha, length = sha_length.split(":", 1)
sha_tuple = (sha, int(length))

# Deterministic ordering
sorted_cvs = {k: cvs_dict[k] for k in sorted(cvs_dict)}

dist = api_deploy.create_distribution(url=url, cvs=sorted_cvs, file_format=file_format, compression=compression, sha256_length_tuple=sha_tuple)
if json_output:
import json as _json
click.echo(_json.dumps({"distribution": dist}))
else:
click.echo(dist)


@app.command()
@click.argument("shell", type=click.Choice(["bash","zsh","fish","powershell"]), required=False)
def completion(shell="bash"):
click.echo(f"Run: eval \"$(_DATABUSCLIENT_COMPLETE=source_{shell} python -m databusclient)\"")
Comment on lines +286 to +289
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

shell argument default not properly configured — will be None when omitted.

Click arguments with required=False default to None regardless of the Python function signature default. When a user runs databusclient completion without specifying a shell, shell will be None, producing source_None in the output.

Proposed fix
 `@app.command`()
-@click.argument("shell", type=click.Choice(["bash","zsh","fish","powershell"]), required=False)
-def completion(shell="bash"):
+@click.argument("shell", type=click.Choice(["bash","zsh","fish","powershell"]), default="bash")
+def completion(shell):
     click.echo(f"Run: eval \"$(_DATABUSCLIENT_COMPLETE=source_{shell} python -m databusclient)\"")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@app.command()
@click.argument("shell", type=click.Choice(["bash","zsh","fish","powershell"]), required=False)
def completion(shell="bash"):
click.echo(f"Run: eval \"$(_DATABUSCLIENT_COMPLETE=source_{shell} python -m databusclient)\"")
`@app.command`()
`@click.argument`("shell", type=click.Choice(["bash","zsh","fish","powershell"]), default="bash")
def completion(shell):
click.echo(f"Run: eval \"$(_DATABUSCLIENT_COMPLETE=source_{shell} python -m databusclient)\"")
🤖 Prompt for AI Agents
In `@databusclient/cli.py` around lines 264 - 267, The completion command's shell
argument can be None because Click ignores the Python default; update the
completion function (completion) to handle a missing shell by normalizing None
to a sensible default (e.g., "bash") before building the echo string — for
example set shell = shell or "bash" at the start of completion, then use that
normalized variable in the click.echo call that constructs the eval string.



if __name__ == "__main__":
app()
Empty file added file.txt
Empty file.
2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

databusclient deploy \
--version-id "https://d8lr.tools.dbpedia.org/hopver/testGroup/testArtifact/1.0-alpha/" \
--versionid "https://d8lr.tools.dbpedia.org/hopver/testGroup/testArtifact/1.0-alpha/" \
--title "Test Title" \
--abstract "Test Abstract" \
--description "Test Description" \
Expand Down
42 changes: 42 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from click.testing import CliRunner
from databusclient import cli


def test_mkdist_multiple_cv():
runner = CliRunner()
sha = 'a' * 64
res = runner.invoke(cli.app, [
'mkdist',
'https://example.org/file',
'--cv', 'b=2',
'--cv', 'a=1',
'--format', 'ttl',
'--compression', 'gz',
'--sha-length', f'{sha}:42'
])
assert res.exit_code == 0, res.output
# keys should be sorted alphabetically: a then b
assert res.output.strip() == f'https://example.org/file|a=1_b=2|ttl|gz|{sha}:42'


def test_mkdist_invalid_cv():
runner = CliRunner()
res = runner.invoke(cli.app, ['mkdist', 'https://example.org/file', '--cv', 'badcv'])
assert res.exit_code != 0
assert 'Invalid content variant' in res.output


def test_mkdist_invalid_sha():
runner = CliRunner()
res = runner.invoke(cli.app, [
'mkdist', 'https://example.org/file', '--cv', 'k=v', '--sha-length', 'abc:123'
])
assert res.exit_code != 0
assert 'Invalid --sha-length' in res.output


def test_completion_output():
runner = CliRunner()
res = runner.invoke(cli.app, ['completion', 'bash'])
assert res.exit_code == 0
assert '_DATABUSCLIENT_COMPLETE' in res.output
38 changes: 38 additions & 0 deletions tests/test_cli_verbose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from click.testing import CliRunner
from unittest.mock import Mock, patch

import databusclient.cli as cli


# CLI-level integration test for -v flag
def test_cli_download_verbose_logs_redacted(caplog):
caplog.set_level("DEBUG", logger="databusclient")
runner = CliRunner()

# Prepare mocked HTTP responses
resp_head_401 = Mock()
resp_head_401.status_code = 401
resp_head_401.headers = {}

resp_head_200 = Mock()
resp_head_200.status_code = 200
resp_head_200.headers = {}

resp_get = Mock()
resp_get.status_code = 200
resp_get.headers = {"content-length": "0"}
resp_get.iter_content = lambda chunk: iter([])

# Initial HEAD returns 401 so client uses --databus-key header on retry
with patch("requests.head", side_effect=[resp_head_401, resp_head_200]), patch(
"requests.get", return_value=resp_get
):
# Run CLI with verbose flag and databus key (so X-API-KEY will be redacted in logs)
target = "https://example.com/account/group/artifact/1/file.txt"
res = runner.invoke(cli.app, ["-v", "download", target, "--localdir", ".", "--databus-key", "SECRET"])

assert res.exit_code == 0, res.output
# Should log HTTP activity and redact secret (captured by caplog)
assert "[HTTP]" in caplog.text
assert "REDACTED" in caplog.text
assert "SECRET" not in caplog.text
2 changes: 1 addition & 1 deletion tests/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
)


@pytest.mark.skip(reason="Integration test: requires live databus.dbpedia.org connection")
def test_with_query():
api_download("tmp", DEFAULT_ENDPOINT, [TEST_QUERY])


@pytest.mark.skip(reason="Live collection download is long-running and flakes on network timeouts")
@pytest.mark.skip(reason="Integration test: requires live databus.dbpedia.org connection")
def test_with_collection():
Expand Down
33 changes: 32 additions & 1 deletion tests/test_download_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import pytest

import requests

import logging
import databusclient.api.download as dl

from databusclient.api.download import VAULT_REQUIRED_HOSTS, DownloadAuthError
from unittest.mock import patch

from databusclient.api.download import download, DownloadAuthError
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate and unused imports.

  • patch is already imported on line 1 (from unittest.mock import Mock, patch).
  • DownloadAuthError is already imported on line 9.
  • download (line 12) is not used anywhere in this file — all tests invoke dl._download_file directly.

These lines can be removed entirely.

Proposed fix
-from unittest.mock import patch
-
-from databusclient.api.download import download, DownloadAuthError
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from unittest.mock import patch
from databusclient.api.download import download, DownloadAuthError
🤖 Prompt for AI Agents
In `@tests/test_download_auth.py` around lines 10 - 12, Remove the
duplicate/unused imports in tests/test_download_auth.py: delete the extra import
of patch from unittest.mock (it's already imported with Mock), remove the
redundant DownloadAuthError import (already imported elsewhere in the file), and
drop the unused download import (tests call dl._download_file directly); ensure
only the required imports remain so tests reference dl._download_file and any
exception symbols via the existing imports.


def make_response(status=200, headers=None, content=b""):
headers = headers or {}
Expand Down Expand Up @@ -102,3 +104,32 @@ def test_403_reports_insufficient_permissions():
dl._download_file(url, localDir='.', vault_token_file="/some/token/file")

assert "permission" in str(exc.value) or "forbidden" in str(exc.value)

def test_verbose_redacts_authorization(monkeypatch, caplog):
caplog.set_level(logging.DEBUG, logger='databusclient')
vault_host = next(iter(VAULT_REQUIRED_HOSTS))
url = f"https://{vault_host}/protected/file.ttl"

resp_head = make_response(status=200, headers={})
resp_401 = make_response(status=401, headers={"WWW-Authenticate": "Bearer realm=\"auth\""})
resp_200 = make_response(status=200, headers={"content-length": "0"}, content=b"")

get_side_effects = [resp_401, resp_200]

post_resp_1 = Mock()
post_resp_1.json.return_value = {"access_token": "ACCESS"}
post_resp_2 = Mock()
post_resp_2.json.return_value = {"access_token": "VAULT"}

with patch("requests.head", return_value=resp_head), patch(
"requests.get", side_effect=get_side_effects
), patch("requests.post", side_effect=[post_resp_1, post_resp_2]):
monkeypatch.setenv("REFRESH_TOKEN", "x" * 90)

# run download with verbose enabled
dl._download_file(url, localDir='.', vault_token_file="/does/not/matter", verbose=True)
assert "[HTTP] HEAD" in caplog.text or "[HTTP] GET" in caplog.text
assert "REDACTED" in caplog.text
# Ensure token values are not directly printed
assert "ACCESS" not in caplog.text
assert "VAULT" not in caplog.text
Comment on lines +108 to +135
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the _download_file function definition and check its signature
find . -type f -name "*.py" | xargs grep -l "_download_file" | head -20

Repository: dbpedia/databus-python-client

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Search for _download_file function definition
rg "def _download_file" -A 10 --type py

Repository: dbpedia/databus-python-client

Length of output: 1296


🏁 Script executed:

#!/bin/bash
# Check how other tests in the test file handle file paths and tmp_path usage
head -50 tests/test_download_auth.py

Repository: dbpedia/databus-python-client

Length of output: 1411


🏁 Script executed:

#!/bin/bash
# Get the complete _download_file function signature
sed -n '/^def _download_file(/,/^[):]$/p' databusclient/api/download.py | head -30

Repository: dbpedia/databus-python-client

Length of output: 1253


🏁 Script executed:

#!/bin/bash
# Check complete test file to see how test_verbose_redacts_authorization is structured
wc -l tests/test_download_auth.py

Repository: dbpedia/databus-python-client

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Read the entire test file
cat tests/test_download_auth.py

Repository: dbpedia/databus-python-client

Length of output: 5033


Add verbose parameter to _download_file function signature.

The test calls _download_file(..., verbose=True) at line 130, but the function signature in databusclient/api/download.py does not include a verbose parameter. This will raise TypeError: _download_file() got an unexpected keyword argument 'verbose' at runtime. The parameter must be added to the function signature and its logging behavior implemented before this test can pass.

Additionally, consider using pytest's tmp_path fixture instead of localDir='.' to isolate test artifacts, rather than writing to the current directory—though this applies to all tests in the file, not just this one.

🤖 Prompt for AI Agents
In `@tests/test_download_auth.py` around lines 108 - 135, The _download_file
function in databusclient/api/download.py needs a new optional parameter verbose
(default False) added to its signature and used to enable detailed HTTP
request/response logging with sensitive values redacted; update the function
_download_file(...) to accept verbose: bool = False, branch on verbose to emit
debug logs (using the existing logger used elsewhere in this module) and ensure
any tokens (e.g., values from REFRESH_TOKEN, access_token, Authorization
headers) are replaced with a constant like "REDACTED" before logging; keep
behavior unchanged when verbose is False and consider using tmp_path in tests
instead of localDir='.' to avoid writing to the working directory.

76 changes: 76 additions & 0 deletions tests/test_utils_verbose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from unittest.mock import Mock, patch

import databusclient.api.utils as utils
import databusclient.api.download as dl

import requests
import logging




def make_response(status=200, headers=None, text=''):
headers = headers or {}
mock = Mock()
mock.status_code = status
mock.headers = headers
mock.text = text
def raise_for_status():
if mock.status_code >= 400:
raise requests.exceptions.HTTPError()
mock.raise_for_status = raise_for_status
return mock


def test_fetch_databus_jsonld_verbose_redacts_api_key(caplog):
caplog.set_level(logging.DEBUG, logger='databusclient')
url = "https://databus.example/resource"
resp = make_response(status=200, headers={"content-type": "application/ld+json"}, text='{}')
with patch("databusclient.api.utils.requests.get", return_value=resp):
txt = utils.fetch_databus_jsonld(url, databus_key="SECRET", verbose=True)
assert "[HTTP] GET" in caplog.text
assert "REDACTED" in caplog.text
assert "SECRET" not in caplog.text
assert txt == '{}'
Comment on lines +25 to +34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the actual function signatures
echo "=== fetch_databus_jsonld signature ==="
rg -n "def fetch_databus_jsonld" --type=py -A3

echo "=== _get_sparql_query_of_collection signature ==="
rg -n "def _get_sparql_query_of_collection" --type=py -A3

echo "=== _query_sparql_endpoint signature ==="
rg -n "def _query_sparql_endpoint" --type=py -A3

Repository: dbpedia/databus-python-client

Length of output: 1067


🏁 Script executed:

#!/bin/bash
# Check the actual test calls
echo "=== Test file lines 25-34 ==="
sed -n '25,34p' tests/test_utils_verbose.py

echo "=== Test file lines 38-47 ==="
sed -n '38,47p' tests/test_utils_verbose.py

echo "=== Test file lines 51-76 ==="
sed -n '51,76p' tests/test_utils_verbose.py

Repository: dbpedia/databus-python-client

Length of output: 2376


🏁 Script executed:

#!/bin/bash
# Check if functions use **kwargs or *args
echo "=== fetch_databus_jsonld full definition ==="
rg -A30 "def fetch_databus_jsonld" databusclient/api/utils.py

echo "=== _get_sparql_query_of_collection full definition ==="
rg -A30 "def _get_sparql_query_of_collection" databusclient/api/download.py

echo "=== _query_sparql_endpoint full definition ==="
rg -A30 "def _query_sparql_endpoint" databusclient/api/download.py

Repository: dbpedia/databus-python-client

Length of output: 3260


🏁 Script executed:

#!/bin/bash
# Check for module-level verbose variable definition
echo "=== verbose variable in utils.py ==="
rg -n "^verbose" databusclient/api/utils.py

echo "=== verbose variable in download.py ==="
rg -n "^verbose" databusclient/api/download.py

echo "=== All references to verbose in utils.py ==="
rg -n "verbose" databusclient/api/utils.py

echo "=== All references to verbose in download.py ==="
rg -n "verbose" databusclient/api/download.py

Repository: dbpedia/databus-python-client

Length of output: 282


Function signatures do not accept verbose parameter; tests will fail with TypeError at runtime.

All three test functions pass verbose=True to their respective functions, but none of the function signatures accept this parameter:

  • Line 30: utils.fetch_databus_jsonld(url, databus_key="SECRET", verbose=True) — signature is (uri: str, databus_key: str | None = None)
  • Line 43: dl._get_sparql_query_of_collection(url, databus_key="SECRET", verbose=True) — signature is (uri: str, databus_key: str | None = None)
  • Line 72: dl._query_sparql_endpoint(endpoint, query, databus_key="SECRET", verbose=True) — signature is (endpoint_url, query, databus_key=None)

Additionally, fetch_databus_jsonld (utils.py lines 57, 60) references a bare verbose variable that is never defined, which will cause a NameError if called. The other two functions (_get_sparql_query_of_collection and _query_sparql_endpoint) have no verbose logging logic implemented at all.

Either add verbose parameters to all three functions and implement the verbose logic in _get_sparql_query_of_collection and _query_sparql_endpoint, or use a different mechanism (e.g., module-level flag or monkeypatching) to control verbose output.

🤖 Prompt for AI Agents
In `@tests/test_utils_verbose.py` around lines 25 - 34, Tests pass verbose=True
but the function signatures and implementations don't accept or use it: add an
optional verbose: bool = False parameter to utils.fetch_databus_jsonld,
datastore loader method _get_sparql_query_of_collection, and
_query_sparql_endpoint (keep default False to preserve existing API), fix
fetch_databus_jsonld to reference the local verbose parameter instead of a bare
name, propagate verbose through calls (fetch_databus_jsonld ->
_get_sparql_query_of_collection -> _query_sparql_endpoint) and implement verbose
logging in the latter two to emit the same redacted "[HTTP] GET" messages when
verbose is true (redact databus_key values and ensure databus_key is accepted
and used for redaction in log output).




def test_get_sparql_query_of_collection_verbose(caplog):
caplog.set_level(logging.DEBUG, logger='databusclient')
url = "https://databus.example/collections/col"
resp = make_response(status=200, headers={"content-type": "text/sparql"}, text='SELECT *')
with patch("databusclient.api.download.requests.get", return_value=resp):
txt = dl._get_sparql_query_of_collection(url, databus_key="SECRET", verbose=True)
assert "[HTTP] GET" in caplog.text
assert "REDACTED" in caplog.text
assert "SECRET" not in caplog.text
assert txt == 'SELECT *'



def test_query_sparql_endpoint_verbose(caplog):
caplog.set_level(logging.DEBUG, logger='databusclient')
endpoint = "https://dbpedia.org/sparql"
sample = {"results": {"bindings": []}}
class MockSPARQL:
def __init__(self, url):
self.url = url
self.method = None
self._query = None
self._headers = None
def setQuery(self, q):
self._query = q
def setReturnFormat(self, fmt):
pass
def setCustomHttpHeaders(self, headers):
self._headers = headers
def query(self):
mock = Mock()
mock.convert.return_value = sample
return mock
with patch("databusclient.api.download.SPARQLWrapper", new=MockSPARQL):
res = dl._query_sparql_endpoint(endpoint, "SELECT ?s WHERE { ?s ?p ?o }", databus_key="SECRET", verbose=True)
assert "[HTTP] POST" in caplog.text
assert "REDACTED" in caplog.text
assert "SECRET" not in caplog.text
assert res == sample