Metadata-Version: 2.4
Name: quickbase-api
Version: 0.5.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.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Requires-Dist: requests>=2.25.0
Requires-Dist: ruamel-yaml>=0.19.1
Requires-Dist: xmltodict>=1.0.4
Provides-Extra: dev
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=9.0.3; extra == 'dev'
Requires-Dist: responses>=0.20; extra == 'dev'
Requires-Dist: ruff>=0.15.11; 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(app_id, app_name="My App")
```

### Tables

```python
# Create a table
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, table_name="Customers")

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

### Fields

```python
# Create a field
client.create_field(table_id, label="Full Name", field_type="text")

# Create multiple fields at once
results = client.create_fields(table_id, fields=[
    {"label": "Email", "field_type": "email"},
    {"label": "Age", "field_type": "numeric"},
    {"label": "Active", "field_type": "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, field_label="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=field_id)

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

### Records

```python
# Upsert (insert or update) records
result = client.upsert_records(table_id, data=[
    {
        "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 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)
```

#### Push Schema Changes Between Apps

Use `push_solution()` to push schema changes between environments:

```python
from quickbase_api import QuickbaseClient

client = QuickbaseClient("realm.quickbase.com", token)

# Preview changes without applying them
changes = client.push_solution(
    "sol-source",
    "sol-target",
    preview_only=True,
)
print(changes)  # {"changes": ["add field", "modify table"]}

# Apply the push and get diagnostics
result, diagnostics = client.push_solution("sol-source", "sol-target")
print(diagnostics.summary())
# Stripped Manager: owner@example.com
# No other issues found.
```

The push automatically:

- Exports both source and target solutions
- Merges source schema with target identity (logical ID, ParameterDefinitions)
- Removes the Manager field to prevent API errors
- Returns diagnostics with any warnings or broken references

#### QBL Utilities

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

```python
from quickbase_api.qbl import (
    parse_qbl,
    serialize_qbl,
    merge_qbl_for_update,
    collect_bad_refs,
)

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

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

# Collect all !BadRef entries (broken references)
bad_refs = collect_bad_refs(data)
for path, value in bad_refs:
    print(f"Broken reference at {path}: {value}")

# Merge source QBL schema into target environment
# Preserves: target's logical ID, ParameterDefinitions
# Removes: Manager field (prevents API errors)
merged_qbl, diagnostics = merge_qbl_for_update(source_qbl, target_qbl)

# Check diagnostics
if diagnostics.has_bad_refs:
    print(f"Warning: {len(diagnostics.bad_refs)} broken references found")

print(diagnostics.summary())
# Output:
# Stripped Manager: owner@example.com
# Found 1 !BadRef entries:
#   Resources.$App_Tasks.Properties.Fields[2].LookupRef: !BadRef $Field_Deleted_User
```

## 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, table_name="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:

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

## Requirements

- Python 3.12+
- [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.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for release history.
