Metadata-Version: 2.4
Name: docges-api-py
Version: 0.1.8
Summary: Async, typed Python client for DocGes REST API
Author-email: "ct.galega" <soporte@ctgalega.com>
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: respx>=0.22; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Dynamic: license-file

# docges-api-py

Async, typed Python client for the DocGes REST API bridge (`docges-ctg-api`).

> **Production-ready** — bootstrapped via SDD (Spec-Driven Development).
> All core endpoints implemented with full test coverage (pytest + respx).

## Installation

```bash
pip install docges-api-py
```

For development with testing tools:

```bash
pip install -e ".[dev]"
```

## Features

- ✅ Async HTTP client using `httpx`
- ✅ Typed request/response models with Pydantic v2
- ✅ Configurable retries (exponential backoff) and timeouts
- ✅ Session-based auth: stores `loginResponse` from `/Auth/Login` and auto-injects it
  for all Content endpoints
- ✅ Full test coverage (pytest + respx)
- ✅ Complete DocGes API client: categories, documents, search, CRUD operations

## Implemented endpoints

### Auth
| Method | Endpoint | Description |
|--------|----------|-------------|
| `auth()` | `POST /Auth/Login` | Authenticate and store session |

### Content Tree
| Method | Endpoint | Description |
|--------|----------|-------------|
| `get_initial_cats()` | `POST /Content/LoadIniCats` | Load root categories for user's warehouse |
| `get_cat_info(sid)` | `POST /Content/LoadChilds` | Load subcategories and documents for a given category SID |

### Document Operations
| Method | Endpoint | Description |
|--------|----------|-------------|
| `upload_file(category_sid, file_path)` | `POST /Content/UploadExtended` | Upload a file to a category |
| `download_content(sid)` | `POST /Content/Download` | Download a document (returns base64 data) |
| `get_document(sid)` | — | Deprecated wrapper for `download_content()` |

### Search
| Method | Endpoint | Description |
|--------|----------|-------------|
| `search_content(field_values, date_values)` | `POST /Content/SearchContent` | Search documents by field or date criteria |

### Category Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| `create_category(parent_sid, name)` | `POST /Content/CreateCategory` | Create a subcategory |
| `rename_category(sid, new_name)` | `POST /Content/RenameCategory` | Rename a category |
| `delete_category(sid)` | `POST /Content/DeleteCategory` | Delete a category |
| `check_dir_struct(root_sid, path_names, create_if_not_exist, full_info)` | — | Check or create nested directory structure (full_info=False returns only last dir) |

### Document Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| `remove_document(sid)` | `POST /Content/RemoveDocument` | Remove a single document |
| `move_content(content_sid, destination_sid)` | `POST /Content/Move` | Move a document to another category |
| `update_content(sid, erp_relation, erp_property)` | `POST /Content/Update` | Update document ERP metadata |

## Quick start

```bash
pip install -e ".[dev]"
```

```python
import asyncio
import os
from docges_api_py import AsyncDocGes

async def main():
    base_url = os.getenv("DOCGES_API_BASE_URL")
    user     = os.getenv("DOCGES_USER")
    pwd      = os.getenv("DOCGES_PASSWORD")
    appid    = os.getenv("DOCGES_APP_ID")

    async with AsyncDocGes(base_url, user, pwd, appid) as client:
        auth_resp = await client.auth()
        print(f"User: {auth_resp.user_info['Nombre']}")

        # Browse root categories
        cats = await client.get_initial_cats()
        for cat in cats.CategoriasHijas or []:
            print(f"  {cat.Nombre}  [SID: {cat.SID}]")

        # Search documents
        results = await client.search_content(
            field_values=[{"field": "name", "mode": "contains", "text": "factura"}]
        )
        print(f"Search results: {len(results.results)}")

asyncio.run(main())
```

### Environment variables

