[![PyPI version](https://badge.fury.io/py/dpyb.svg)](https://pypi.org/project/dpyb/)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)

# dpyb

A simple, dependency-free, file-based Python database.

`dpyb` is a lightweight database that stores all its data in a single Python-readable file with the `.dpyb.py` extension. Every table is a plain Python dictionary, so you can read, inspect, and even version-control your database with Git.

- **Zero dependencies** — only the Python standard library
- **Human-readable format** — data is stored as plain Python dicts
- **Automatic backups** — every write operation creates a timestamped backup
- **Simple API** — 13 functions, no classes to learn
- **Consistent tables** — rows and columns never go out of sync

---

## Requirements

- Python **3.12** or newer

---

## Installation

```bash
pip install dpyb
```

---

## Quick start

```python
import dpyb

# Create a new database
dpyb.CreateDataBase("mydb")

# Add a table
dpyb.AddTableToDataBase("mydb", "users")

# Add 5 empty rows and 2 extra columns
dpyb.AddRow("mydb", "users", 5)      # 6 rows total
dpyb.AddColumn("mydb", "users", 2)   # 3 columns total

# Fill some cells
dpyb.WriteData("mydb", "users", 1, 1, "Ali")
dpyb.WriteData("mydb", "users", 1, 2, 25)
dpyb.WriteData("mydb", "users", 1, 3, "Tehran")

# Read a cell
print(dpyb.ReadData("mydb", "users", 1, 1))   # "Ali"
```

The file `mydb.dpyb.py` will look like this:

```python
# a d-py-b database file, DO NOT EDIT THIS FILE
# d-py-b, a simple python database by darkstarshine2011

# ---

users = {
    1: {1: 'Ali', 2: 25, 3: 'Tehran'},
    2: {1: '', 2: '', 3: ''},
    3: {1: '', 2: '', 3: ''},
    4: {1: '', 2: '', 3: ''},
    5: {1: '', 2: '', 3: ''},
    6: {1: '', 2: '', 3: ''},
}
```

---

## Data model

A `dpyb` database is a set of tables. Each table is a dictionary:

```
Table = {
    RowID: { ColumnID: Value, ColumnID: Value, ... },
    RowID: { ColumnID: Value, ColumnID: Value, ... },
}
```

Example:

```python
users = {
    1: {1: "Ali",  2: 25, 3: "Tehran"},
    2: {1: "Sara", 2: 30, 3: "Shiraz"},
    3: {1: "Reza", 2: 40, 3: "Mashhad"},
}
```

- **Row IDs** and **Column IDs** are integers.
- **Values** can be any Python literal: `str`, `int`, `float`, `bool`, `None`, `list`, `dict`, `tuple`.

### How `AddRow` and `AddColumn` work

- `AddRow(db, table, N)` adds **N** new rows. Each new row inherits **every column that already exists** in the table, filled with `""`.
- `AddColumn(db, table, N)` adds **N** new columns to **every row** in the table, filled with `""`.
- A newly created table starts with **1 row** and **1 column**.

This means you never have to worry about rows and columns getting out of sync. Every row always has exactly the same set of columns.

---

## API Reference

### `GetDataBaseName(DataBaseFile)`
Normalizes a database filename. Adds the `.dpyb.py` extension if missing.

```python
dpyb.GetDataBaseName("mydb")        # "mydb.dpyb.py"
dpyb.GetDataBaseName("mydb.dpyb")   # "mydb.dpyb.py"
dpyb.GetDataBaseName("mydb.py")     # "mydb.dpyb.py"
```

### `CreateDataBase(DataBaseFile)`
Creates a new, empty database file. **Overwrites** the file if it already exists.

```python
dpyb.CreateDataBase("mydb")
```

### `CreateBackup(DataBaseFile)`
Creates a timestamped backup of the database in the `Backup/` folder.

> This is called automatically by every write operation, so you rarely need to call it manually.

### `AddTableToDataBase(DataBaseFile, TableName)`
Adds a new table to the database. The table starts with 1 row and 1 column, both empty. Does nothing if the table already exists.

```python
dpyb.AddTableToDataBase("mydb", "users")
```

### `AddRow(DataBaseFile, TableName, Rows=1)`
Adds `Rows` new rows to the table. Each new row inherits all existing columns, filled with `""`. Default is 1 row.

```python
dpyb.AddRow("mydb", "users")      # 1 new row
dpyb.AddRow("mydb", "users", 5)   # 5 new rows
```

### `AddColumn(DataBaseFile, TableName, Columns=1)`
Adds `Columns` new columns to every row of the table, filled with `""`. Default is 1 column.

```python
dpyb.AddColumn("mydb", "users")      # 1 new column
dpyb.AddColumn("mydb", "users", 3)   # 3 new columns
```

### `ReadData(DataBaseFile, TableName, Row, Column)`
Returns the value of a single cell.

```python
Name = dpyb.ReadData("mydb", "users", 1, 1)   # "Ali"
```

### `WriteData(DataBaseFile, TableName, Row, Column, Value)`
Writes a value into a single cell.

```python
dpyb.WriteData("mydb", "users", 1, 2, 26)
```

### `DeleteRow(DataBaseFile, TableName, Row)`
Deletes a row from the table.

```python
dpyb.DeleteRow("mydb", "users", 2)
```

### `DeleteColumn(DataBaseFile, TableName, Column)`
Deletes a column from every row of the table.

```python
dpyb.DeleteColumn("mydb", "users", 3)
```

### `DeleteTable(DataBaseFile, TableName)`
Deletes an entire table from the database.

```python
dpyb.DeleteTable("mydb", "users")
```

### `ListTables(DataBaseFile)`
Returns a list of all table names.

```python
dpyb.ListTables("mydb")   # ["users", "posts"]
```

### `GetTable(DataBaseFile, TableName)`
Returns the entire table as a Python dictionary.

```python
Users = dpyb.GetTable("mydb", "users")
```

---

## Examples

The `examples/` folder contains 15 ready-to-run scripts, from a basic hello-world to a small inventory system.

| # | File | What it shows |
|---|------|---------------|
| 01 | Getting started | Create a database, add a table, write and read a cell |
| 02 | Adding rows and columns | How `AddRow` and `AddColumn` keep tables consistent |
| 03 | Reading and writing cells | Fill a small table cell by cell |
| 04 | Iterating over a table | Loop through every row and every cell |
| 05 | Multiple tables | One database, many independent tables |
| 06 | Deleting | Delete rows, columns, and full tables |
| 07 | Filling from a list | Populate a table from a Python list |
| 08 | Simple todo list | A tiny command-line app with state |
| 09 | Searching and filtering | Find rows matching a condition |
| 10 | Contact book | Add / find / delete a contact |
| 11 | Statistics on a table | Sum, average, min, max with plain Python |
| 12 | Copying a table | Duplicate a table inside the same database |
| 13 | Nested data | Store lists and dicts inside cells |
| 14 | Inventory system | A small shop inventory with sell / restock |
| 15 | Complete workflow | End-to-end tour of the entire API |

### Filling a table cell by cell

```python
import dpyb

dpyb.CreateDataBase("mydb")
dpyb.AddTableToDataBase("mydb", "users")

# We want 2 more rows and 2 more columns
dpyb.AddRow("mydb", "users", 2)      # now 3 rows total
dpyb.AddColumn("mydb", "users", 2)   # now 3 columns total

# Fill row 1
dpyb.WriteData("mydb", "users", 1, 1, "Ali")
dpyb.WriteData("mydb", "users", 1, 2, 25)
dpyb.WriteData("mydb", "users", 1, 3, "Tehran")

# Fill row 2
dpyb.WriteData("mydb", "users", 2, 1, "Sara")
dpyb.WriteData("mydb", "users", 2, 2, 30)
dpyb.WriteData("mydb", "users", 2, 3, "Shiraz")

print(dpyb.GetTable("mydb", "users"))
```

### Iterating over a table

```python
Users = dpyb.GetTable("mydb", "users")

for RowID in Users:
    Name = Users[RowID][1]
    Age = Users[RowID][2]
    print(f"Row {RowID}: {Name}, age {Age}")
```

### Working with multiple tables

```python
dpyb.CreateDataBase("mydb")
dpyb.AddTableToDataBase("mydb", "users")
dpyb.AddTableToDataBase("mydb", "posts")

dpyb.AddRow("mydb", "users", 3)
dpyb.AddRow("mydb", "posts", 10)

print(dpyb.ListTables("mydb"))   # ["users", "posts"]
```

### Storing nested data

```python
dpyb.WriteData("mydb", "users", 1, 1, "Ali")
dpyb.WriteData("mydb", "users", 1, 2, ["admin", "developer"])
dpyb.WriteData("mydb", "users", 1, 3, {"country": "Iran", "active": True})

Users = dpyb.GetTable("mydb", "users")
print(Users[1][2])              # ["admin", "developer"]
print(Users[1][3]["country"])   # "Iran"
```

---

## Backups

Every write operation (`AddRow`, `AddColumn`, `WriteData`, `DeleteRow`, `DeleteColumn`, `DeleteTable`, `AddTableToDataBase`) creates a timestamped backup inside a `Backup/` folder next to your script.

```
Backup/
├── 20261005_120000_123456.dpyb.py.backup
├── 20261005_120015_654321.dpyb.py.backup
└── ...
```

Read operations (`ReadData`, `GetTable`, `ListTables`) do **not** create backups.

---

## Notes

- Database files use the `.dpyb.py` extension and should **not** be edited by hand. They are valid Python files, but a wrong edit can break the parser.
- `dpyb` has **zero external dependencies**.
- Not designed for concurrent writes. If multiple processes write to the same database at the same time, data loss may occur.
- Not designed for large datasets. Every write reads and rewrites the entire file.

---

## License

This project is licensed under the **GNU Affero General Public License v3.0 or later** (AGPL-3.0-or-later).
See the [LICENSE](https://github.com/darkstarshine2011/dpyb/blob/main/LICENSE) file for the full text.

---

## Author

Made by **darkstarshine2011**

---

## Credits

- **Library code** — darkstarshine2011
- **Examples** — written by DeepSeek