das-cli 1.2.21
DAS api client.
Requires Python: >=3.9

# DAS Python Client

A Python library and CLI for interacting with the Data Archive System (DAS) API.

## Table of Contents

- Quickstart
- Installation
- CLI Usage
- Python API
- Configuration
- Examples
- Troubleshooting
- Development
- Contributing
- License

## Quickstart

```bash
# Install
pip install das-cli

# Login (prompts for credentials if omitted)
das login --api-url https://your-das-instance/api

# Search entries
das search entries --attribute Name --query 'name(*core*)' --format table

# Get an entry
das entry get --code 7b.b.4c
```

## Installation

### Requirements

- Python 3.13 or higher

### Install from PyPI

```bash
pip install das-cli
```

### Install from Source

```bash
git clone https://git.nioz.nl/ict-projects/das-cli.git
cd das-cli
pip install -e .
```

## CLI Usage

The package installs a `das` executable for interacting with the DAS API.

### Authentication

```bash
das login --api-url https://your-das-instance/api --username your_username --password your_password
```

- Omit `--username`/`--password` to be prompted securely.

### Search

```bash
das search entries --attribute <AttributeName> --query '<SearchQuery>' \
  --max-results 10 --page 1 --sort-by Name --sort-order asc --format table
```

Options:

- `--max-results <n>`: Maximum results per page (default: 10)
- `--page <num>`: Page number (default: 1)
- `--sort-by <field>`: Field to sort by (default: Name)
- `--sort-order <order>`: `asc` or `desc` (default: asc)
- `--format <format>`: `table`, `json`, or `compact` (default: table)
- `--raw`: Print raw API response

Get a specific entry by ID:

```bash
das search entry <ID> --format table
```

Help for query syntax:

```bash
das search help
```

Example queries:

- Name contains pattern: `name(*pattern*)`
- Multiple conditions: `name(*pattern*);code(*ABC*)`
- Date comparison: `Created at(>2023-01-01)`

### Entries

```bash
# Get entry by code
das entry get --code CODE

# Get entry by ID
das entry get --id ID

# Delete an entry
das entry delete --code CODE
# Or by ID
das entry delete --id ID
# Skip confirmation prompt
das entry delete --code CODE --force

# Create entries from file or data
das entry create --attribute <AttributeName> <file_path>
# Examples:
#   JSON file (can contain a list of entries)
#   das entry create --attribute core c:\data\entries.json
#   CSV file (rows become entries)
#   das entry create --attribute core c:\data\entries.csv
#   Excel file (rows become entries)
#   das entry create --attribute core c:\data\entries.xls
#   Single entry from data string
#   das entry create --attribute core --data { 'Name': 'Entry 1', ... }
#   Multiple entries from data string
#   das entry create --attribute core --data [{ 'Name': 'Entry 1' }, { 'Name': 'Entry 2' }]

# Update entries from file or data
das entry update --attribute <AttributeName> [--code CODE] <file_path>
# Notes:
# - For bulk updates, each entry must include a Code field
# - For single updates via --data, you can pass --code or include Code in data
# Examples:
#   JSON file (each object must include Code)
#   das entry update --attribute core c:\data\entries.json
#   CSV file (must have Code column)
#   das entry update --attribute core c:\data\entries.csv
#   Excel file (must have Code column)
#   das entry update --attribute core c:\data\entries.xls
#   Single entry with explicit code
#   das entry update --attribute core --code ENT001 --data { 'Grant Public Access': Yes }
#   Single entry with Code in data
#   das entry update --attribute core --data { 'Code': 'ENT001', 'Grant Public Access': Yes }
#   Multiple entries from data string (each must include Code)
#   das entry update --attribute core --data [{ 'Code': 'ENT001' }, { 'Code': 'ENT002' }]
```

#### Upload and link a digital object

```bash
# Upload a file as a digital object and link it to an entry
das entry upload-digital-object --entry-code ENT001 --type Dataset --description "CTD raw" c:\data\ctd.zip
```

