Repository navigation
feat: support overriding HTTP headers and User-Agent (#98) #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,9 @@ | |
| from databusclient.api.utils import ( | ||
| fetch_databus_jsonld, | ||
| get_databus_id_parts_from_file_url, | ||
| get_default_headers, | ||
| set_header, | ||
| set_header_if_missing, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -23,16 +26,18 @@ class DeleteQueue: | |
| Allows adding multiple databus URIs to a queue and executing their deletion in batch. | ||
| """ | ||
|
|
||
| def __init__(self, databus_key: str, manifest_context=None): | ||
| def __init__(self, databus_key: str, manifest_context=None, headers=None): | ||
| """Create a DeleteQueue bound to a given Databus API key. | ||
|
|
||
| Args: | ||
| databus_key: API key used to authenticate deletion requests. | ||
| manifest_context: Optional ManifestContext to record deletion | ||
| outcomes into. Passed through to _delete_list on execute(). | ||
| headers: Optional custom HTTP headers to send with deletion requests. | ||
| """ | ||
| self.databus_key = databus_key | ||
| self.manifest_context = manifest_context | ||
| self.headers = headers | ||
| self.queue: set[str] = set() | ||
|
|
||
| def add_uri(self, databusURI: str): | ||
|
|
@@ -79,6 +84,7 @@ def execute(self): | |
| self.databus_key, | ||
| force=True, | ||
| manifest_context=self.manifest_context, | ||
| headers=self.headers, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -122,6 +128,7 @@ def _delete_resource( | |
| force: bool = False, | ||
| queue: DeleteQueue = None, | ||
| manifest_context=None, | ||
| headers=None, | ||
| ): | ||
| """Delete a single Databus resource (version, artifact, group). | ||
|
|
||
|
|
@@ -159,8 +166,10 @@ def _delete_resource( | |
| return | ||
|
|
||
| print(f"[DELETE] {databusURI}") | ||
| headers = {"accept": "*/*", "X-API-KEY": databus_key} | ||
| response = requests.delete(databusURI, headers=headers, timeout=30) | ||
| req_headers = get_default_headers(headers) | ||
| set_header_if_missing(req_headers, "accept", "*/*") | ||
| set_header(req_headers, "X-API-KEY", databus_key) | ||
| response = requests.delete(databusURI, headers=req_headers, timeout=30) | ||
|
|
||
| if response.status_code in (200, 204): | ||
| print(f"Successfully deleted: {databusURI}") | ||
|
|
@@ -179,6 +188,7 @@ def _delete_list( | |
| force: bool = False, | ||
| queue: DeleteQueue = None, | ||
| manifest_context=None, | ||
| headers=None, | ||
| ): | ||
| """Delete a list of Databus resources. | ||
|
|
||
|
|
@@ -191,7 +201,7 @@ def _delete_list( | |
| """ | ||
| for databusURI in databusURIs: | ||
| _delete_resource( | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context, headers=headers | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -202,6 +212,7 @@ def _delete_artifact( | |
| force: bool = False, | ||
| queue: DeleteQueue = None, | ||
| manifest_context=None, | ||
| headers=None, | ||
| ): | ||
| """Delete an artifact and all its versions. | ||
|
|
||
|
|
@@ -215,7 +226,7 @@ def _delete_artifact( | |
| force: If True, skip confirmation prompt and proceed with deletion. | ||
| queue: If queue is provided, add the URI to the queue instead of deleting immediately. | ||
| """ | ||
| artifact_body = fetch_databus_jsonld(databusURI, databus_key) | ||
| artifact_body = fetch_databus_jsonld(databusURI, databus_key, headers=headers) | ||
|
|
||
| json_dict = json.loads(artifact_body) | ||
| versions = json_dict.get("databus:hasVersion") | ||
|
|
@@ -235,11 +246,19 @@ def _delete_artifact( | |
| else: | ||
| # Delete all versions | ||
| _delete_list( | ||
| version_uris, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| version_uris, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context, headers=headers | ||
| ) | ||
|
|
||
| # Finally, delete the artifact itself | ||
| _delete_resource(databusURI, databus_key, dry_run=dry_run, force=force, queue=queue,manifest_context=manifest_context) | ||
| _delete_resource( | ||
| databusURI, | ||
| databus_key, | ||
| dry_run=dry_run, | ||
| force=force, | ||
| queue=queue, | ||
| manifest_context=manifest_context, | ||
| headers=headers, | ||
| ) | ||
|
|
||
|
|
||
| def _delete_group( | ||
|
|
@@ -249,6 +268,7 @@ def _delete_group( | |
| force: bool = False, | ||
| queue: DeleteQueue = None, | ||
| manifest_context=None, | ||
| headers=None, | ||
| ): | ||
| """Delete a group and all its artifacts and versions. | ||
|
|
||
|
|
@@ -262,7 +282,7 @@ def _delete_group( | |
| force: If True, skip confirmation prompt and proceed with deletion. | ||
| queue: If queue is provided, add the URI to the queue instead of deleting immediately. | ||
| """ | ||
| group_body = fetch_databus_jsonld(databusURI, databus_key) | ||
| group_body = fetch_databus_jsonld(databusURI, databus_key, headers=headers) | ||
|
|
||
| json_dict = json.loads(group_body) | ||
| artifacts = json_dict.get("databus:hasArtifact", []) | ||
|
|
@@ -279,14 +299,35 @@ def _delete_group( | |
| # Delete all artifacts (which deletes their versions) | ||
| for artifact_uri in artifact_uris: | ||
| _delete_artifact( | ||
| artifact_uri, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| artifact_uri, | ||
| databus_key, | ||
| dry_run=dry_run, | ||
| force=force, | ||
| queue=queue, | ||
| manifest_context=manifest_context, | ||
| headers=headers, | ||
| ) | ||
|
|
||
| # Finally, delete the group itself | ||
| _delete_resource(databusURI, databus_key, dry_run=dry_run, force=force, queue=queue,manifest_context=manifest_context) | ||
| _delete_resource( | ||
| databusURI, | ||
| databus_key, | ||
| dry_run=dry_run, | ||
| force=force, | ||
| queue=queue, | ||
| manifest_context=manifest_context, | ||
| headers=headers, | ||
| ) | ||
|
|
||
|
|
||
| def delete(databusURIs: List[str], databus_key: str, dry_run: bool, force: bool, manifest_context=None): | ||
| def delete( | ||
| databusURIs: List[str], | ||
| databus_key: str, | ||
| dry_run: bool = False, | ||
| force: bool = False, | ||
| manifest_context=None, | ||
| headers=None, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass headers through recursive artifact and group deletion. The public
|
||
| ): | ||
| """Delete a dataset from the databus. | ||
|
|
||
| Delete a group, artifact, or version identified by the given databus URI. | ||
|
|
@@ -299,7 +340,7 @@ def delete(databusURIs: List[str], databus_key: str, dry_run: bool, force: bool, | |
| force: If True, skip confirmation prompt and proceed with deletion. | ||
| """ | ||
|
|
||
| queue = DeleteQueue(databus_key, manifest_context=manifest_context) | ||
| queue = DeleteQueue(databus_key, manifest_context=manifest_context, headers=headers) | ||
|
|
||
| for databusURI in databusURIs: | ||
| _host, _account, group, artifact, version, file = ( | ||
|
|
@@ -309,24 +350,36 @@ def delete(databusURIs: List[str], databus_key: str, dry_run: bool, force: bool, | |
| if group == "collections" and artifact is not None: | ||
| print(f"Deleting collection: {databusURI}") | ||
| _delete_resource( | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context, headers=headers | ||
| ) | ||
| elif file is not None: | ||
| print(f"Deleting file is not supported via API: {databusURI}") | ||
| elif version is not None: | ||
| print(f"Deleting version: {databusURI}") | ||
| _delete_resource( | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context, headers=headers | ||
| ) | ||
| elif artifact is not None: | ||
| print(f"Deleting artifact and all its versions: {databusURI}") | ||
| _delete_artifact( | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| databusURI, | ||
| databus_key, | ||
| dry_run=dry_run, | ||
| force=force, | ||
| queue=queue, | ||
| manifest_context=manifest_context, | ||
| headers=headers, | ||
| ) | ||
| elif group is not None and group != "collections": | ||
| print(f"Deleting group and all its artifacts and versions: {databusURI}") | ||
| _delete_group( | ||
| databusURI, databus_key, dry_run=dry_run, force=force, queue=queue, manifest_context=manifest_context | ||
| databusURI, | ||
| databus_key, | ||
| dry_run=dry_run, | ||
| force=force, | ||
| queue=queue, | ||
| manifest_context=manifest_context, | ||
| headers=headers, | ||
| ) | ||
| else: | ||
| print(f"Deleting {databusURI} is not supported.") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,10 +8,16 @@ | |
| import hashlib | ||
| import json | ||
| from enum import Enum | ||
| from typing import Dict, List, Optional, Tuple, Union | ||
| from typing import Any, Dict, Iterable, List, Optional, Tuple, Union | ||
|
|
||
| import requests | ||
|
|
||
| from databusclient.api.utils import ( | ||
| get_default_headers, | ||
| set_header, | ||
| set_header_if_missing, | ||
| ) | ||
|
|
||
| _debug = False | ||
|
|
||
|
|
||
|
|
@@ -176,14 +182,17 @@ def _get_file_stats(distribution_str: str) -> Tuple[Optional[str], Optional[int] | |
| return sha256sum, content_length | ||
|
|
||
|
|
||
| def _load_file_stats(url: str) -> Tuple[str, int]: | ||
| def _load_file_stats( | ||
| url: str, headers: Union[Dict[str, Any], Iterable[str], None] = None | ||
|
Comment on lines
+185
to
+186
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass deploy headers into fallback file-stat requests.
|
||
| ) -> Tuple[str, int]: | ||
| """Download the file at ``url`` and compute its SHA-256 and length. | ||
|
|
||
| This is used as a fallback when the caller did not supply checksum/size | ||
| information in the CLI or metadata file. | ||
| """ | ||
|
|
||
| resp = requests.get(url, timeout=30) | ||
| req_headers = get_default_headers(headers) | ||
| resp = requests.get(url, headers=req_headers, timeout=30) | ||
| if resp.status_code >= 400: | ||
| raise requests.exceptions.RequestException(response=resp) | ||
|
|
||
|
|
@@ -192,7 +201,10 @@ def _load_file_stats(url: str) -> Tuple[str, int]: | |
| return sha256sum, content_length | ||
|
|
||
|
|
||
| def get_file_info(distribution_str: str) -> Tuple[Dict[str, str], str, str, str, int]: | ||
| def get_file_info( | ||
| distribution_str: str, | ||
| headers: Union[Dict[str, Any], Iterable[str], None] = None, | ||
| ) -> Tuple[Dict[str, str], str, str, str, int]: | ||
| """Return parsed file information for a distribution string. | ||
|
|
||
| Returns a tuple `(cvs, format_extension, compression, sha256sum, size)`. | ||
|
|
@@ -210,7 +222,7 @@ def get_file_info(distribution_str: str) -> Tuple[Dict[str, str], str, str, str, | |
|
|
||
| if sha256sum is None or content_length is None: | ||
| __url = str(distribution_str).split("|")[0] | ||
| sha256sum, content_length = _load_file_stats(__url) | ||
| sha256sum, content_length = _load_file_stats(__url, headers=headers) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deploy headers are sent unfiltered to third-party file hosts.
The download path filters credential headers by destination origin with
|
||
|
|
||
| return cvs, format_extension, compression, sha256sum, content_length | ||
|
|
||
|
|
@@ -320,6 +332,7 @@ def create_dataset( | |
| group_title: str = None, | ||
| group_abstract: str = None, | ||
| group_description: str = None, | ||
| headers: Union[Dict[str, Any], Iterable[str], None] = None, | ||
| ) -> Dict[str, Union[List[Dict[str, Union[bool, str, int, float, List]]], str]]: | ||
| """ | ||
| Creates a Databus Dataset as a python dict from distributions and submitted metadata. WARNING: If file stats (sha256sum, content length) | ||
|
|
@@ -376,7 +389,7 @@ def create_dataset( | |
| compression, | ||
| sha256sum, | ||
| content_length, | ||
| ) = get_file_info(dst_string) | ||
| ) = get_file_info(dst_string, headers=headers) | ||
|
|
||
| if not cvs and len(distributions) > 1: | ||
| raise BadArgumentException( | ||
|
|
@@ -463,6 +476,7 @@ def deploy( | |
| verify_parts: bool = False, | ||
| log_level: DeployLogLevel = DeployLogLevel.debug, | ||
| debug: bool = False, | ||
| headers: Union[Dict[str, Any], Iterable[str], None] = None, | ||
| ) -> None: | ||
| """Deploys a dataset to the databus. The endpoint is inferred from the DataID identifier. | ||
| Parameters | ||
|
|
@@ -479,7 +493,9 @@ def deploy( | |
| controls whether output shold be printed to the console (stdout) | ||
| """ | ||
|
|
||
| headers = {"X-API-KEY": f"{api_key}", "Content-Type": "application/json"} | ||
| req_headers = get_default_headers(headers) | ||
| set_header(req_headers, "X-API-KEY", f"{api_key}") | ||
| set_header_if_missing(req_headers, "Content-Type", "application/json") | ||
| data = json.dumps(dataid) | ||
|
|
||
| try: | ||
|
|
@@ -491,7 +507,7 @@ def deploy( | |
| base | ||
| + f"/api/publish?verify-parts={str(verify_parts).lower()}&log-level={log_level.name}" | ||
| ) | ||
| resp = requests.post(api_uri, data=data, headers=headers, timeout=30) | ||
| resp = requests.post(api_uri, data=data, headers=req_headers, timeout=30) | ||
|
|
||
| if debug or _debug: | ||
| try: | ||
|
|
@@ -517,6 +533,7 @@ def deploy_from_metadata( | |
| artifact_version_description: str, | ||
| license_url: str, | ||
| apikey: str, | ||
| headers: Union[Dict[str, Any], Iterable[str], None] = None, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forward metadata deploy headers to
|
||
| ) -> None: | ||
| """ | ||
| Deploy a dataset from metadata entries. | ||
|
|
@@ -547,10 +564,11 @@ def deploy_from_metadata( | |
| artifact_version_description=artifact_version_description, | ||
| license_url=license_url, | ||
| distributions=distributions, | ||
| headers=headers, | ||
| ) | ||
|
|
||
| print(f"Deploying dataset version: {version_id}") | ||
| deploy(dataset, apikey) | ||
| deploy(dataset, apikey, headers=headers) | ||
|
|
||
| print(f"Successfully deployed to {version_id}") | ||
| print(f"Deployed {len(metadata)} file(s):") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forward stored headers when executing the delete queue.
DeleteQueuestoresheaders, butexecute()does not passself.headersto_delete_list. Normal deletion queues each URI before issuing its DELETE requests. Those requests therefore never receive CLI, API, or workflow header overrides. Passheaders=self.headersfromexecute().