Metadata-Version: 2.4
Name: seyon
Version: 0.1.4
Summary: YAML-based API mocking server
Author: Prem Raj
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml
Requires-Dist: psycopg2-binary
Requires-Dist: fastapi
Requires-Dist: uvicorn
Dynamic: license-file

# seyon

A YAML-driven API mocking tool that parses schema definitions from YAML files, validates them, generates databases (SQLite or PostgreSQL) with mock data, and serves a dynamic REST API — all from a single YAML spec.

## Features

- Define your database schema (tables, columns, types, constraints) in YAML
- Validate the YAML schema for correctness
- Generate **SQLite** (local `.db` file) or **PostgreSQL** (remote server) databases
- Populate tables with realistic mock data (random phone numbers, emails, UUIDs, etc.)
- Serve a **FastAPI REST server** with auto-generated CRUD endpoints per model
- **Pagination**, **filtering**, **sorting** on all list endpoints
- **Relation resolution** — foreign keys auto-resolve to full referenced objects
- Handle `PRIMARY KEY`, `UNIQUE`, `NOT NULL`, `AUTOINCREMENT`/`SERIAL`, and `max-length` constraints automatically
- Support for Neon.tech serverless PostgreSQL (auto-resolves hostnames via Google DNS)

## Project Structure

```
seyon/
├── src/
│   ├── seyon/
│   │   └── main.py              # CLI entry point (start, generate, validate, serve)
│   ├── database/
│   │   ├── base.py              # DatabaseAbstract — abstract base class
│   │   ├── factory.py           # DataFactory — selects DB generator by type
│   │   ├── sqlite_generator.py  # SQLiteGenerator — table creation & data insertion
│   │   └── postgress_generator.py  # PostgressGenerator — PostgreSQL implementation
│   ├── parser/
│   │   └── yaml_load.py         # load_yaml — reads and parses YAML files
│   ├── schema/
│   │   ├── constants.py         # Elements — supported types & allowed properties
│   │   ├── field.py             # Field — column definition & mock value generation
│   │   ├── model.py             # Model — table definition & validation
│   │   └── project.py           # Project — top-level spec & validation
│   ├── server/
│   │   ├── app.py               # create_app(project) — FastAPI factory
│   │   ├── router_builder.py    # Dynamic CRUD route registration per model
│   │   └── response_builder.py  # Delegates to DB generator, resolves relations
│   └── util/
│       ├── config.py            # Settings — paths, DB name, defaults
│       ├── exeception.py        # ProjectException — structured error reporting
│       └── logging.py           # Logging — file-based timestamped logs
├── employee.yaml                # Example single-model YAML spec
├── test_multi.yaml              # Example multi-model YAML spec
├── pyproject.toml               # Package metadata & build config
├── requirements.txt             # Python dependencies
├── skills/
│   └── skill.md                 # IDE skill for spec authoring
└── README.md
```

## Installation

### Prerequisites

- Python 3.10 or higher
- pip
- (Optional) PostgreSQL server or Neon.tech account for PSQL backend

### Steps

1. **Clone the repository**

   ```bash
   git clone <repo-url>
   cd seyon
   ```

2. **Create a virtual environment (recommended)**

   ```bash
   python -m venv .venv
   .venv\Scripts\activate    # Windows
   # source .venv/bin/activate  # Linux/macOS
   ```

3. **Install the package**

   ```bash
   pip install -e .
   ```

   This installs the `seyon` CLI command and dependencies (`pyyaml`, `psycopg2-binary`).

## Usage

### CLI Commands

```bash
seyon <command> --file <yaml-file>
```

| Command    | Description                                         |
|------------|-----------------------------------------------------|
| `validate` | Load and validate the YAML schema                  |
| `generate` | Validate, create tables, and populate with mock data |
| `start`    | Validate and create tables only                    |
| `serve`    | Validate, create tables, and start the REST API server |

### Examples

```bash
# Validate a YAML file
seyon validate --file employee.yaml

# Generate SQLite database with mock data
seyon generate --file employee.yaml

# Generate PostgreSQL database with mock data
seyon generate --file test_multi.yaml

# Start the REST API server (FastAPI)
seyon serve --file test_multi.yaml --port 8000
```

**SQLite**: generates `mock.db` in the current directory.
**PostgreSQL**: connects to the remote server specified in the `database:` section.

## Server API