#### Link or unlink digital objects

```bash
# Link digital objects by their codes to an entry
das entry link-digital-objects --entry-code ENT001 -d DO001 -d DO002

# Unlink digital objects from an entry
das entry link-digital-objects --entry-code ENT001 -d DO003 --unlink
```

#### Change ownership

```bash
# Transfer ownership of one or more entries to a user
das entry chown --user alice --code ENT001 --code ENT002
```

### Hangfire

```bash
das hangfire sync-doi ID
```

### Attributes

```bash
# By ID
das attribute get --id 123

# By name
das attribute get --name "temperature"

# By alias
das attribute get --alias "temp"

# By table name
das attribute get --table-name "measurements"

# Converters
das attribute get-name 123
das attribute get-id "temperature"
```

### Cache

```bash
das cache list
das cache clear "cache-name"
das cache clear-all
```

### Configuration

```bash
# Enable SSL certificate verification (recommended)
das config ssl-verify true

# Disable SSL certificate verification (development/testing only)
das config ssl-verify false

# Show current SSL verification status
das config ssl-status

# Reset all configuration (clears credentials and settings)
das config reset --force
```

### AI

```bash
# Start interactive DAS AI session (prompts for OpenAI API key if needed)
das ai enable

# Clear saved OpenAI key and auth token
das ai clear [--force]

# Alias for `das ai clear`
das ai logout [--force]
```

## Python API

The DAS CLI provides three layers of interaction: CLI commands, Manager layer, and Service layer. Below are examples for each command showing usage at all three layers.

### Entries

#### Get Entry

**CLI:**
```bash
# Get entry by code
das entry get --code 7b.b.4c

# Get entry by ID
das entry get --id 123
```

**Manager Layer:**
```python
from das.managers.entries_manager import EntryManager

entry_manager = EntryManager()

# Get entry by code
entry = entry_manager.get(code="7b.b.4c")

# Get entry by ID
entry = entry_manager.get(id="123")
```

**Service Layer:**
```python
from das.services.entries import EntriesService
from das.common.config import load_api_url

base_url = load_api_url()
entry_service = EntriesService(base_url)

# Get entry by code
entry = entry_service.get(code="7b.b.4c")

# Get entry by ID
entry = entry_service.get(id="123")
```

#### Create Entry

**CLI:**
```bash
# Create from JSON file
das entry create --attribute core c:\data\entries.json

# Create from CSV file
das entry create --attribute core c:\data\entries.csv

# Create single entry from data string
das entry create --attribute core --data "{ 'Name': 'Entry 1', 'Description': 'Test entry' }"

# Create multiple entries from data string
das entry create --attribute core --data "[{ 'Name': 'Entry 1' }, { 'Name': 'Entry 2' }]"
```

**Manager Layer:**
```python
from das.managers.entries_manager import EntryManager

entry_manager = EntryManager()

# Create single entry
result = entry_manager.create(
    attribute="core",
    entry={"Name": "Entry 1", "Description": "Test entry"}
)

# Create multiple entries
results = entry_manager.create(
    attribute="core",
    entries=[
        {"Name": "Entry 1", "Description": "Test entry 1"},
        {"Name": "Entry 2", "Description": "Test entry 2"}
    ]
)
```

**Service Layer:**
```python
from das.services.entries import EntriesService
from das.services.attributes import AttributesService
from das.common.config import load_api_url

base_url = load_api_url()
entry_service = EntriesService(base_url)
attribute_service = AttributesService(base_url)

# Get attribute ID first
attribute_id = attribute_service.get_id(name="core")

# Create entry
entry_id = entry_service.create(
    attribute_id=attribute_id,
    entry={"name": "Entry 1", "description": "Test entry"}
)
```

#### Update Entry

