diff --git a/README.md b/README.md index e329578..b66635d 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,26 @@ python3 -m pip install databusclient You can then use the client in the command line: ```bash +# Python databusclient --help -databusclient deploy --help -databusclient delete --help -databusclient download --help + +# Example output: +# Usage: databusclient [OPTIONS] COMMAND [ARGS]... +# +# Options: +# --install-completion [bash|zsh|fish|powershell|pwsh] Install completion for the specified shell. +# --show-completion [bash|zsh|fish|powershell|pwsh] Show completion for the specified shell. +# --help Show this message and exit. +# +# Commands: +# deploy +# download +# delete +# mkdist +# completion +``` + +### Download command ``` ### Docker @@ -283,7 +299,7 @@ Usage: databusclient deploy [OPTIONS] [DISTRIBUTIONS]... - Upload & deploy via Nextcloud (--webdav-url, --remote, --path) Options: - --version-id TEXT Target databus version/dataset identifier of the form [required] --title TEXT Dataset title [required] @@ -297,6 +313,18 @@ Options: --remote TEXT rclone remote name (e.g., 'nextcloud') --path TEXT Remote path on Nextcloud (e.g., 'datasets/mydataset') --help Show this message and exit. +<<<<<<< HEAD + +``` +#### Examples of using deploy command +##### Mode 1: Classic Deploy (Distributions) +``` +databusclient deploy --versionid https://databus.dbpedia.org/user1/group1/artifact1/2022-05-18 --title title1 --abstract abstract1 --description description1 --license http://dalicc.net/licenselibrary/AdaptivePublicLicense10 --apikey MYSTERIOUS 'https://raw.githubusercontent.com/dbpedia/databus/master/server/app/api/swagger.yml|type=swagger' +``` + +``` +databusclient deploy --versionid https://dev.databus.dbpedia.org/denis/group1/artifact1/2022-05-18 --title "Client Testing" --abstract "Testing the client...." --description "Testing the client...." --license http://dalicc.net/licenselibrary/AdaptivePublicLicense10 --apikey MYSTERIOUS 'https://raw.githubusercontent.com/dbpedia/databus/master/server/app/api/swagger.yml|type=swagger' +======= ``` ### Mode 1: Classic Deploy (Distributions) @@ -320,6 +348,7 @@ docker run --rm -v $(pwd):/data dbpedia/databus-python-client deploy \ --license http://dalicc.net/licenselibrary/AdaptivePublicLicense10 \ --apikey MYSTERIOUS \ 'https://raw.githubusercontent.com/dbpedia/databus/master/server/app/api/swagger.yml|type=swagger +>>>>>>> upstream/main ``` A few more notes for CLI usage: @@ -336,6 +365,10 @@ All files referenced there will be registered on the Databus. ```bash # Python databusclient deploy \ +<<<<<<< HEAD + --metadata /home/metadata.json \ + --versionid https://databus.org/user/dataset/version/1.0 \ +======= --metadata ./metadata.json \ --version-id https://databus.dbpedia.org/user1/group1/artifact1/1.0 \ --title "Metadata Deploy Example" \ @@ -347,6 +380,7 @@ databusclient deploy \ docker run --rm -v $(pwd):/data dbpedia/databus-python-client deploy \ --metadata ./metadata.json \ --version-id https://databus.dbpedia.org/user1/group1/artifact1/1.0 \ +>>>>>>> upstream/main --title "Metadata Deploy Example" \ --abstract "This is a short abstract of the dataset." \ --description "This dataset was uploaded using metadata.json." \ @@ -382,6 +416,9 @@ databusclient deploy \ --webdav-url https://cloud.example.com/remote.php/webdav \ --remote nextcloud \ --path datasets/mydataset \ +<<<<<<< HEAD + --versionid https://databus.org/user/dataset/version/1.0 \ +======= --version-id https://databus.dbpedia.org/user1/group1/artifact1/1.0 \ --title "Test Dataset" \ --abstract "Short abstract of dataset" \ @@ -396,6 +433,7 @@ docker run --rm -v $(pwd):/data dbpedia/databus-python-client deploy \ --remote nextcloud \ --path datasets/mydataset \ --version-id https://databus.dbpedia.org/user1/group1/artifact1/1.0 \ +>>>>>>> upstream/main --title "Test Dataset" \ --abstract "Short abstract of dataset" \ --description "This dataset was uploaded for testing the Nextcloud → Databus pipeline." \ @@ -481,6 +519,48 @@ databusclient delete https://databus.dbpedia.org/dbpedia/collections/dbpedia-sna docker run --rm -v $(pwd):/data dbpedia/databus-python-client delete https://databus.dbpedia.org/dbpedia/collections/dbpedia-snapshot-2022-12 --databus-key YOUR_API_KEY ``` +### mkdist command + +Create a distribution string from components. + +Usage: +``` +databusclient mkdist URL --cv key=value --cv key2=value2 --format ttl --compression gz --sha-length : +``` + +Example: +``` +python -m databusclient mkdist "https://example.org/file.ttl" --cv lang=en --cv part=sorted --format ttl --compression gz --sha-length aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:12345 +``` + +## Completion + +Enable shell completion (bash example): +``` +eval "$(_DATABUSCLIENT_COMPLETE=source_bash python -m databusclient)" +``` + +### mkdist command + +Create a distribution string from components. + +Usage: +``` +databusclient mkdist URL --cv key=value --cv key2=value2 --format ttl --compression gz --sha-length : +``` + +Example: +``` +python -m databusclient mkdist "https://example.org/file.ttl" --cv lang=en --cv part=sorted --format ttl --compression gz --sha-length aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:12345 +``` + +## Completion + +Enable shell completion (bash example): +``` +eval "$(_DATABUSCLIENT_COMPLETE=source_bash python -m databusclient)" +``` + ## Module Usage diff --git a/databusclient/cli.py b/databusclient/cli.py index 069408e..b5145bf 100644 --- a/databusclient/cli.py +++ b/databusclient/cli.py @@ -4,6 +4,7 @@ from typing import List import click +import re import databusclient.api.deploy as api_deploy from databusclient.api.delete import delete as api_delete @@ -213,5 +214,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): + 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)\"") + + if __name__ == "__main__": app() diff --git a/test.sh b/test.sh index f590198..0a4c096 100755 --- a/test.sh +++ b/test.sh @@ -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" \ diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..3dfd3eb --- /dev/null +++ b/tests/test_cli.py @@ -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