Metadata-Version: 2.4
Name: bijoy
Version: 1.0.0
Summary: Object Observability for Python — automatically track every attribute change.
Author: Bijoy
License: MIT
Project-URL: Homepage, https://github.com/bijoy/bijoy
Project-URL: Issues, https://github.com/bijoy/bijoy/issues
Keywords: observability,debugging,auditing,attribute-tracking,history
Classifier: Development Status :: 5 - Production/Stable
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 :: Debuggers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

📜 Bijoy
  
    Object Observability for Python
    Automatically track every attribute change — built for debugging & auditing.

    Python 3.8+
    Zero Dependencies
    Thread Safe
    MIT License

---

## ✨ What is Bijoy?

**Bijoy** is a lightweight Python library that gives your objects *memory*. By inheriting from the `Chronicle` base class, every attribute change is automatically recorded with:

- 🕐 **High-precision UTC timestamp**
- 📍 **Exact source location** (file, line number, function name)
- 🔒 **Thread-safe** logging via `threading.Lock`
- 📦 **Zero external dependencies** — uses only the Python Standard Library

Perfect for **debugging state mutations**, **auditing workflows**, and understanding *when* and *where* your objects changed.

---

## 🚀 Quick Start

### Installation

```bash
pip install bijoy
```

Or install from source:

```bash
git clone https://github.com/bijoy/bijoy.git
cd bijoy
pip install -e .
```

### Basic Usage

```python
from bijoy import Chronicle


class Server(Chronicle):
    def __init__(self, name: str):
        super().__init__(max_history=50)
        self.name = name
        self.status = "booting"


srv = Server("web-prod-01")
srv.status = "deploying"
srv.status = "running"

# View the full history of an attribute
for entry in srv.get_history("status"):
    print(entry)

# Undo the last change
srv.undo("status")
print(srv.status)  # "deploying"

# Print a formatted audit report
srv.audit()
```

**Output:**

```
========================================================================
  CHRONICLE AUDIT — Server (id=0x...)
========================================================================

  Attribute: 'name'
  ————————————————————————————————————————
    [1] value = 'web-prod-01'
        time  = 2026-03-21 17:00:00.123456 UTC
        where = example.py:8 in __init__()

  Attribute: 'status'
  ————————————————————————————————————————
    [1] value = 'booting'
        time  = 2026-03-21 17:00:00.123457 UTC
        where = example.py:9 in __init__()
    [2] value = 'deploying'
        time  = 2026-03-21 17:00:00.123458 UTC
        where = example.py:13 in <module>()

========================================================================
```

---

## 📖 API Reference

### `Chronicle(max_history: int = 100)`

Base class. Inherit from it to enable automatic attribute tracking.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `max_history` | `int` | `100` | Maximum entries kept per attribute (FIFO) |

---

### `get_history(attr_name: str) → List[HistoryEntry]`

Returns a chronological list of all recorded changes for the given attribute.

```python
entries = obj.get_history("status")
for e in entries:
    print(f"{e.value} at {e.timestamp} — {e.filename}:{e.lineno}")
```

---

### `undo(attr_name: str) → Any`

Reverts an attribute to its **previous** value and removes the latest history entry.

```python
obj.status = "crashed"
prev = obj.undo("status")  # restores previous value
```

- Returns the restored value, or `None` if the attribute had only one entry (gets deleted).
- Raises `KeyError` if no history exists.

---

### `audit() → str`

Prints and returns a human-readable, formatted report of **all** recorded attribute changes.

```python
report = obj.audit()  # prints to stdout and returns the string
```

---

### `toggle_recording(enable: bool) → None`

Pause or resume history tracking. Useful to skip noisy updates.

```python
obj.toggle_recording(False)
obj.cpu_load = 99.9  # NOT recorded
obj.toggle_recording(True)
obj.cpu_load = 42.0  # recorded
```

---

### `clear_history(attr_name: str | None = None) → None`

Erase recorded history. Pass an attribute name to clear only that attribute, or `None` to clear everything.

---

### `HistoryEntry`

Each recorded change is stored as a `HistoryEntry` with these fields:

| Field | Type | Description |
|-------|------|-------------|
| `value` | `Any` | The new value assigned |
| `timestamp` | `datetime` | UTC time of the change |
| `filename` | `str` | Source file path |
| `lineno` | `int` | Line number in the source file |
| `function` | `str` | Function/method name |

---

## 🧪 Running Tests

```bash
python -m unittest test_chronicle -v
```

```
test_initial_assignment_creates_history ........................ ok
test_subsequent_changes_are_appended ........................... ok
test_history_entry_has_caller_metadata ......................... ok
test_private_attrs_not_logged .................................. ok
test_undo_restores_previous_value .............................. ok
test_undo_removes_latest_entry ................................. ok
test_undo_single_entry_deletes_attribute ....................... ok
test_undo_no_history_raises .................................... ok
test_oldest_entries_discarded .................................. ok
test_max_history_of_one ........................................ ok
test_paused_changes_not_logged ................................. ok
test_resume_recording .......................................... ok
test_concurrent_writes ......................................... ok

----------------------------------------------------------------------
Ran 13 tests in 0.XXXs

OK
```

---

## 🏗️ Project Structure

```
bijoy/
├── bijoy/
│   ├── __init__.py
│   └── chronicle.py
├── example_chronicle.py
├── test_chronicle.py
├── pyproject.toml
├── LICENSE
├── .gitignore
└── README.md
```

---

## 🎯 Design Highlights

| Feature | How |
|---------|-----|
| **No recursion loops** | `__init__` writes internals via `self.__dict__` directly |
| **No meta-logging** | Attributes starting with `_` are silently skipped |
| **O(1) eviction** | Uses `collections.deque(maxlen=…)` for FIFO pruning |
| **Thread safety** | All history reads/writes are guarded by `threading.Lock` |
| **Zero dependencies** | Only `inspect`, `threading`, `datetime`, `collections` |

---

## 📄 License

MIT License — free for personal and commercial use.

---

  Made with ❤️ by Bijoy

