Skip to content

Commit

Permalink
Merge pull request #17 from pomponchik/develop
Browse files Browse the repository at this point in the history
0.0.15
  • Loading branch information
pomponchik committed Apr 1, 2024
2 parents da86ae8 + 4a514c0 commit 1e4ee4d
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 43 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ['3.7']
os: [ubuntu-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v2
Expand All @@ -27,6 +28,14 @@ jobs:
shell: bash
run: mypy locklib --strict

- name: Run mypy for tests
shell: bash
run: mypy tests

- name: Run mypy in strict mode for protocols tests
shell: bash
run: mypy tests/units/protocols/ --strict

- name: Run ruff
shell: bash
run: ruff locklib
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Downloads](https://static.pepy.tech/badge/locklib/month)](https://pepy.tech/project/locklib)
[![Downloads](https://static.pepy.tech/badge/locklib)](https://pepy.tech/project/locklib)
[![codecov](https://codecov.io/gh/pomponchik/locklib/graph/badge.svg?token=O9G4FD8QFC)](https://codecov.io/gh/pomponchik/locklib)
[![Lines of code](https://sloc.xyz/github/pomponchik/locklib/?category=code)](https://github.com/boyter/scc/)
[![Lines of code](https://sloc.xyz/github/pomponchik/locklib/?category=code?)](https://github.com/boyter/scc/)
[![Hits-of-Code](https://hitsofcode.com/github/pomponchik/locklib?branch=main)](https://hitsofcode.com/github/pomponchik/locklib/view?branch=main)
[![Test-Package](https://github.com/pomponchik/locklib/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/locklib/actions/workflows/tests_and_coverage.yml)
[![Python versions](https://img.shields.io/pypi/pyversions/locklib.svg)](https://pypi.python.org/pypi/locklib)
Expand Down
6 changes: 3 additions & 3 deletions locklib/locks/smart_lock/lock.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
try:
from threading import Lock, get_native_id # type: ignore[attr-defined]
from threading import Lock, get_native_id # type: ignore[attr-defined, unused-ignore]
except ImportError: # pragma: no cover
from threading import Lock, get_ident as get_native_id # get_native_id is available only since python 3.8

from collections import deque
from typing import Type, Deque, Dict
from typing import Type, Deque, Dict, Optional
from types import TracebackType

from locklib.locks.smart_lock.graph import LocksGraph
Expand Down Expand Up @@ -61,5 +61,5 @@ def release(self) -> None:
def __enter__(self) -> None:
self.acquire()

def __exit__(self, exception_type: Type[BaseException], exception_value: BaseException, traceback: TracebackType) -> None:
def __exit__(self, exception_type: Optional[Type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType]) -> None:
self.release()
4 changes: 3 additions & 1 deletion locklib/protocols/async_context_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
class AsyncContextLockProtocol(LockProtocol, Protocol):
def __aenter__(self) -> Any:
raise NotImplementedError('Do not use the protocol as a lock.')
return None

def __aexit__(self, exception_type: Optional[Type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType]) -> bool:
def __aexit__(self, exception_type: Optional[Type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType]) -> Any:
raise NotImplementedError('Do not use the protocol as a lock.')
return None
4 changes: 3 additions & 1 deletion locklib/protocols/context_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
class ContextLockProtocol(LockProtocol, Protocol):
def __enter__(self) -> Any:
raise NotImplementedError('Do not use the protocol as a lock.')
return None

def __exit__(self, exception_type: Optional[Type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType]) -> bool:
def __exit__(self, exception_type: Optional[Type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType]) -> Any:
raise NotImplementedError('Do not use the protocol as a lock.')
return None
7 changes: 5 additions & 2 deletions locklib/protocols/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
except ImportError: # pragma: no cover
from typing_extensions import Protocol, runtime_checkable # type: ignore[assignment]

from typing import Any

@runtime_checkable
class LockProtocol(Protocol):
def acquire(self) -> None:
def acquire(self) -> Any:
raise NotImplementedError('Do not use the protocol as a lock.')
return None

def release(self) -> None:
def release(self) -> Any:
raise NotImplementedError('Do not use the protocol as a lock.')
return None
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ build-backend = 'setuptools.build_meta'

[project]
name = 'locklib'
version = '0.0.14'
version = '0.0.15'
authors = [
{ name='Evgeniy Blinov', email='[email protected]' },
]
description = 'A wonderful life without deadlocks'
description = 'When there are not enough locks from the standard library'
readme = 'README.md'
requires-python = '>=3.7'
classifiers = [
Expand All @@ -25,6 +25,7 @@ classifiers = [
'License :: OSI Approved :: MIT License',
'Topic :: Software Development :: Libraries',
'Intended Audience :: Developers',
'Typing :: Typed',
]

[tool.setuptools.package-data]
Expand Down
2 changes: 1 addition & 1 deletion tests/units/locks/smart_lock/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_normal_using():
lock = SmartLock()
index = 0

def function():
def function() -> None:
nonlocal index

for _ in range(number_of_attempts_per_thread):
Expand Down
29 changes: 18 additions & 11 deletions tests/units/protocols/test_async_context_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@


@pytest.mark.parametrize(
'lock',
'lock', # type: ignore[no-untyped-def, unused-ignore]
[
ALock(),
],
)
def test_locks_are_instances_of_context_lock_protocol(lock):
def test_locks_are_instances_of_context_lock_protocol(lock): # type: ignore[no-untyped-def, unused-ignore]
assert isinstance(lock, AsyncContextLockProtocol)


@pytest.mark.parametrize(
'other',
'other', # type: ignore[no-untyped-def, unused-ignore]
[
1,
None,
Expand All @@ -34,30 +34,37 @@ def test_locks_are_instances_of_context_lock_protocol(lock):
SmartLock(),
],
)
def test_other_objects_are_not_instances_of_context_lock(other):
def test_other_objects_are_not_instances_of_context_lock(other): # type: ignore[no-untyped-def, unused-ignore]
assert not isinstance(other, AsyncContextLockProtocol)


def test_just_async_contextmanager_is_not_async_context_lock():
def test_just_async_contextmanager_is_not_async_context_lock(): # type: ignore[no-untyped-def]
@asynccontextmanager
async def context_manager():
async def context_manager(): # type: ignore[no-untyped-def]
yield 'kek'

assert not isinstance(context_manager(), AsyncContextLockProtocol)


def test_not_implemented_methods_for_async_context_lock_protocol():
def test_not_implemented_methods_for_async_context_lock_protocol(): # type: ignore[no-untyped-def]
class AsyncContextLockProtocolImplementation(AsyncContextLockProtocol):
pass

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
AsyncContextLockProtocolImplementation().acquire()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
AsyncContextLockProtocolImplementation().release()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
AsyncContextLockProtocolImplementation().__aenter__()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
AsyncContextLockProtocolImplementation().__aexit__(None, None, None)


def tests_for_type_checking(): # type: ignore[no-untyped-def]
def some_function(lock: AsyncContextLockProtocol) -> AsyncContextLockProtocol:
return lock

some_function(ALock())
34 changes: 22 additions & 12 deletions tests/units/protocols/test_context_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@


@pytest.mark.parametrize(
'lock',
'lock', # type: ignore[no-untyped-def, unused-ignore]
[
MLock(),
TLock(),
TRLock(),
SmartLock(),
],
)
def test_locks_are_instances_of_context_lock_protocol(lock):
def test_locks_are_instances_of_context_lock_protocol(lock): # type: ignore[no-untyped-def, unused-ignore]
assert isinstance(lock, ContextLockProtocol)


@pytest.mark.parametrize(
'other',
'other', # type: ignore[no-untyped-def, unused-ignore]
[
1,
None,
Expand All @@ -34,12 +34,12 @@ def test_locks_are_instances_of_context_lock_protocol(lock):
{},
],
)
def test_other_objects_are_not_instances_of_context_lock(other):
def test_other_objects_are_not_instances_of_context_lock(other): # type: ignore[no-untyped-def, unused-ignore]
assert not isinstance(other, ContextLockProtocol)


@pytest.mark.skipif(sys.version_info < (3, 9) and sys.version_info > (3, 7), reason='Problems with Python 3.8')
def test_asyncio_lock_is_not_just_context_lock():
def test_asyncio_lock_is_not_just_context_lock(): # type: ignore[no-untyped-def]
"""
asyncio lock is an instance of the AsyncContextLockProtocol, not just ContextLockProtocol.
But! In python 3.8 it is both.
Expand All @@ -48,26 +48,36 @@ def test_asyncio_lock_is_not_just_context_lock():
assert not isinstance(ALock(), ContextLockProtocol)


def test_just_contextmanager_is_not_context_lock():
def test_just_contextmanager_is_not_context_lock(): # type: ignore[no-untyped-def]
@contextmanager
def context_manager():
def context_manager(): # type: ignore[no-untyped-def]
yield 'kek'

assert not isinstance(context_manager(), ContextLockProtocol)


def test_not_implemented_methods_for_context_lock_protocol():
def test_not_implemented_methods_for_context_lock_protocol(): # type: ignore[no-untyped-def]
class ContextLockProtocolImplementation(ContextLockProtocol):
pass

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
ContextLockProtocolImplementation().acquire()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
ContextLockProtocolImplementation().release()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
ContextLockProtocolImplementation().__enter__()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
ContextLockProtocolImplementation().__exit__(None, None, None)


def tests_for_type_checking(): # type: ignore[no-untyped-def]
def some_function(lock: ContextLockProtocol) -> ContextLockProtocol:
return lock

some_function(MLock())
some_function(TLock())
some_function(TRLock())
some_function(SmartLock())
25 changes: 18 additions & 7 deletions tests/units/protocols/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


@pytest.mark.parametrize(
'lock',
'lock', # type: ignore[no-untyped-def, unused-ignore]
[
MLock(),
TLock(),
Expand All @@ -18,12 +18,12 @@
SmartLock(),
],
)
def test_locks_are_instances_of_lock_protocol(lock):
def test_locks_are_instances_of_lock_protocol(lock): # type: ignore[no-untyped-def, unused-ignore]
assert isinstance(lock, LockProtocol)


@pytest.mark.parametrize(
'other',
'other', # type: ignore[no-untyped-def, unused-ignore]
[
1,
None,
Expand All @@ -33,16 +33,27 @@ def test_locks_are_instances_of_lock_protocol(lock):
{},
],
)
def test_other_objects_are_not_instances_of_lock(other):
def test_other_objects_are_not_instances_of_lock(other): # type: ignore[no-untyped-def, unused-ignore]
assert not isinstance(other, LockProtocol)


def test_not_implemented_methods_for_lock_protocol():
def test_not_implemented_methods_for_lock_protocol(): # type: ignore[no-untyped-def]
class LockProtocolImplementation(LockProtocol):
pass

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
LockProtocolImplementation().acquire()

with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')):
with pytest.raises(NotImplementedError, match=full_match('Do not use the protocol as a lock.')): # type: ignore[operator]
LockProtocolImplementation().release()


def tests_for_type_checking(): # type: ignore[no-untyped-def]
def some_function(lock: LockProtocol) -> LockProtocol:
return lock

some_function(MLock())
some_function(TLock())
some_function(TRLock())
some_function(ALock())
some_function(SmartLock())

0 comments on commit 1e4ee4d

Please sign in to comment.