Metadata-Version: 2.5
Name: Zhibet
Version: 0.0.2
Summary: A lightweight PostgreSQL wrapper with safe identifier handling.
Project-URL: Homepage, https://github.com/yourusername/pg-toolkit
Project-URL: Issues, https://github.com/yourusername/pg-toolkit/issues
Author-email: Amadou Bah <Amadoumadou12341@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Database
Requires-Python: >=3.10
Requires-Dist: psycopg2-binary>=2.9
Description-Content-Type: text/markdown

# pg-toolkit

A lightweight PostgreSQL wrapper with safe identifier handling and a simple API for common table operations.

**Status:** Alpha — the API is stable for the methods listed below, currently working on additional methods.

---

## Installation

```bash
pip install zhibet
```

**Requirements:** Python 3.10+ (uses structural pattern matching).

---

## Quick Start

```python
from zhibet import Zhibet

db = Zhibet
db.connect_db(
    database_str="mydb",
    user_str="postgres",
    password_str="secret",
    host_str="localhost",
    port=5432,
)

db.create_table("users", {
    "id":    ["SERIAL", "PRIMARY KEY"],
    "name":  ["TEXT", "NOT NULL"],
    "email": ["TEXT", "UNIQUE"],
})

db.insert_data("users", {"name": "Alice", "email": "alice@example.com"})

rows = db.query_table("users")
print(rows)
# [{'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}]
```

---

## Configuration

`Zhibet` reads environment variables at **import time** via `python-dotenv`.

Create a `.env` file in the **current working directory** where your script runs:

```env
POSTGRES_SCHEMA=workspace
```

> ⚠️ **Important:** `load_dotenv()` runs when `pg_toolkit` is imported. If you change `.env` after import, you must restart your process.
>
> ⚠️ **Known limitation:** `create_table`, `insert_data`, and `drop_table` currently hardcode the `workspace.` schema prefix and **ignore** `POSTGRES_SCHEMA`. This will be fixed in a future release.

---

## Connection Lifecycle

```python
db = PG()                        # does NOT connect yet
db.connect_db(...)               # establishes connection + cursor

# ... use db.query_table(), db.insert_data(), etc ...

# No explicit close() method exists yet.
# The connection is closed when the object is garbage collected
# or when the Python process exits.
```

**There is no `close()` method.** If you need deterministic cleanup in a long-running process, call `db.connection_str.close()` directly.

---

## API Reference

### `connect_db(database_str, user_str, password_str, host_str, port)`
Opens a psycopg2 connection and creates a cursor. Raises the original exception on failure (after printing a message).

### `query_table(table_name) -> list[dict]`
Returns all rows as a list of dictionaries keyed by column name.

```python
rows = db.query_table("users")
# [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
```

> ⚠️ This method does **not** validate `table_name` and does **not** raise on error — it prints the error and returns `None`.

### `create_table(table_name, new_table_info)`
Creates a table if it doesn't exist.

`new_table_info` is a dict mapping column names to a **list of constraint strings**:

```python
db.create_table("products", {
    "id":    ["SERIAL", "PRIMARY KEY"],
    "name":  ["TEXT", "NOT NULL"],
    "price": ["NUMERIC(10,2)", "DEFAULT 0"],
})
```

Produces:
```sql
CREATE TABLE IF NOT EXISTS workspace.products (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    price NUMERIC(10,2) DEFAULT 0
);
```

Table and column names are validated against `^[a-zA-Z_][a-zA-Z0-9_]*$`. Invalid names raise `ValueError`.

### `insert_data(table_name, data)`
Inserts a single row. Uses parameterized queries (`%s`) — values are safe.

```python
db.insert_data("users", {"name": "Bob", "email": "bob@example.com"})
```

> ⚠️ Only **one row at a time**. There is no bulk insert.

### `drop_table(table_name, if_exists=True)`
Drops a table. Defaults to `IF EXISTS` for safety.

```python
db.drop_table("users")                 # DROP TABLE IF EXISTS workspace.users;
db.drop_table("users", if_exists=False) # DROP TABLE workspace.users;
```

### `safe_identifier(identifier) -> str`
Validates a SQL identifier. Raises `ValueError` if it contains anything other than letters, digits, and underscores (and doesn't start with a digit).

```python
db.safe_identifier("users")        # → "users"
db.safe_identifier("users; DROP")  # → raises ValueError
```

### `modify_table(table_name, alteration_type)`
> 🚧 **Not yet implemented.** Currently only prints the requested operation. Do not rely on this method.

---

## Error Handling

| Method | On error |
|--------|----------|
| `connect_db` | Prints + **raises** |
| `query_table` | Prints + returns `None` |
| `create_table` | Prints + **raises** (after rollback) |
| `insert_data` | Prints + **raises** (after rollback) |
| `drop_table` | Prints + **raises** (after rollback) |


## License

MIT — see the [LICENSE](LICENSE) file.