Metadata-Version: 2.4
Name: synchronized-lock
Version: 0.1.0
Summary: Cross-process synchronization with a simple Python decorator
Author: Michal Seredyn
License-Expression: MIT
Project-URL: Homepage, https://github.com/mseredyn/synchronized
Project-URL: Repository, https://github.com/mseredyn/synchronized.git
Project-URL: Issues, https://github.com/mseredyn/synchronized/issues
Keywords: synchronization,locking,multiprocessing,decorator
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: filelock>=3.12
Provides-Extra: test
Requires-Dist: build>=1.2; extra == "test"
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: twine>=5; extra == "test"
Dynamic: license-file

# synchronized

Cross-process and cross-thread synchronization for Python instance methods with
a simple `@synchronized` decorator.

The decorator uses [`filelock`](https://py-filelock.readthedocs.io/) underneath,
so callers in separate processes can coordinate through the same lock file. The
decorated class does not need a base class or any lock-management plumbing.

## Installation

```bash
python -m pip install synchronized-lock
```

Python 3.10 or newer is required.

## Quick start

```python
from synchronized import synchronized


class UserStorage:
    @synchronized("users")
    def update(self, user_id: str) -> None:
        # Only one caller using the "users" lock can run this at a time,
        # including callers in another process.
        ...
```

Use the decorator without arguments to create a lock for a method:

```python
class Cache:
    @synchronized
    def rebuild(self) -> None:
        ...
```

That lock is shared by every instance of the same runtime class. Different
classes receive different locks.

## Lock scopes

An explicit name is useful when different methods must share one critical
section:

```python
class UserStorage:
    @synchronized("users")
    def lock_user(self, user_id: str) -> None: ...

    @synchronized("users")
    def unlock_user(self, user_id: str) -> None: ...
```

For distinct real-world resources represented by instances of one class, use
`Scope.INSTANCE` and provide a stable `lock_key()`:

```python
from synchronized import Scope, synchronized


class Database:
    def __init__(self, url: str) -> None:
        self.url = url

    def lock_key(self) -> str:
        return self.url

    @synchronized(scope=Scope.INSTANCE)
    def migrate(self) -> None:
        ...
```

`lock_key()` must return a stable, non-empty string. Instance scope rejects
objects without it because an in-memory identity cannot coordinate across
processes.

The decorator supports synchronous methods. Decorating `async def` is rejected
with an error because holding a filesystem lock across an awaited coroutine
requires different cancellation and concurrency semantics.

## Configuration

Locks default to the platform temporary directory under `synchronized-locks`.
Set a directory through the environment:

```bash
export SYNCHRONIZED_LOCK_DIR=/var/tmp/my-app-locks
```

Or configure it before decorated methods are invoked:

```python
from synchronized import configure

configure("/var/tmp/my-app-locks")
```

Calling `configure()` again routes subsequent calls through the new directory,
including lock names that were previously used.

Every process that should coordinate must use the same directory. Lock names
are converted to portable filenames and include a SHA-256-derived suffix.

## Development and releases

```bash
python -m pip install -e '.[test]'
pytest
python -m build
python -m twine check dist/*
```

GitHub Actions runs the test suite on supported Python versions. Creating a
GitHub release publishes its artifacts to PyPI through trusted publishing once
the GitHub environment is configured as a trusted publisher on PyPI.

## License

[MIT](LICENSE)
