Metadata-Version: 2.4
Name: noco-client
Version: 0.1.0
Summary: Small unofficial Python REST client for NocoDB table records
Keywords: nocodb,rest,client,api
Author: Wenjun Mao
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Dist: requests>=2.32.4
Requires-Dist: tenacity>=8.5.0
Requires-Dist: pydantic-settings>=2.8.1
Requires-Dist: secret-backed-settings>=0.1.0
Requires-Python: >=3.12
Project-URL: Repository, https://github.com/Wenjun-Mao/ContentShuttle
Project-URL: Issues, https://github.com/Wenjun-Mao/ContentShuttle/issues
Project-URL: Changelog, https://github.com/Wenjun-Mao/ContentShuttle/blob/main/packages/noco-client/CHANGELOG.md
Description-Content-Type: text/markdown

# noco-client

Small unofficial Python REST client for NocoDB table-record operations and
minimal table metadata provisioning.

This package is intentionally focused on scraper pipeline needs: listing records,
creating records, updating records, chunked bulk inserts, disposable table
creation/deletion for activation tests, retry handling, and structured errors.

It is not an official NocoDB package and does not depend on third-party NocoDB
client libraries.

## Install

```bash
uv add noco-client
```

or:

```bash
pip install noco-client
```

## Basic Usage

```python
from noco_client import NocoDBClient, eq


client = NocoDBClient(
    base_url="https://nocodb.example.com",
    api_token="replace-with-token",
)

articles = client.table("articles_table_id")
records = articles.list(where=eq("uid", "article-1"), limit=1)
```

Canonical table methods are `list`, `iter_records`, `insert`, `update`,
`delete`, and `bulk_insert`. Compatibility aliases such as `get_records`,
`insert_record`, `update_record`, `delete_records`, and `bulk_insert_records`
remain available because existing scraper sinks use them.

## Read-Only Discovery

Use the client-level discovery helpers when an activation audit needs to resolve
base/table names from IDs without mutating NocoDB:

```python
bases = client.list_bases()
table = client.find_table("table_id")
matches = client.find_tables_by_title("Articles")

if table:
    print(table["base_title"], table["table_title"])
```

These helpers are read-only and use NocoDB v2 metadata:

- `GET /api/v2/meta/bases`
- `GET /api/v2/meta/bases/{base_id}/tables`

They return dictionaries that include `base_id`, `base_title`, `table_id`,
`table_title`, plus the raw `base` and `table` metadata payloads. They do not
create/delete bases or tables.

## Metadata API Boundary

There are two table record clients, and the distinction matters:

- `client.table(table_id)` is the existing v2 data client:
  `/api/v2/tables/{table_id}/records`. This path remains unchanged for current
  production sinks.
- `client.base(base_id).table(table_id)` is the v3 data client:
  `/api/v3/data/{base_id}/{table_id}/records`. Use this for tables created
  through the v3 metadata API because v3 data wraps caller fields in a
  `{"fields": ...}` payload.

The metadata surface is intentionally small and exists only to create and clean
up disposable tables inside an existing test base:

```python
base = client.base("base_id")
table = base.create_table(
    "cs_activation_test",
    fields=[
        {"title": "uid", "type": "SingleLineText"},
        {"title": "title", "type": "SingleLineText"},
        {"title": "content", "type": "LongText"},
        {"title": "image", "type": "URL"},
        {"title": "comments_count", "type": "Number"},
    ],
)

try:
    base.table(table["id"]).insert_record({"uid": "article-1"})
finally:
    base.delete_table(table["id"])
```

Supported metadata methods:

- `client.base(base_id)`
- `base.list_tables()`
- `base.table(table_id)`
- `base.create_table(title, fields=None, description=None, source_id=None)`
- `base.delete_table(table_id)`