**CLI:**
```bash
# Update from JSON file
das entry update --attribute core c:\data\entries.json

# Update from CSV file
das entry update --attribute core c:\data\entries.csv

# Update single entry with explicit code
das entry update --attribute core --code ENT001 --data "{ 'Grant Public Access': 'Yes' }"

# Update single entry with Code in data
das entry update --attribute core --data "{ 'Code': 'ENT001', 'Grant Public Access': 'Yes' }"

# Update multiple entries from data string
das entry update --attribute core --data "[{ 'Code': 'ENT001', 'Name': 'Updated 1' }, { 'Code': 'ENT002', 'Name': 'Updated 2' }]"
```

**Manager Layer:**
```python
from das.managers.entries_manager import EntryManager

entry_manager = EntryManager()

# Update single entry
result = entry_manager.update(
    attribute="core",
    code="ENT001",
    entry={"Grant Public Access": "Yes"}
)

# Update multiple entries
results = entry_manager.update(
    attribute="core",
    entries=[
        {"Code": "ENT001", "Name": "Updated Entry 1"},
        {"Code": "ENT002", "Name": "Updated Entry 2"}
    ]
)
```

**Service Layer:**
```python
from das.services.entries import EntriesService
from das.services.attributes import AttributesService
from das.common.config import load_api_url

base_url = load_api_url()
entry_service = EntriesService(base_url)
attribute_service = AttributesService(base_url)

# Get existing entry to retrieve attribute_id
existing_entry = entry_service.get(code="ENT001")
attribute_id = existing_entry.get("attributeId")

# Update entry
entry_id = entry_service.update(
    attribute_id=attribute_id,
    entry={"name": "Updated Entry 1", "grantpublicaccess": "Yes"}
)
```

#### Delete Entry

**CLI:**
```bash
# Delete by code (with confirmation)
das entry delete --code ENT001

# Delete by code (skip confirmation)
das entry delete --code ENT001 --force

# Delete by ID
das entry delete --id 123
```

**Manager Layer:**
```python
from das.managers.entries_manager import EntryManager

entry_manager = EntryManager()

# Delete by code
success = entry_manager.delete(code="ENT001")

# Delete by ID
success = entry_manager.delete(id="123")
```

**Service Layer:**
```python
from das.services.entries import EntriesService
from das.common.config import load_api_url

base_url = load_api_url()
entry_service = EntriesService(base_url)

# Delete by code
success = entry_service.delete(code="ENT001")

# Delete by ID
success = entry_service.delete_by_id(id="123")
```

#### Change Ownership

**CLI:**
```bash
# Transfer ownership of entries to a user
das entry chown --user alice --code ENT001 --code ENT002
```

**Manager Layer:**
```python
from das.managers.entries_manager import EntryManager

entry_manager = EntryManager()

# Change ownership
result = entry_manager.chown(
    user_name="alice",
    entry_code_list=["ENT001", "ENT002"]
)
```

**Service Layer:**
```python
from das.services.entries import EntriesService
from das.services.users import UsersService
from das.common.config import load_api_url

base_url = load_api_url()
entry_service = EntriesService(base_url)
user_service = UsersService(base_url)

# Get user ID
user = user_service.get_user("alice")
user_id = user.get("id")

# Get entry IDs
entry1 = entry_service.get(code="ENT001")
entry2 = entry_service.get(code="ENT002")
entry_ids = [entry1.get("entry", {}).get("id"), entry2.get("entry", {}).get("id")]

# Change ownership
result = entry_service.chown(
    new_user_id=user_id,
    entry_list_ids=entry_ids
)
```

### Search

**CLI:**
```bash
# Search entries
das search entries --attribute Cores --query "name(*64*)" --max-results 10 --page 1 --sort-by Name --sort-order asc --format table

# Get detailed entry by ID
das search entry 6b0e68e6-00cd-43a7-9c51-d56c9c091123 --format json
```

**Manager Layer:**
```python
from das.managers.search_manager import SearchManager

search_manager = SearchManager()

# Search entries
results = search_manager.search_entries(
    attribute="Cores",
    query="name(*64*)",
    max_results=10,
    page=1,
    sort_by="Name",
    sort_order="asc"
)
```