When you run `seyon serve`, a FastAPI server starts with auto-generated CRUD endpoints for each model in the spec.

| Method   | Endpoint                 | Description              |
|----------|--------------------------|--------------------------|
| `GET`    | `/`                      | Project info + model list |
| `GET`    | `/{model_name}`          | List rows (paginated)    |
| `GET`    | `/{model_name}/{id}`     | Get row by primary key   |
| `POST`   | `/{model_name}`          | Create a new row         |
| `PUT`    | `/{model_name}/{id}`     | Update row by primary key |
| `DELETE` | `/{model_name}/{id}`     | Delete row by primary key |

Interactive API docs at `http://localhost:8000/docs` (Swagger UI).

### Pagination

`GET /{model_name}` returns a paginated envelope:

```json
{ "total": 50, "skip": 0, "limit": 100, "data": [...] }
```

| Param   | Default | Description                     |
|---------|---------|---------------------------------|
| `skip`  | `0`     | Number of rows to skip          |
| `limit` | `100`   | Max rows to return (max 1000)   |

Example: `GET /employee?skip=10&limit=20`

### Filtering

Any query param matching a field name becomes an equality filter. Supports operators via `__` suffix:

| Operator       | Example                              |
|----------------|--------------------------------------|
| `eq` (default) | `?name=John` or `?name__eq=John`     |
| `ne`           | `?name__ne=John`                     |
| `lt`           | `?age__lt=30`                        |
| `gt`           | `?age__gt=18`                        |
| `lte`          | `?age__lte=65`                       |
| `gte`          | `?age__gte=21`                       |
| `contains`     | `?name__contains=oh`                 |
| `startswith`   | `?name__startswith=J`                |
| `endswith`     | `?name__endswith=n`                  |

Example: `GET /employee?department_id=1&age__gt=25`

### Sorting

| Param       | Default | Description               |
|-------------|---------|---------------------------|
| `sort_by`   | —       | Field name to sort by     |
| `sort_order`| `asc`   | `asc` or `desc`           |

Example: `GET /employee?sort_by=name&sort_order=desc`

### Relations

Fields with `type: relation` and `references:` are auto-resolved to full objects in responses:

```yaml
models:
  department:
    fields:
      id: { type: integer, primarykey: true }
      name: { type: string }
  employee:
    fields:
      id: { type: integer, primarykey: true }
      department_id: { type: relation, references: department }
```

Response to `GET /employee/1`:
```json
{
  "id": 1,
  "department_id": { "id": 1, "name": "Engineering" }
}
```

Use `?resolve_relations=false` to get raw foreign key integers.

## YAML Schema Reference

### Database Backend Configuration

```yaml
database:
  type: sqlite | psql | postgresql   # required — backend selection

  # PostgreSQL-only fields (ignored for SQLite):
  db_host: localhost                  # default: localhost
  db_port: 5432                       # default: 5432
  db_name: mydb                       # default: mock
  db_username: user                   # default: root
  db_password: pass                   # default: root
  db_sslmode: require                 # optional — e.g. require, disable, verify-full
  db_options: endpoint=my-endpoint    # optional — extra connection parameters
```

> **Neon.tech**: Hostnames ending with `.neon.tech` are auto-resolved via Google DNS (8.8.8.8). The endpoint ID is extracted from the hostname and passed as a connection option automatically.

### Supported Data Types

| Type       | Description            | Generated Value                     |
|------------|------------------------|-------------------------------------|
| integer    | Whole number           | `1` (sequential for primary keys)   |
| string     | Text                   | `"example"`                         |
| boolean    | True/false             | `True`                              |
| float      | Decimal number         | `1.0`                               |
| double     | Double-precision float | `1.0`                               |
| bigint     | Large integer          | `1234567890123456789`               |
| phone      | Phone number           | Random 10-digit Indian mobile       |
| email      | Email address          | Random `username@example.com`       |
| date       | Calendar date          | Current date                        |
| datetime   | Date and time          | Current timestamp                   |
| time       | Time of day            | Current time                        |
| text       | Long text              | Lorem ipsum placeholder             |
| uuid       | UUID v4                | Random UUID string                  |
| url        | Web URL                | `https://example.com`               |
| json       | JSON object            | `{"key": "value"}`                  |
| relation   | Foreign key to another model | Random valid PK from referenced model |

### PostgreSQL Type Mapping

