Metadata-Version: 2.4
Name: pycodb
Version: 0.2.0
Summary: A simple module for working with NocoDB
Author: Helpmap
Author-email: helpmap.online@gmail.com
Requires-Python: >=3.6
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
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: Programming Language :: Python :: 3.14
Requires-Dist: requests (>=2.25.0)
Description-Content-Type: text/markdown

# PycoDB

Python client for [NocoDB](https://nocodb.com/) with a Django ORM–style layer.

## Quick start

```python
from pycodb import DataBase, BaseTable, RepositoryManager

DataBase.DOMAIN = 'your-host:8080'
DataBase.NOCODB_API_KEY = 'your-token'

class AppData(DataBase):
    class Users(BaseTable):
        view_id = '...'
        table_id = '...'

class Users(RepositoryManager):
    data_source = AppData

repo = Users()
for row in repo.objects.filter(status='active').order_by('-CreatedAt')[:50]:
    print(row.name)
```

## Layers

| Layer | Classes | Role |
|-------|---------|------|
| HTTP | `NocoClient` | Transport, errors (`NocoAPIError`) |
| Table API | `api/`, `BaseTable` | NocoDB v2/v3 records CRUD, links, upload |
| Query | `query/` | Lookups, `Q`, `build_where`, nested params |
| Gateway | `DataBase` | Per-table access, `DataProxy` compatibility |
| ORM-like | `Manager`, `QuerySet`, `Model` | Lazy queries, Django-style API |
| Meta | `MetaClient` | Bases, tables, columns, link fields |

### Package layout (`query/`)

```
pycodb/query/
  operators/lookups.py   # __gt, __in, escape_value, build_where
  expressions.py       # Q (& | ~)
  nested.py            # prefetch_related params
```

Legacy imports `from pycodb.where import Q` still work.

## QuerySet (0.2+)

`objects.filter()` returns a lazy `QuerySet` (not `DataProxy`). Evaluate with `list()`, `for`, or `get()`:

```python
qs = repo.objects.filter(campaign='x').order_by('-Id')
qs.count()
qs.first()
qs.get(Id=1)  # raises DoesNotExist / MultipleObjectsReturned
```

## Model (0.3+)

```python
from pycodb import Model, RepositoryManager

class Banner(Model):
    class Meta:
        pk = 'Id'

class Banners(RepositoryManager):
    model = Banner
    data_source = AppData

banner = Banners.objects.create(title='Hello')
banner.title = 'Updated'
banner.save(update_fields=['title'])
banner.refresh_from_db()
banner.delete()

obj, created = Banners.objects.get_or_create(slug='x', defaults={'title': 'Hi'})
Banners.objects.filter(status='live').values_list('title', flat=True)
mapping = Banners.objects.in_bulk([1, 2, 3])
```

### QuerySet extras

| Method | Description |
|--------|-------------|
| `get_or_create` / `update_or_create` | Django-style upsert |
| `bulk_create` | Insert many rows |
| `values_list` | Tuples or `flat=True` |
| `only` | Limit returned columns (+ pk) |
| `in_bulk` | Dict keyed by pk |
| `earliest` / `latest` | First row by sort |
| `Q` | `Q(a=1) \| Q(b=2)` composable filters |

## API v3 (0.4+)

```python
DataBase.API_VERSION = 'v3'
DataBase.BASE_ID = 'p_your_base_id'

class AppData(DataBase):
    class Tasks(BaseTable):
        base_id = 'p_your_base_id'  # optional per-table override
        view_id = '...'
        table_id = 'm...'
```

v3 responses are normalized to the same `{Id, ...}` shape as v2.

## Meta API

```python
meta = DataBase.meta()
bases = meta.list_bases()
tables = meta.list_tables('p_base_id')
columns = meta.get_table_columns('m_table_id')
links = meta.find_link_fields('m_table_id')  # linkFieldId for link APIs
```

## Links & nested

```python
# Expand linked data in list (v2 nested[] / v3 nestedPage)
qs = repo.objects.filter(status='open').prefetch_related({
    'cl_link_field_id': ['Title', 'Status'],
})

# Per-record linked rows
banner.get_linked('cl_link_field_id', fields=['Title'])
banner.link('cl_link_field_id', 10, 11)
banner.unlink('cl_link_field_id', 10)
```

## Configuration

| Attribute | Default | Description |
|-----------|---------|-------------|
| `DataBase.DOMAIN` | — | Host or full URL |
| `DataBase.NOCODB_API_KEY` | — | API token (`xc-token`) |
| `DataBase.SCHEME` | `http` | `http` or `https` |
| `DataBase.TIMEOUT` | `30` | Request timeout (seconds) |
| `DataBase.API_VERSION` | `v2` | `v2` or `v3` |
| `DataBase.BASE_ID` | — | Required for v3 data API |

## Tests

```bash
python3 -m unittest discover -s tests -v
```

