Metadata-Version: 2.4
Name: synced-memory
Version: 0.1.0
Summary: A Python abstraction layer that makes Redis or DragonflyDB behave like a shared Python object across processes/microservices.
Author-email: Sinan Ozel <coding@sinan.slmail.me>
License: MIT License
        
        Copyright (c) 2025 Sinan Ozel
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/sinan-ozel/synced-memory
Project-URL: Issues, https://github.com/sinan-ozel/synced-memory/issues
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: redis>=4.0
Provides-Extra: test
Requires-Dist: pytest>=7.0.0; extra == "test"
Requires-Dist: pytest-cov>=3.0.0; extra == "test"
Requires-Dist: pytest-depends>=1.0.1; extra == "test"
Requires-Dist: pytest-mock>=3.14.0; extra == "test"
Requires-Dist: httpx>=0.28.1; extra == "test"
Provides-Extra: dev
Requires-Dist: ruff>=0.12.11; extra == "dev"
Requires-Dist: isort>=5.0.0; extra == "dev"
Requires-Dist: black>=24.0.0; extra == "dev"
Requires-Dist: docformatter>=1.7.5; extra == "dev"
Requires-Dist: mike; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "docs"
Provides-Extra: publish
Requires-Dist: packaging>=25.0; extra == "publish"
Dynamic: license-file

![Tests & Lint](https://github.com/sinan-ozel/synced-memory/actions/workflows/ci.yaml/badge.svg?branch=main)
![PyPI](https://img.shields.io/pypi/v/synced-memory.svg)
![Downloads](https://static.pepy.tech/badge/synced-memory)
![Monthly Downloads](https://static.pepy.tech/badge/synced-memory/month)
![License](https://img.shields.io/github/license/sinan-ozel/synced-memory.svg)
[![Documentation](https://img.shields.io/badge/docs-github--pages-blue)](https://sinan-ozel.github.io/synced-memory/)


# 🗄️ synced-memory

A production-ready Python class for seamless, multiprocessing-safe, persistent key-value storage
using Redis or DragonflyDB as a backend. If the backend is unavailable, values are cached locally
and queued for syncing when it comes back online. All values are serialized as JSON, and you
interact with it using natural Python attribute access.

# Purpose

The intention is to use this with agentic workflows deployed as microservices,
allowing for multiple instances of the same pod to share their state.


## ✨ Features

- 🔄 **Multiprocessing-safe**: All processes share the same state via Redis or DragonflyDB.
- 🧠 **Pythonic API**: Set and get attributes as if they were regular object properties.
- 🕰️ **Persistence**: Values survive process restarts and context blocks.
- 🚦 **Resilient**: If the backend is down, changes are queued and flushed when it returns.
- 🧩 **Customizable**: Prefixes and conversation IDs for namespacing.
- 🧵 **Background sync**: Queued changes are flushed automatically in the background.

## 🚀 Quickstart

```bash
pip install synced-memory
```

```python
from synced_memory.redis import Memory

mem = Memory()
mem.answer = 42
print(mem.answer)  # 42

# Across processes or instances:
mem2 = Memory()
print(mem2.answer)  # 42

mem.settings = {"theme": "dark", "volume": 0.75}
print(mem.settings)  # {'theme': 'dark', 'volume': 0.75}
```

To use DragonflyDB instead:

```python
from synced_memory.dragonflydb import Memory

mem = Memory()
mem.answer = 42
```

## 🧑‍💻 Context Management

You can use `Memory` as a context manager for automatic resource handling:

```python
with Memory() as memory:
    memory.session = "active"
    print(memory.session)  # "active"

# Later, in a new context:
with Memory() as memory:
    print(memory.session)  # "active"
```

## 🔄 Auto-Synced Collections

Lists and dictionaries are automatically wrapped as `SyncedList` and `SyncedDict`, which sync changes to the backend immediately:

```python
mem = Memory()
mem.items = [1, 2, 3]
mem.items.append(4)  # Automatically syncs

mem2 = Memory()
print(mem2.items)  # [1, 2, 3, 4]

mem.config = {"theme": "dark"}
mem.config["lang"] = "en"  # Automatically syncs
print(mem2.config)  # {'theme': 'dark', 'lang': 'en'}
```

**Nested structures** work too:
```python
mem.data = {"user": {"preferences": {"color": "blue"}}}
mem.data["user"]["preferences"]["color"] = "red"  # Syncs!
```

### Converting to Plain Python Types

For libraries that need plain Python objects (serialization, pickling, etc.):

```python
mem.items = [1, 2, 3]
plain_list = mem.items.aslist()  # Returns regular list

mem.config = {"key": "value"}
plain_dict = mem.config.asdict()  # Returns regular dict

import pickle
pickle.dump(plain_list, file)  # Works!
```

## 🗂️ Namespacing

By default, `synced-memory` uses `memory:` as its key prefix. Override with `REDIS_PREFIX`:

```python
mem = Memory()
mem.state = {"step": 1}
print(mem.state)  # {'step': 1}
```

## Scoping

Use `PrefixedMemory` to isolate memory by a custom scope prefix:

```python
from synced_memory.redis import PrefixedMemory

mem = PrefixedMemory(prefix="session_abc123")
mem.state = {"step": 1}
print(mem.state)  # {'step': 1}
```

## ⚙️ Environment Variables

- `REDIS_HOST`: Backend hostname (default: `redis`)
- `REDIS_PORT`: Backend port (default: `6379`)
- `REDIS_PREFIX`: Key prefix (default: `memory:`)

```

--- WHEN UPDATING README.md: YOU CAN KEEP EVERYTHING BELOW THIS LINE ---
```

# 🛠️ Development

The only requirement is 🐳 Docker.
(The `.devcontainer` and `tasks.json` are prepared assuming a *nix system, but if you know the commands, this will work on Windows, too.)

1. Clone the repo.
2. Branch out.
3. Open in "devcontainer" on VS Code and start developing. Run `pytest` under `tests` to test.
4. Alternatively, if you are a fan of Test-Driven Development like me, you can run the tests without getting on a container. `.vscode/tasks.json` has the command to do so, but it's also listed here:
```
docker compose -f tests/docker-compose.yaml up --build --abort-on-container-exit --exit-code-from test
```