The v3 table client keeps the same flat-record caller shape as the v2 table
client. It translates inserts and bulk inserts to `{"fields": record}` and
flattens v3 responses like `{"id": 1, "fields": {"uid": "a"}}` back to
`{"Id": 1, "uid": "a"}`.

The v2 table client remains the default production API and keeps its existing
100-record bulk chunk size. The v3 table client is explicit opt-in and chunks
bulk creates at 10 records. That v3 limit is a live-observed constraint from
our self-hosted NocoDB returning `422: Maximum 10 entities are allowed per
request`; public docs/OpenAPI do not clearly publish that limit for the plain
create-record endpoint.

This package does not create/delete bases, manage roles, views, hooks,
permissions, or broad NocoDB administration. The implemented metadata endpoints
are NocoDB v3:

- `GET /api/v3/meta/bases/{base_id}/tables`
- `POST /api/v3/meta/bases/{base_id}/tables`
- `DELETE /api/v3/meta/bases/{baseId}/tables/{tableId}`

NocoDB's v3 create-table schema calls table columns `fields`. Optional
`source_id` is passed only when the target base requires a specific data source.

## Settings

`NocoDBClientSettings` uses `secret-backed-settings` for environment variables
and local or Docker file secrets. Environment variables use the caller-provided
prefix, while file secret names are unprefixed field names such as `api_token`
and `base_url`.

```python
from noco_client import NocoDBClient, NocoDBClientSettings


settings = NocoDBClientSettings(_env_prefix="NOCODB_")
client = NocoDBClient(
    base_url=settings.base_url,
    api_token=settings.api_token,
    timeout=settings.timeout,
    min_request_interval=settings.min_request_interval,
)
```

Example environment variables:

```dotenv
NOCODB_BASE_URL=https://nocodb.example.com
NOCODB_API_TOKEN=replace-with-token
```

## Live Integration Tests

Unit tests do not require a NocoDB instance. Live integration tests are opt-in
and target a disposable test-only NocoDB base/table:

- Test base ID: `p8sfkf45poyrbxu`
- Test base title: `4TestOnly`
- Test table ID: `mnn0b32e2obhsyv`
- Test table title: `Table-1`
- Default title field: `Title`

The v2 record test inserts a unique single record, bulk-inserts 101 records to
exercise existing v2 chunking, verifies filtered reads and pagination, then
deletes the records it created.

The metadata tests include one read-only discovery check for the known test
base/table and one disposable-table lifecycle test. The lifecycle test creates a
uniquely named disposable table in the test base, inserts and reads one record
through the returned table ID, bulk-inserts 101 records through the v3 table
client to prove safe 10-record chunking, and deletes the table in cleanup. It
refuses to run outside the known test base unless `NOCO_TEST_ALLOW_ANY_BASE=1`
is set.

Required environment:

```powershell
$env:NOCO_CLIENT_INTEGRATION = "1"
$env:NOCO_TEST_BASE_URL = "https://your-nocodb-host.example.com"
$env:NOCO_TEST_API_TOKEN = "replace-with-your-token"
```

`NOCO_TEST_API_TOKEN` can be omitted when `NOCODB_API_KEY` is present in the
environment or local `.env`. The record test uses the table ID; override it with
`NOCO_TEST_TABLE_ID` when needed.

Additional metadata test environment:

```powershell
$env:NOCO_CLIENT_META_INTEGRATION = "1"
```

Optional metadata overrides:

- `NOCO_TEST_BASE_ID`: defaults to `p8sfkf45poyrbxu`
- `NOCO_TEST_SOURCE_ID`: only needed when the base requires a data source ID
- `NOCO_TEST_ALLOW_ANY_BASE=1`: allows destructive disposable-table tests
  outside the known test base

Run only the live tests:

```powershell
uv run pytest packages/noco-client/tests/integration -q
```

Repository-local note: this monorepo also contains downstream sink integration
tests that use the same live-test guards to create disposable article-only
tables. Those tests are not part of the public `noco-client` API.