| Variable | Description | Example |
|----------|-------------|---------|
| `DOCGES_API_BASE_URL` | Bridge base URL | `http://192.168.0.90:49375` |
| `DOCGES_USER` | Username | `administrador` |
| `DOCGES_PASSWORD` | Password | `12345` |
| `DOCGES_APP_ID` | App ID (maps to server config in `clients.xml`) | `local-dev` |
| `DOCGES_ROOT_SID` | Root category SID for directory operations | `1ddbfd1b-5d33-44af-b392-...` |

**Known appIDs:**
- `local-dev` — local development
- `pre-dev` — pre-production
- `pro-*` — production servers

## Examples

All examples read credentials from environment variables. Run them with:

```bash
export DOCGES_API_BASE_URL=http://192.168.0.90:49375
export DOCGES_USER=administrador
export DOCGES_PASSWORD=12345
export DOCGES_APP_ID=local-dev
python examples/<number>_<name>.py
```

| # | File | What it does |
|---|------|-------------|
| 01 | `examples/01_auth.py` | Login and inspect session context |
| 02 | `examples/02_initial_cats.py` | Load and list root categories |
| 03 | `examples/03_browse_category.py` | Browse a category by SID (subcats + docs) |
| 04 | `examples/04_search.py` | Search documents by name |
| 05 | `examples/05_upload.py` | Upload a file to a category |
| 06 | `examples/06_download.py` | Download a document by SID |
| 07 | `examples/07_category_crud.py` | Create, rename, and delete a category |
| 08 | `examples/08_document_operations.py` | Update ERP metadata, move, and remove a document |
| 09 | `examples/09_dir_structure.py` | Check or create nested directory structure |

## Development

```bash
pip install -e ".[dev]"
ruff check src/ tests/
mypy src/
pytest --cov=docges_api_py
```

## Architecture notes

- The DocGes REST API is a thin bridge in front of DocGes WCF services. It uses session-based auth: `/Auth/Login` returns a `loginResponse` (containing `userInfo` and `wareHouseInfo`) that must be passed back on every Content request via `requestModel.loginResponse`.
- Content endpoints expect `contenidoModel`, `fileModel`, etc. as **top-level fields** alongside `requestModel`, not nested inside it. The client builds the correct payload shape automatically.
- For DocGes Web (bridge) compatibility, the client also includes `loginDocGesData` (username/password/appID) in `requestModel` — this is needed by some bridge deployments that reconstruct the DocGes login model server-side.
- All file content is base64-encoded in transit (handled automatically by the client).

## Project structure

```
docges-api-py/
├── src/docges_api_py/
│   ├── __init__.py            # Exports AsyncDocGes, models
│   ├── client.py              # AsyncDocGes client (all endpoints)
│   └── models/
│       ├── __init__.py        # Re-exports all models
│       ├── auth.py            # AuthRequest, AuthResponse
│       ├── document.py        # Document
│       ├── requests.py        # Request models (Pydantic v2)
│       └── responses.py       # Response models (ResCat, ResFileDownload, etc.)
├── tests/
│   ├── conftest.py
│   ├── test_client_auth.py
│   ├── test_initial_cats.py
│   ├── test_content_methods.py
│   ├── test_remaining_methods.py
│   ├── test_check_dir_struct.py
│   ├── test_models_requests.py
│   ├── test_models_responses.py
│   ├── test_models_init.py
│   ├── test_endpoints.py
│   ├── test_retry.py
│   ├── test_token_refresh.py
│   └── test_examples.py
├── examples/
│   ├── 01_auth.py
│   ├── 02_initial_cats.py
│   ├── 03_browse_category.py
│   ├── 04_search.py
│   ├── 05_upload.py
│   ├── 06_download.py
│   ├── 07_category_crud.py
│   ├── 08_document_operations.py
│   └── 09_dir_structure.py
├── docs/
│   └── MIGRATION.md
├── scripts/
│   └── docges_debug/          # Debug helpers for payload investigation
├── pyproject.toml
├── ruff.toml
├── mypy.ini
└── README.md
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
