Metadata-Version: 2.4
Name: aiodict
Version: 1.0.1
Summary: Fast JSON-backed persistent dictionary with autosave, TTL, file locking, and namespaced views.
Author: Abbosjon (with AI)
License: MIT
Project-URL: Homepage, https://pypi.org/project/aiodict/
Project-URL: Documentation, https://pypi.org/project/aiodict/
Project-URL: Repository, https://github.com/your-username/aiodict
Project-URL: Issues, https://github.com/your-username/aiodict/issues
Keywords: json,dict,storage,cache,telegram-bot,persistent-dict,aiogram
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 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: filelock>=3.16
Dynamic: license-file

# aiodict

**aiodict** is a fast, practical, JSON-backed persistent dictionary for Python.
It keeps data in memory for speed, flushes to disk atomically, supports TTL, autosave, filesystem locking, transactions, and namespaced views.

It is especially useful for:

- Telegram bots and admin tools
- local config storage
- small to medium automation projects
- prototypes and MVPs
- lightweight caches and counters

It is **not** a replacement for PostgreSQL or Redis in high-scale distributed systems. It is a strong local persistence layer for one machine, with optional multi-process file coordination via a lock file.

---

## Features

- `dict`-like API
- atomic JSON writes using a temporary file and `os.replace`
- in-memory cache for fast reads and writes
- debounced autosave
- TTL per key
- explicit `flush()` and `reload()`
- namespaced views
- transaction-style batching
- backup file creation (`.bak`)
- filesystem lock for safer multi-process access on one machine
- custom serializer / deserializer hooks

---

## Installation

```bash
pip install aiodict
```

---

## Quick Start

```python
from aiodict import aiodict

# Create or open a JSON-backed store
store = aiodict("db.json")

store["bot_name"] = "My Bot"
store["admin_id"] = 123456789

print(store["bot_name"])
print(store.get("missing", "default value"))

store.flush()
store.close()
```

---

## Why use aiodict?

`aiodict` is designed for cases where you want something much more convenient than manually reading and writing JSON, but much simpler than deploying a full database.

Compared to a plain `dict`, it gives you persistence.
Compared to raw JSON file handling, it gives you:

- better structure
- autosave
- TTL
- locking
- atomic writes
- transactions
- reusable helper methods

---

## Main Class

### `aiodict`

```python
aiodict(
    path="db.json",
    autosave=True,
    autosave_delay=0.35,
    create_backup=True,
    serializer=None,
    deserializer=None,
    indent=None,
    sort_keys=False,
    cleanup_expired_on_load=True,
    lock_timeout=10.0,
    encoding="utf-8",
)
```

### Parameters

#### `path`
Path to the JSON file.

#### `autosave`
If `True`, mutations are automatically flushed after `autosave_delay`.

#### `autosave_delay`
Debounce delay in seconds before a pending write is saved.

#### `create_backup`
If `True`, a `.bak` copy of the previous JSON file is created before each successful write.

#### `serializer`
Optional function used to convert unsupported objects into JSON-serializable values.

#### `deserializer`
Optional function used to transform values back after loading from disk.

#### `indent`
JSON indentation. Use `None` for compact output and better speed.

#### `sort_keys`
If `True`, saved JSON keys are sorted.

#### `cleanup_expired_on_load`
If `True`, expired keys are removed when the store is loaded.

#### `lock_timeout`
How long to wait for the file lock before raising an error.

#### `encoding`
File encoding, usually `utf-8`.

---

## Basic Usage

### Set and read values

```python
from aiodict import aiodict

store = aiodict("db.json")
store["name"] = "Abbosjon"
print(store["name"])
```

### Safe reads

```python
lang = store.get("lang", "en")
```

### Delete a key

```python
del store["name"]
```

### Check if a key exists

```python
if store.exists("name"):
    print("exists")
```

---

## Batch Operations

### Update many values

```python
store.update({
    "theme": "dark",
    "timezone": "Asia/Tashkent",
})
```

### Multi-set

```python
store.mset({
    "a": 1,
    "b": 2,
    "c": 3,
})
```

### Multi-get

```python
values = store.mget(["a", "b", "missing"], default=None)
print(values)
```

---

## Numeric Helpers

### Increment a counter

```python
store.incr("visits")
store.incr("visits", 5)
```

---

## List Helpers

### Append

```python
store.append("admins", 123456789)
```

### Append uniquely

```python
store.append("admins", 123456789, unique=True)
```

### Extend

