Metadata-Version: 2.4
Name: qalib-cli
Version: 0.1.0
Summary: Scaffold data-driven apps from declarative YAML DocTypes - Frappe-inspired, MySQL-backed, with a cute TUI.
Project-URL: Homepage, https://github.com/muthanii/qalib
Project-URL: Repository, https://github.com/muthanii/qalib
Project-URL: Issues, https://github.com/muthanii/qalib/issues
Author: qalib contributors
License-Expression: MIT
License-File: LICENSE
Keywords: codegen,doctype,frappe,mysql,scaffolding,textual,tui,yaml
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.11
Requires-Dist: alembic>=1.13
Requires-Dist: fastapi>=0.111
Requires-Dist: jinja2>=3.1
Requires-Dist: pydantic>=2.7
Requires-Dist: pymysql>=1.1
Requires-Dist: rich>=13.7
Requires-Dist: ruamel-yaml>=0.18
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: typer>=0.12
Requires-Dist: uvicorn>=0.30
Requires-Dist: watchdog>=4.0
Provides-Extra: fast
Requires-Dist: mysqlclient>=2.2; extra == 'fast'
Description-Content-Type: text/markdown

# qalib

> قالب — Arabic for "mold / template"

**qalib scaffolds data-driven applications from declarative YAML files.**

You describe your entities — *DocTypes*, in the Frappe Framework sense — as plain YAML
files in your project. qalib turns them into typed SQLAlchemy models, Pydantic schemas,
Alembic migrations and a live MySQL schema. The YAML is the single source of truth;
everything else is a derived artifact you never hand-edit.

Type `qalib` and a **web desk** opens in your browser — Streamlit-style. It ships inside
the Python package (no Node, no npm, no build step): a calm, Frappe-Desk-inspired UI with
list views, filters, metadata-driven forms, inline child tables and English/Arabic
switching, all rendered live from your YAML. Edit a YAML file, refresh the browser, and
the desk follows.