When using `type: psql`, YAML types are mapped to PostgreSQL column types:

| YAML type   | PostgreSQL type     |
|-------------|---------------------|
| integer     | INTEGER             |
| bigint      | BIGINT              |
| float       | REAL                |
| double      | DOUBLE PRECISION    |
| string      | VARCHAR(255)        |
| text        | TEXT                |
| boolean     | BOOLEAN             |
| date        | DATE                |
| datetime    | TIMESTAMP           |
| time        | TIME                |
| email       | VARCHAR(255)        |
| phone       | VARCHAR(20)         |
| password    | VARCHAR(255)        |
| url         | VARCHAR(500)        |
| uuid        | UUID                |
| json        | JSONB               |
| relation    | INTEGER             |

### Constraints

- **`primarykey`**: For integer primary keys, values autoincrement from 1.
- **`unique`**: Phone fields use sequential offset, email fields use indexed username, other string types get a `_N` suffix.
- **`required`**: Adds `NOT NULL` constraint to the column.
- **`max-length`**: Optional positive integer. For `string`, `email`, `password`, `url`, `phone`, and `text` types, sets `VARCHAR(n)` column length in DDL and limits generated mock values. Example: `max-length: 100`.
- **`autoincrement`**: Optional boolean. Only for `integer` type. Uses `AUTOINCREMENT` in SQLite and `SERIAL` in PostgreSQL. The field is omitted from INSERT statements so the DB assigns the value.

### Example: SQLite (Single Model)

```yaml
project: employee-management
database:
  type: sqlite

models:
  employee:
    count: 100
    fields:
      id:
        type: integer
        primarykey: true
      username:
        type: string
        unique: true
      email:
        type: email
      mobile:
        type: phone
```

### Example: PostgreSQL (Multiple Models)

```yaml
project: company-management
database:
  type: psql
  db_host: your-instance.region.neon.tech
  db_port: 5432
  db_name: neondb
  db_username: neondb_owner
  db_password: your-password
  db_sslmode: require

models:
  employee:
    count: 50
    fields:
      id:
        type: integer
        primarykey: true
      name:
        type: string
      email:
        type: email
      mobile:
        type: phone
      department_id:
        type: relation
        references: department

  department:
    count: 10
    fields:
      id:
        type: integer
        primarykey: true
      name:
        type: string
        unique: true
      head_count:
        type: integer

  project:
    count: 30
    fields:
      id:
        type: integer
        primarykey: true
      title:
        type: string
        unique: true
      budget:
        type: float
      start_date:
        type: date
```

## Data Flow

```
YAML File
    │
    ▼
parser/yaml_load.py  ──►  dict
    │
    ▼
schema/       ──►  Project (validated) ──►  Model ──►  Field
    │
    ├── validate / generate ──────────────────►  database/factory.py
    │                                               │
    │                        ├── sqlite ────────────┤───►  SQLiteGenerator
    │                        │                      │         │
    │                        └── psql ──────────────┘───►  PostgressGenerator
    │                                                          │
    │                                          mock.db ◄───────┘
    │                                       PostgreSQL ◄───────┘
    │
    └── serve ──►  server/app.py (create_app)
                        │
                        ▼
              server/router_builder.py
                        │
                        ▼
              FastAPI routes per model
                        │
                        ▼
              server/response_builder.py
                        │
                        ▼
              database/generator CRUD methods
```

## Logging

Logs are written to `logs/<DD_MM_YYYY_HH>/<DD_MM_YYYY_HH>.log` with timestamps and log levels.

## Development

### Running from source

```bash
python -m seyon.main generate --file employee.yaml
```

### Adding a new database backend

1. Create a new generator class in `src/database/` implementing `DatabaseAbstract`
2. Implement `connect()`, `close()`, `generate_table()`, `generate_data()`, `get_all_data()`, `get_by_id()`, `insert()`, `update()`, `delete()`
3. Register it in `src/database/factory.py`
4. Add the dependency to `pyproject.toml` and `requirements.txt`

### Adding a new data type

1. Add the type name to `Elements.SUPPORTED_DATA_TYPES` in `src/schema/constants.py`
2. Add a `generate_value` branch in `src/schema/field.py`
3. If the type can be `unique`, update uniqueness handling in all generators
4. For PostgreSQL, add the type to `TYPE_MAP` in `postgress_generator.py`

## License

MIT
