Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,14 @@ from multiprocessing import Lock as MLock
from threading import Lock as TLock, RLock as TRLock
from asyncio import Lock as ALock

from locklib import SmartLock, LockProtocol
from locklib import SmartLock, SmartRLock, LockProtocol

print(isinstance(MLock(), LockProtocol)) # True
print(isinstance(TLock(), LockProtocol)) # True
print(isinstance(TRLock(), LockProtocol)) # True
print(isinstance(ALock(), LockProtocol)) # True
print(isinstance(SmartLock(), LockProtocol)) # True
print(isinstance(SmartRLock(), LockProtocol)) # True
```

However, most idiomatic Python code uses locks as context managers. If your code does too, you can use one of the two protocols derived from the base `LockProtocol`: `ContextLockProtocol` or `AsyncContextLockProtocol`. Thus, the protocol hierarchy looks like this:
Expand All @@ -89,18 +90,19 @@ LockProtocol

`ContextLockProtocol` describes objects that satisfy `LockProtocol` and also implement the [context manager protocol](https://docs.python.org/3/library/stdtypes.html#typecontextmanager). Similarly,`AsyncContextLockProtocol` describes objects that satisfy `LockProtocol` and implement the [asynchronous context manager](https://docs.python.org/3/reference/datamodel.html#async-context-managers) protocol.

Almost all standard library locks, as well as `SmartLock`, satisfy `ContextLockProtocol`:
Almost all standard library locks, as well as `SmartLock` and `SmartRLock`, satisfy `ContextLockProtocol`:

```python
from multiprocessing import Lock as MLock
from threading import Lock as TLock, RLock as TRLock

from locklib import SmartLock, ContextLockProtocol
from locklib import SmartLock, SmartRLock, ContextLockProtocol

print(isinstance(MLock(), ContextLockProtocol)) # True
print(isinstance(TLock(), ContextLockProtocol)) # True
print(isinstance(TRLock(), ContextLockProtocol)) # True
print(isinstance(SmartLock(), ContextLockProtocol)) # True
print(isinstance(SmartRLock(), ContextLockProtocol)) # True
```

However, the [`asyncio.Lock`](https://docs.python.org/3/library/asyncio-sync.html#asyncio.Lock) belongs to a separate category and `AsyncContextLockProtocol` is needed to describe it:
Expand Down Expand Up @@ -209,6 +211,29 @@ If you want to catch this exception, you can also import it from `locklib`:
from locklib import DeadLockError
```

`SmartLock` is deliberately not recursive: acquiring the same instance twice from the same thread raises `DeadLockError`. When recursive ownership is needed, use `SmartRLock` instead. It keeps the same deadlock detection for waits between threads, but lets the owning thread enter the same lock repeatedly:

```python
from locklib import SmartRLock

lock = SmartRLock()

with lock, lock:
pass
```

If you use explicit `acquire()` and `release()` calls, every successful acquire must have a matching release:

```python
from locklib import SmartRLock

lock = SmartRLock()

lock.acquire()
lock.acquire()
lock.release()
lock.release()
```

## Test your locks

Expand Down
47 changes: 47 additions & 0 deletions docs/plans/1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Вынести базовую реализацию SmartLock

## Motivation
- Подготовить код к будущему семейству смартлоков: общая текущая реализация должна жить в базовом `AbstractSmartLock`, а публичный `SmartLock` должен стать конкретной тонкой оберткой.
- Сохранить поведение и публичный импорт `SmartLock` без изменений.
- Сделать перенос через `git mv`, чтобы по diff было легко проверить, что реализация не потерялась и была только переименована/расширена.

## Summary
- Перед изменениями сохранить этот план в `docs/plans/1.md`.
- Перенести текущую реализацию `SmartLock` в `AbstractSmartLock` через `git mv`.
- Оставить публичный `SmartLock` тонким наследником без изменения поведения.
- Добавить один тест, фиксирующий, что `SmartLock` является потомком `AbstractSmartLock`.

## Key Changes
- Выполнить `git mv locklib/locks/smart_lock/lock.py locklib/locks/smart_lock/abstract.py`.
- В `abstract.py` переименовать `SmartLock` в `AbstractSmartLock`, добавить импорт `ABC` из `abc` и объявить `class AbstractSmartLock(ABC):`; абстрактные методы и guard на прямое инстанцирование не добавлять.
- Создать новый `locklib/locks/smart_lock/lock.py` с минимальным кодом:
```python
from locklib.locks.smart_lock.abstract import AbstractSmartLock


class SmartLock(AbstractSmartLock):
...
```
- В `tests/units/locks/smart_lock/test_lock.py` импортировать `AbstractSmartLock` и добавить тест с докстрингом в стиле проекта:
```python
def test_smart_lock_is_abstract_smart_lock_subclass():
"""SmartLock keeps AbstractSmartLock as its base implementation."""
assert issubclass(SmartLock, AbstractSmartLock)
```
- Не менять `locklib/__init__.py`: `from locklib import SmartLock` должен работать как раньше.
- Не экспортировать `AbstractSmartLock` на верхнем уровне пакета.

## Test Plan
- Запустить `ruff check locklib`.
- Запустить `ruff check tests`.
- Запустить `mypy locklib --strict`.
- Запустить `mypy tests`.
- Запустить coverage-команду из `AGENTS.md`:
```bash
coverage run --source=locklib --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100
```

## Assumptions
- `AbstractSmartLock` пока остается полностью рабочей реализацией и технически может быть инстанцирован напрямую, потому что у него нет абстрактных методов.
- Рассматривалась возможность написать тесты, которые проверят невозможность прямого инстанцирования `AbstractSmartLock`, но выяснилось, что ABC-классы без абстрактных методов не поднимают исключение при инстанцировании, так что решили пока такие тесты не добавлять.
- `SmartLock` должен сохранить весь текущий runtime API, сообщения исключений и поведение deadlock detection.
Loading
Loading