**Service Layer:**
```python
from das.services.search import SearchService
from das.services.attributes import AttributesService
from das.common.config import load_api_url

base_url = load_api_url()
search_service = SearchService(base_url)
attribute_service = AttributesService(base_url)

# Get attribute ID
attr_response = attribute_service.get_attribute(name="Cores")
attribute_id = attr_response.get("result", {}).get("items", [])[0].get("id")

# Search entries
results = search_service.search_entries(
    attributeId=attribute_id,
    queryString="name(*64*);",
    maxResultCount=10,
    skipCount=0,
    sorting="name asc"
)
```

### Downloads

#### Create Download Request

**CLI:**
```bash
# Create request for all files from an entry
das download request --entry ENT001 --name "My Download Request"

# Create request with specific files
das download request --entry ENT001 --file FILE001 --file FILE002

# Create request from JSON file
das download request --from-file request.json
```

**Manager Layer:**
```python
from das.managers.download_manager import DownloadManager

download_manager = DownloadManager()

# Create download request
request_data = {
    'name': 'My Download Request',
    'ENT001': ['FILE001', 'FILE002'],  # Specific files
    'ENT002': []  # All files from this entry
}
request_id = download_manager.create_download_request(request_data)
```

**Service Layer:**
```python
from das.services.downloads import DownloadRequestService
from das.common.config import load_api_url

base_url = load_api_url()
download_service = DownloadRequestService(base_url)

# Create download request (requires formatted request items)
request_items = {
    'items': [
        {
            'name': 'My Download Request',
            'sourceId': 'entry_id_1',
            'sourceAttributeId': 123,
            'id': 'digital_object_id_1'
        }
    ]
}
request_id = download_service.create(request_items)
```

#### List Download Requests

**CLI:**
```bash
# List your download requests
das download my-requests --format table
```

**Manager Layer:**
```python
from das.managers.download_manager import DownloadManager

download_manager = DownloadManager()

# Get all download requests
my_requests = download_manager.get_my_requests()
```

**Service Layer:**
```python
from das.services.downloads import DownloadRequestService
from das.common.config import load_api_url

base_url = load_api_url()
download_service = DownloadRequestService(base_url)

# Get all download requests
my_requests = download_service.get_my_requests()
```

#### Delete Download Request

**CLI:**
```bash
# Delete a download request
das download delete-request 6b0e68e6-00cd-43a7-9c51-d56c9c091123
```

**Manager Layer:**
```python
from das.managers.download_manager import DownloadManager

download_manager = DownloadManager()

# Delete download request
result = download_manager.delete_download_request("6b0e68e6-00cd-43a7-9c51-d56c9c091123")
```

**Service Layer:**
```python
from das.services.downloads import DownloadRequestService
from das.common.config import load_api_url

base_url = load_api_url()
download_service = DownloadRequestService(base_url)

# Delete download request
result = download_service.delete("6b0e68e6-00cd-43a7-9c51-d56c9c091123")
```

#### Download Files

**CLI:**
```bash
# Download files to current directory
das download files 6b0e68e6-00cd-43a7-9c51-d56c9c091123

# Download to specific folder
das download files 6b0e68e6-00cd-43a7-9c51-d56c9c091123 --out C:\Downloads

# Download with explicit filename, overwriting if exists
das download files 6b0e68e6-00cd-43a7-9c51-d56c9c091123 --out C:\Downloads\bundle.zip --force
```

**Manager Layer:**
```python
from das.managers.download_manager import DownloadManager

download_manager = DownloadManager()

# Save download to disk
saved_path = download_manager.save_download(
    request_id="6b0e68e6-00cd-43a7-9c51-d56c9c091123",
    output_path="C:\\Downloads",
    overwrite=False
)
```

**Service Layer:**
```python
from das.services.downloads import DownloadRequestService
from das.common.config import load_api_url

base_url = load_api_url()
download_service = DownloadRequestService(base_url)

# Get streaming response
response = download_service.download_files("6b0e68e6-00cd-43a7-9c51-d56c9c091123")

# Save to disk manually
with open("download.zip", "wb") as f:
    for chunk in response.iter_content(chunk_size=8192):
        if chunk:
            f.write(chunk)
```

