Metadata-Version: 2.4
Name: larzsql
Version: 0.1.0
Summary: An injection-safe SQL query builder: SELECT/INSERT/UPDATE/DELETE with bound parameters and validated identifiers. Pure Python, zero dependencies.
Author: larz-scripter
License: MIT
Project-URL: Homepage, https://github.com/larz-scripter/larzsql
Project-URL: Repository, https://github.com/larz-scripter/larzsql
Project-URL: Issues, https://github.com/larz-scripter/larzsql/issues
Keywords: sql,query-builder,sql-builder,parameterized,injection-safe,database,orm-alternative,dbapi,zero-dependency
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# larzsql

**An injection-safe SQL query builder. Pure Python, zero dependencies.**

Concatenating SQL is how injection happens. larzsql builds SELECT/INSERT/UPDATE/
DELETE where **every value becomes a bound parameter** (never interpolated) and
every table/column name is validated as a plain identifier - so a value or a name
can't smuggle in SQL. You get back `(sql, params)` ready for your DB-API driver.

```python
from larzsql import select, insert, update, delete

select("id", "name").from_("users").where(active=True, age__gte=18) \
    .order_by("-created").limit(10).build()
# ('SELECT id, name FROM users WHERE active = ? AND age >= ? ORDER BY created DESC LIMIT 10',
#  [True, 18])

insert("users").values(name="Ada", email="a@b.com").build()
# ('INSERT INTO users (name, email) VALUES (?, ?)', ['Ada', 'a@b.com'])
```

## Why

- **Injection-safe by construction.** Values are always bound parameters - even a
  `"1; DROP TABLE users; --"` value ends up in `params`, never in the SQL text -
  and identifiers are validated against a strict pattern, so
  `from_("users; DROP ...")` raises instead of executing.
- **Fluent and complete.** `where` with Django-style lookups (`age__gte`,
  `id__in`, `deleted__isnull`) or raw fragments, `join`/`left_join`, `order_by`
  (`-col` for DESC), `limit`/`offset`, plus INSERT/UPDATE/DELETE.
- **Driver-agnostic.** `build(paramstyle="?")` for sqlite or `"%s"` for
  psycopg2/MySQL.
- **Not an ORM.** It builds strings + params; you run them. Zero dependencies,
  pairs with [larzdb](https://github.com/larz-scripter/larzdb) /
  [larzmigrate](https://github.com/larz-scripter/larzmigrate).

## Install

```bash
pip install larzsql
```

## Usage

```python
from larzsql import select, insert, update, delete

select("*").from_("orders").where(status__in=["paid", "shipped"]).build()
select("u.id", "o.total").from_("users").left_join("orders", "orders.user_id = users.id").build()
update("users").set(active=False).where(id=5).build()
delete("sessions").where(expires__lt=now).build()

sql, params = select("*").from_("t").where(id=1).build(paramstyle="%s")
cursor.execute(sql, params)
```

## Tests

```bash
python -m unittest discover -s tests -v   # 17 tests incl. injection safety
```

## The Larz stack

One of 30+ pure-Python, zero-dependency libraries at
[github.com/larz-scripter](https://github.com/larz-scripter).

## License

MIT (c) larz-scripter