The terminal side stays minimal on purpose — like the best modern CLIs: `qalib serve`
prints a quiet status banner (project, URL, what it's watching) and gets out of your
way, with a small ASCII cat that celebrates clean syncs (`[cli] mascot = false` turns it
off). VS Code is a first-class citizen: `qalib init` wires JSON-Schema autocomplete and
inline validation for every DocType YAML file.

## The core loop

```
  desk / CLI ──creates/edits──▶  modules/*/doctypes/*.yaml ──edited in VS Code──┐
       ▲                              │                                   │
       │                              ▼                                   │
       └────────reads back───  qalib sync  ──▶  generated Python models    │
                                     │      ──▶  Alembic migration         │
                                     │      ──▶  MySQL schema              │
                                     └───────────────watch mode ◀──────────┘
```

`qalib sync` is idempotent: running it twice on an unchanged tree produces zero diffs and
zero migrations.

## Install

```bash
uv add qalib-cli                # as a project dependency
uvx --from qalib-cli qalib      # run without installing
pip install qalib-cli           # classic
```

> The package installs as `qalib-cli` (the bare `qalib` name is squatted on PyPI),
> but the command and the import are plain `qalib`.

Requires Python 3.11+ and a MySQL 8 server for syncing (validation and codegen work
offline).

## 60-second quickstart

```bash
qalib init myapp && cd myapp
# .env, git, pre-commit and VS Code wiring are ready; modules are folders:

code modules/crm/doctypes/customer.yaml   # schema autocomplete included

qalib check        # validate the YAML: every error with file, line, and a suggested fix
qalib sync         # generate models + migration, apply to MySQL
qalib              # opens the web desk in your browser (list views, forms, child tables)
qalib watch        # re-sync automatically whenever a YAML file changes
```

No `npm install`, ever: the web desk is part of the `qalib` package itself. While the
server runs, YAML edits show up on the next browser refresh.

## What a DocType looks like

One DocType per file, one folder per module (Frappe-app style):
`modules/<module>/doctypes/<doctype>.yaml` — the `module:` key is optional and derived
from the folder. Labels are `{en, ar}` maps — qalib is Arabic-first and every table
is `utf8mb4`.

```yaml
# modules/crm/doctypes/customer.yaml
doctype: Customer
module: CRM
label:
  en: Customer
  ar: زبون

naming:
  strategy: series
  series: "CUST-.YYYY.-.#####"

title_field: customer_name

fields:
  - fieldname: customer_name
    label: {en: Customer Name, ar: اسم الزبون}
    fieldtype: Data
    reqd: true
    in_list_view: true

  - fieldname: customer_type
    fieldtype: Select
    options: [Individual, Company]
    default: Company

  - fieldname: territory
    label: {en: Territory, ar: المنطقة}
    fieldtype: Link
    options: Territory        # foreign key to tabTerritory.name
    on_delete: restrict

  - fieldname: credit_limit
    fieldtype: Currency
    default: 0

  - fieldname: addresses
    fieldtype: Table
    options: Customer Address # child table rows, edited inline

permissions:
  - role: Sales User
    read: true
    write: true
    create: true
```

From this, `qalib sync` derives:

- a `tabCustomer` MySQL table with standard fields (`name` primary key, `creation`,
  `modified`, `owner`, …) plus your columns, indexes and foreign keys,
- a typed SQLAlchemy 2.0 model and Pydantic schemas under `generated/` (banner-marked,
  never hand-edited — escape hatches live in `hooks/` and `overrides/`),
- a reviewed Alembic migration. Destructive changes (dropped columns or tables) are
  refused unless you pass `--allow-destructive`.

See [`examples/crm`](examples/crm) for a complete committed project: a territory tree, a
customer with an embedded address child table, and a submittable sales order with line
items.

## CLI reference

```
qalib new [app|module|doctype]          create anything — guided, commented files
qalib init <name> [--db-url URL]        scaffold a new project (= qalib new app)
qalib doctype new [NAME]                interactive or flag-driven (= qalib new doctype)
qalib doctype list [--module M]
qalib doctype show <NAME>               rendered field table
qalib doctype rename <OLD> <NEW>
qalib doctype delete <NAME> --allow-destructive
qalib check [--strict]                  validate YAML only, exit 1 on error
qalib sync [--plan-only] [--yes] [--allow-destructive]
qalib migrate [--revision REV]          alembic passthrough
qalib watch                             re-sync on YAML change (debounce 400ms)
qalib db shell | reset | import-db      (import-db lands in Milestone 5)
qalib fixtures load|dump
qalib serve [--host H] [--port P]       web desk + REST API (also: bare `qalib`)
```

Global flags: `--project PATH`, `--verbose`, `--no-color`, and `--json` for
machine-readable output from `check`, `sync --plan-only` and `doctype list`.

## The web desk

Bare `qalib` (or `qalib serve`) starts a local server and opens the desk: a sidebar of
your DocTypes grouped by module, list views with the columns you marked `in_list_view`,
filters from `in_standard_filter`, and create/edit forms generated from the field
metadata — sections and columns from your layout fields, inline grids for child tables,
Submit/Cancel for submittable DocTypes, and an EN/AR toggle with real RTL. It talks to a
REST API (`/api/...`) you can also use directly; DocType `permissions` are enforced per
role, and each module's `hooks/<doctype>.py` functions (`validate`, `before_insert`, …)
run on every write. Password fields are write-only and never returned.

The look is modern Frappe: white surfaces, quiet gray borders, and a single near-black
accent — dark mode inverts it. The desk is three static files inside the wheel —
hand-written JS/CSS, no framework, no build step, no external requests, no `npm install`.

## CLI charm

The terminal stays calm and friendly — gentle wording, no noise, and a small ASCII cat
that keeps you company:

```
$ qalib sync
Loaded 5 DocType(s).
Generated code: 0 written, 12 unchanged.
✓ Everything is in sync
  /\_/\
 ( ^.^ )  ~ nice!
  > ^ <
```

Not a cat person? `[cli] mascot = false` in qalib.toml.

## Dev container

The repository ships a dev container with Python, uv and a MySQL 8 service
(utf8mb4) preconfigured:

1. Open the folder in VS Code and choose **Reopen in Container**.
2. `uv sync` runs automatically on create; `QALIB_TEST_DB_URL` already points at the
   bundled MySQL service.

## Contributing / dev setup

```bash
uv sync                                        # install everything
uv run ruff format --check src tests           # formatting
uv run ruff check src tests                    # lint
uv run mypy --strict src/qalib                 # types
uv run pytest -q                               # unit + snapshot tests

docker compose -f docker-compose.test.yml up -d   # MySQL 8 on port 3307
uv run pytest -q -m db                            # integration tests
```

Commits follow [Conventional Commits](https://www.conventionalcommits.org/)
(`feat:`, `fix:`, `refactor:`, `test:`, `docs:`).

## Roadmap

| Milestone | Scope | Status |
|---|---|---|
| M1 | Schema & YAML core: DocType/Field models, loader, round-trip writer, `qalib check` | shipping |
| M2 | Codegen & MySQL: DDL mapping, SQLAlchemy models, idempotent `qalib sync`, naming series | shipping |
| M3 | Developer UX: VS Code round-trip, watch mode, CLI charm (superseded the TUI) | shipping |
| M4 | App runtime: web desk + REST API (`qalib serve`), hooks, docstatus, permissions | shipping |
| M5 | Polish: `qalib import-db` reverse engineering, fixtures, end-to-end CRM example in CI | next |

Prefer a custom frontend? `qalib init --frontend` also drops in a minimal React + Vite
template wired to the same REST API — entirely optional; the built-in desk needs nothing.

## Arabic-first

qalib assumes bilingual applications from day one: MySQL charset is `utf8mb4` with
`utf8mb4_unicode_ci` collation, every label is an `{en, ar}` map, and the desk is tested
under `LANG=ar_IQ.UTF-8`. Latin-only and LTR-only assumptions are treated as bugs.

## License

[MIT](LICENSE)