### Attributes

**CLI:**
```bash
# Get attribute by ID
das attribute get --id 123

# Get attribute by name
das attribute get --name "temperature"

# Get attribute name by ID
das attribute get-name 123

# Get attribute ID by name
das attribute get-id "temperature"
```

**Manager Layer:**
```python
from das.services.attributes import AttributesService
from das.common.config import load_api_url

base_url = load_api_url()
attribute_service = AttributesService(base_url)

# Get attribute by various methods
attribute = attribute_service.get_attribute(id=123)
attribute = attribute_service.get_attribute(name="temperature")
attribute = attribute_service.get_attribute(alias="temp")
attribute = attribute_service.get_attribute(table_name="measurements")

# Get name/ID converters
name = attribute_service.get_name(123)
attr_id = attribute_service.get_id("temperature")
```

**Service Layer:**
```python
from das.services.attributes import AttributesService
from das.common.config import load_api_url

base_url = load_api_url()
attribute_service = AttributesService(base_url)

# Get attribute
result = attribute_service.get_attribute(id=123, name="temperature", alias="temp", table_name="measurements")

# Get name/ID converters
name = attribute_service.get_name(123)
attr_id = attribute_service.get_id("temperature")
```

### Cache

**CLI:**
```bash
# List all cache entries
das cache list

# Clear a specific cache
das cache clear "cache-name"

# Clear all caches
das cache clear-all
```

**Manager Layer:**
```python
from das.app import Das

client = Das("https://your-das-instance/api")
client.authenticate("username", "password")

# List all caches
cache_entries = client.cache.get_all()

# Clear specific cache
result = client.cache.clear_cache("cache-name")

# Clear all caches
result = client.cache.clear_all()
```

**Service Layer:**
```python
from das.services.cache import CacheService
from das.common.config import load_api_url

base_url = load_api_url()
cache_service = CacheService(base_url)

# List all caches
cache_entries = cache_service.get_all()

# Clear specific cache
result = cache_service.clear_cache("cache-name")

# Clear all caches
result = cache_service.clear_all()
```

### Hangfire

**CLI:**
```bash
# Trigger DOI synchronization
das hangfire sync-doi 123
```

**Manager Layer:**
```python
from das.app import Das

client = Das("https://your-das-instance/api")
client.authenticate("username", "password")

# Sync DOI
client.hangfire.sync_doi("123")
```

**Service Layer:**
```python
from das.services.hangfire import HangfireService
from das.common.config import load_api_url

base_url = load_api_url()
hangfire_service = HangfireService(base_url)

# Sync DOI
result = hangfire_service.sync_doi("123")
```

### Digital Objects

#### Upload Digital Object

**CLI:**
```bash
# Upload a file as digital object and link to entry
das entry upload-digital-object --entry-code ENT001 --type Dataset --description "CTD raw" c:\data\ctd.zip
```

**Manager Layer:**
```python
from das.managers.digital_objects_manager import DigitalObjectsManager

digital_objects_manager = DigitalObjectsManager()

# Upload digital object
digital_object_id = digital_objects_manager.upload_digital_object(
    entry_code="ENT001",
    file_description="CTD raw",
    digital_object_type="Dataset",
    file_path="c:\\data\\ctd.zip"
)
```

#### Link/Unlink Digital Objects

**CLI:**
```bash
# Link digital objects to an entry
das entry link-digital-objects --entry-code ENT001 -d DO001 -d DO002

# Unlink digital objects from an entry
das entry link-digital-objects --entry-code ENT001 -d DO003 --unlink
```

**Manager Layer:**
```python
from das.managers.digital_objects_manager import DigitalObjectsManager

digital_objects_manager = DigitalObjectsManager()

# Link digital objects
success = digital_objects_manager.link_existing_digital_objects(
    entry_code="ENT001",
    digital_object_code_list=["DO001", "DO002"],
    is_unlink=False
)

# Unlink digital objects
success = digital_objects_manager.link_existing_digital_objects(
    entry_code="ENT001",
    digital_object_code_list=["DO003"],
    is_unlink=True
)
```