```python
store.extend("admins", [111, 222, 333])
```

### Remove values from a list

```python
store.remove("admins", 222)
store.remove("admins", 111, all_occurrences=True)
```

---

## TTL Support

### Set with TTL

```python
store.set("otp:123", "918273", ttl=60)
```

### Add or update expiration later

```python
store.expire("otp:123", 120)
```

### Read remaining TTL

```python
remaining = store.ttl("otp:123")
print(remaining)
```

### Remove expiration

```python
store.persist("otp:123")
```

### Remove expired keys manually

```python
removed = store.cleanup_expired()
print(f"Removed {removed} expired keys")
```

---

## Transactions

Use `transaction()` when you want several writes and one final flush.

```python
with store.transaction():
    store["user:1"] = {"name": "Alice"}
    store["user:2"] = {"name": "Bob"}
    store.incr("user_count", 2)
```

This is useful when you want cleaner save behavior and fewer disk writes.

---

## Namespaces

Namespaces help organize keys without managing prefixes manually.

```python
users = store.namespace("users")
settings = store.namespace("settings")

users["1001"] = {"name": "Ali"}
settings["language"] = "uz"

print(users["1001"])
print(settings["language"])
```

Internally, those become:

- `users:1001`
- `settings:language`

---

## Explicit Persistence

### Save now

```python
store.flush()
```

### Reload from disk

```python
store.reload()
```

### Close cleanly

```python
store.close()
```

### Context manager usage

```python
from aiodict import aiodict

with aiodict("db.json") as store:
    store["hello"] = "world"
```

---

## Serialization Hooks

### Custom serializer

```python
from datetime import datetime
from aiodict import aiodict


def serializer(value):
    if isinstance(value, datetime):
        return {"__type__": "datetime", "value": value.isoformat()}
    raise TypeError


def deserializer(value):
    if isinstance(value, dict) and value.get("__type__") == "datetime":
        return datetime.fromisoformat(value["value"])
    return value


store = aiodict("db.json", serializer=serializer, deserializer=deserializer)
store["created_at"] = datetime.now()
store.flush()
```

---

## Useful Methods

### `copy()`
Returns a plain `dict` snapshot of the live data.

```python
snapshot = store.copy()
```

### `stats()`
Returns basic runtime information.

```python
print(store.stats())
```

Example output:

```python
{
    "keys": 12,
    "ttl_keys": 2,
    "autosave": True,
    "autosave_delay": 0.35,
    "file_path": "db.json",
    "file_size_bytes": 2048,
    "dirty": False,
}
```

---

## Telegram Bot Example

```python
from aiodict import aiodict

store = aiodict("bot_db.json")
users = store.namespace("users")
stats = store.namespace("stats")


def register_user(user_id: int, full_name: str) -> None:
    users[str(user_id)] = {
        "full_name": full_name,
        "joined_at": "2026-04-05",
    }
    stats.incr("total_users")


def is_registered(user_id: int) -> bool:
    return users.exists(str(user_id))
```

This works well for small and medium bots on a single machine.

---

## Performance Notes

`aiodict` is fast because reads and writes primarily hit memory. Disk writes are delayed and batched.

That said, it still writes to one JSON file, so it is best suited for:

- low to moderate write frequency
- one machine
- small to medium datasets

It is **not** ideal for:

- massive datasets
- very high write concurrency
- distributed multi-server systems
- analytics-heavy workloads

---

## Recommended Use Cases

Good fit:

- bot config
- user settings
- counters
- drafts
- feature flags
- local admin data
- temporary OTP or cooldown keys with TTL

Not a good fit:

- payment ledgers
- large e-commerce systems
- multi-node web backends
- big broadcast job queues at very high scale

---

## Practical Scale Guidance

As a primary store, `aiodict` is a good fit for:

- a few hundred to a few thousand actively changing keys
- small to medium bots
- local tools and internal systems

For large production systems with many active writes, use PostgreSQL or Redis.

A practical rule:

- **Great:** small to medium single-machine projects
- **Acceptable:** moderate workloads with controlled write volume
- **Wrong tool:** large distributed systems or heavy concurrent write workloads

---

## Development Tips

- call `flush()` before shutdown in critical flows
- use namespaces to keep keys organized
- prefer compact JSON (`indent=None`) for best performance
- use `transaction()` when doing many writes together
- use TTL for temporary values like OTPs and cooldowns
- keep stored values JSON-friendly whenever possible

---

## License

MIT
