Metadata-Version: 2.4
Name: quickbase-api
Version: 0.2.0
Summary: A simple Python wrapper for the Quickbase API
Project-URL: Homepage, https://github.com/tbrezler/quickbase-api
Project-URL: Issues, https://github.com/tbrezler/quickbase-api/issues
Project-URL: Documentation, https://github.com/tbrezler/quickbase-api#readme
Author-email: Tyler Brezler <tbrezler@gmail.com>
License: MIT
License-File: LICENSE.txt
Keywords: api,database,quickbase,rest,wrapper
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: requests>=2.25.0
Requires-Dist: ruamel-yaml>=0.19.1
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.20; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# quickbase-api

A simple Python wrapper for the [Quickbase API](https://developer.quickbase.com/).

[![PyPI version](https://badge.fury.io/py/quickbase-api.svg)](https://pypi.org/project/quickbase-api/)
[![Python versions](https://img.shields.io/pypi/pyversions/quickbase-api.svg)](https://pypi.org/project/quickbase-api/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Installation

```bash
pip install quickbase-api
```

## Quick Start

```python
import quickbase_api

# Option 1: Pass credentials directly
client = quickbase_api.client(
    realm="mycompany.quickbase.com",
    user_token="your-user-token",
)

# Option 2: Use environment variables
#   export QUICKBASE_REALM=mycompany.quickbase.com
#   export QUICKBASE_USER_TOKEN=your-user-token
client = quickbase_api.client()

# Query records from a table
records = client.query_for_data(
    table_id="bqrg4xyza",
    select=[3, 6, 7],
    where="{6.EX.'Active'}",
)
```

## Authentication

This library uses [Quickbase user tokens](https://developer.quickbase.com/auth) for
authentication. You can pass your credentials directly or set environment variables:

| Environment Variable     | Description                                            |
|--------------------------|--------------------------------------------------------|
| `QUICKBASE_REALM`        | Your Quickbase realm (e.g., `mycompany.quickbase.com`) |
| `QUICKBASE_USER_TOKEN`   | Your Quickbase user token                              |

> **Security note:** Never commit tokens to source control. Use environment variables
> or a secrets manager.

## Usage

### Apps

```python
# Create an app
app_id = client.create_app(
    name="My App",
    description="An example app",
    assign_token=True,
)

# Get app metadata
app = client.get_app(app_id)

# Delete an app
client.delete_app("My App", app_id)
```

### Tables

```python
# Create a table
table_id = client.create_table(app_id, {
    "name": "Customers",
    "description": "Customer records",
})

# List all tables in an app
tables = client.get_tables(app_id)

# Find a table ID by name
table_id = client.get_table_id(app_id, "Customers")

# Delete a table
client.delete_table(app_id, table_id)
```

### Fields

```python
# Create a field
client.create_field(table_id, {
    "label": "Full Name",
    "fieldType": "text",
})

# Create multiple fields at once
results = client.create_fields(table_id, [
    {"label": "Email", "fieldType": "email"},
    {"label": "Age", "fieldType": "numeric"},
    {"label": "Active", "fieldType": "checkbox"},
])
print(f"Created: {len(results['succeeded'])}, Failed: {len(results['failed'])}")

# List all fields in a table
fields = client.get_table_fields(table_id)

# Find a field ID by label
field_id = client.get_field_id(table_id, "Full Name")

# Get a mapping of all field labels to IDs
field_map = client.get_field_label_id_map(table_id)
# {"Full Name": "6", "Email": "7", "Age": "8", "Active": "9"}

# Set a key field (uses legacy API)
client.set_key_field(table_id, field_id)

# Mark a field as required
client.set_required_field(table_id, field_id)
```

### Records

```python
# Upsert (insert or update) records
result = client.upsert_records(table_id, [
    {
        "6": {"value": "Jane Smith"},
        "7": {"value": "jane@example.com"},
        "8": {"value": 32},
    },
    {
        "6": {"value": "Bob Jones"},
        "7": {"value": "bob@example.com"},
        "8": {"value": 45},
    },
])
print(f"Created: {result['created']}, Updated: {result['updated']}")

# Check for partial failures
if result["errored"] > 0:
    print(f"Errors: {result['errored_details']}")

# Query for records
records = client.query_for_data(
    table_id=table_id,
    select=[6, 7, 8],
    where="{8.GT.30}",
    sort_by=[{"fieldId": 6, "order": "ASC"}],
    options={"skip": 0, "top": 100},
)

# Delete records
deleted = client.delete_records(table_id, where="{8.LT.18}")
print(f"Deleted {deleted} records")
```

### Reports

```python
# List all reports for a table
reports = client.get_reports(table_id)

# Get a specific report
report = client.get_report(table_id, report_id="1")

# Run a report
results = client.run_report(table_id, report_id="1", skip=0, top=100)
```

### Solutions

The Solutions API lets you work with app schemas as code using Quickbase's
QBL (YAML-based) format. This is useful for cloning apps, pushing schema
changes between environments, and previewing changes before applying them.

```python
# Get solution metadata and resource information
solution = client.get_solution("solution-id")

# Export a solution's QBL schema definition
qbl = client.export_solution("solution-id")

# Create a new app from a QBL definition
result = client.create_solution(qbl)

# Update an existing solution with a new QBL definition
result = client.update_solution("solution-id", qbl)

# Preview changes without applying them
changeset = client.list_solution_changes("solution-id", qbl)
```

#### Clone an App

Create a copy of an existing app with a new name and optional visual
customizations. The Manager field is automatically stripped since the
API assigns the caller as the new owner.

```python
result, diagnostics = client.clone_solution(
    source_solution_id="source-solution-id",
    name="My App - Copy",
    app_color="#4a90d9",
    app_icon="Briefcase",
)

# Review any issues found during cloning
print(diagnostics.summary())
```

#### Push Schema Changes Between Apps

Push schema changes from one solution to another. The source QBL is
automatically modified to match the target app's name, color, and icon
before being applied.

```python
# Preview what would change (dry run)
changeset = client.push_solution(
    "source-solution-id",
    "target-solution-id",
    preview_only=True,
)

# Apply the changes
result, diagnostics = client.push_solution(
    "source-solution-id",
    "target-solution-id",
)
```

#### QBL Utilities

The `qbl` module provides lower-level utilities for working with QBL
definitions directly:

```python
from quickbase_api.qbl import parse_qbl, serialize_qbl, modify_qbl, extract_app_properties

# Parse QBL while preserving custom tags (!Ref, !BadRef)
data = parse_qbl(qbl)

# Serialize back to a YAML string
yaml_string = serialize_qbl(data)

# Modify app properties in a QBL definition
modified_qbl, diagnostics = modify_qbl(
    qbl,
    name="New App Name",
    app_color="#ff0000",
    app_icon="Calendar",
)

# Extract app name, color, and icon from a QBL definition
props = extract_app_properties(qbl)
# {"name": "My App", "app_color": "#1a73e8", "app_icon": "Briefcase"}
```

## Error Handling

The library raises specific exceptions that you can catch and handle:

```python
from quickbase_api import QuickbaseAPIError, QuickbaseNotFoundError

# Handle API errors
try:
    app = client.get_app("invalid-id")
except QuickbaseAPIError as e:
    print(f"API error {e.status_code}: {e.response_body}")

# Handle not-found lookups
try:
    table_id = client.get_table_id(app_id, "Nonexistent Table")
except QuickbaseNotFoundError as e:
    print(f"Not found: {e}")
```

Exception hierarchy:

```
QuickbaseError              # Base exception
├── QuickbaseAPIError       # HTTP errors from the API
└── QuickbaseNotFoundError  # Resource not found in lookup methods
```

## Logging

This library uses Python's built-in `logging` module. To see log output,
configure logging in your application:

```python
import logging

# See all quickbase-api log messages
logging.basicConfig(level=logging.INFO)

# Or configure just the quickbase_api logger
logging.getLogger("quickbase_api").setLevel(logging.DEBUG)
```

## Configuration

### Retry Behavior

The library automatically retries failed requests up to 5 times with
exponential backoff for the following HTTP status codes:

- `429` — Rate limited
- `502` — Bad gateway
- `503` — Service unavailable
- `504` — Gateway timeout

## Requirements

- Python 3.9+
- [requests](https://requests.readthedocs.io/) >= 2.25.0
- [ruamel.yaml](https://yaml.readthedocs.io/) >= 0.19.1

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