### Configuration

**CLI:**
```bash
# Enable SSL verification
das config ssl-verify true

# Disable SSL verification
das config ssl-verify false

# Check SSL status
das config ssl-status

# Reset all configuration
das config reset --force
```

**Python:**
```python
from das.common.config import save_verify_ssl, load_verify_ssl, save_api_url, load_api_url

# SSL verification
save_verify_ssl(True)   # enable (recommended)
save_verify_ssl(False)  # disable (dev/testing)
current_setting = load_verify_ssl()

# API URL
save_api_url("https://your-das-instance/api")
api_url = load_api_url()
```

## Configuration

### SSL certificate verification

```bash
# Enable (default)
das config ssl-verify true

# Disable (development/testing only)
das config ssl-verify false

# Check status
das config ssl-status
```

## Downloads

```bash
# Create a download request
das download request --entry ENT001 --name "My Download Request"

# Create a request with specific files
das download request --entry ENT001 --file FILE001 --file FILE002

# Create a request with multiple entries
das download request --entry ENT001 --entry ENT002 --name "Multiple Entries"

# Create a request from JSON file
das download request --from-file request.json

# List your download requests
das download my-requests --format table

# Delete a download request
das download delete-request 6b0e68e6-00cd-43a7-9c51-d56c9c091123

# Download files for a completed request
das download files 6b0e68e6-00cd-43a7-9c51-d56c9c091123

# Save to a specific folder
das download files 6b0e68e6-00cd-43a7-9c51-d56c9c091123 --out C:\Downloads

# Save to an explicit filename, overwriting if it exists
das download files 6b0e68e6-00cd-43a7-9c51-d56c9c091123 --out C:\Downloads\bundle.zip --force
```

## Examples

```bash
# List cache and clear a specific cache
das cache list
das cache clear "attributes"

# Dump a single entry as JSON
das search entry 123 --format json

# Paginate through search results
das search entries --attribute Name --query 'name(*core*)' --max-results 50 --page 2

# List your download requests
das download my-requests --format table

# Reset all configuration
das config reset --force
```

## Troubleshooting

- Authentication failed:
  - Ensure `--api-url` points to the correct DAS instance base URL (often ends with `/api`).
  - Try re-running `das login` without passing password to be prompted.
- SSL certificate errors:
  - Use `das config ssl-verify false` temporarily in non-production environments.
  - Consider configuring your corporate CA store instead of disabling verification.
- Proxy/network issues:
  - Respect environment variables `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` if required.
- Windows PowerShell quoting:
  - Prefer single quotes for queries, or escape `*` and `;` as needed (e.g., `\*`).
- Unexpected output formatting:
  - Switch format with `--format json` or `--format compact`.

## Dependencies

Runtime and development dependencies are declared in `requirements.txt` and `pyproject.toml`. Install via `pip install das-cli` or `pip install -e .` for development.

## Development

### Setting Up Development Environment

```bash
# Clone the repository
git clone https://git.nioz.nl/ict-projects/das-cli.git
cd das-cli

# Create and activate a virtual environment
python -m venv venv
.\venv\Scripts\activate  # On Windows
source venv/bin/activate    # On Unix/macOS

# Install in editable mode
pip install -e .

# (Optional) Install dev/test tooling
pip install -e .[dev]
```

### Running Tests

```bash
# Run all tests
python run_tests.py

# Run a specific test file
python -m pytest tests/specific_test_file.py
```

## Contributing

Contributions are welcome! Please open a Pull Request.

Report bugs or request features via the [DAS CLI issue tracker](https://git.nioz.nl/ict-projects/das-cli/-/issues).

## License

MIT License

## Maintainers

This project is maintained by the Royal Netherlands Institute for Sea Research (NIOZ).