Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
31b6089
Update pytest and full_match to newer versions
Oct 8, 2025
c596a91
Update test imports to use `match` instead of `full_match` and replace
Oct 8, 2025
2898349
Add Python 3.14 to lint workflow
Oct 8, 2025
3be913e
Run ruff on the library and test directories before type‑checking
Oct 8, 2025
77cdff3
Update Actions to v4/v5 and add Py3.14 support
Oct 8, 2025
ea99cd2
Add pip dependency caching to CI workflows
Oct 8, 2025
0ea9ded
Add installation step to lint workflow
Oct 8, 2025
24ed92f
Add Coveralls integration for test coverage
Oct 8, 2025
0af1279
Rename workflow to Tests
Oct 8, 2025
08052e4
Add support for Python 3.14 in pyproject.toml classifiers
Oct 8, 2025
271a2b6
New version tag
Oct 8, 2025
a2ff20d
Update README badges: replace CodeCov with Coveralls and add DeepWiki
Oct 8, 2025
7ae84ec
A little refactoring
Oct 8, 2025
a005810
Create tracer lock implementation module and package structure
Oct 8, 2025
7e9112d
Remove redundant `# type: ignore[operator]` comments from lock protocol
Oct 8, 2025
8c7bee8
Use full_match.match in test_lock
Oct 8, 2025
c2b09f0
Remove typing_extensions fallback for Protocol import
Oct 8, 2025
7f6e37d
Create TracerEvent enum and LockTraceWrapper for lock tracing operations
Oct 9, 2025
dbc2935
Use Enum instead of StrEnum for TracerEvent
Oct 9, 2025
4e29b76
Add distinctness check for TracerEvent kinds
Oct 9, 2025
4bc7643
Add tracer utilities to package exports
Oct 9, 2025
b2e433a
Fix import of TracerEvent in unit tests
Oct 9, 2025
633b1ac
Add unit test for LockTraceWrapper
Oct 9, 2025
32cb3b7
Release lock on exit and return status
Oct 9, 2025
dc1f273
Replace README logo with new SVG asset
Oct 9, 2025
3696672
Remove extra white path from the last logo
Oct 9, 2025
8177f1e
Update logo_7.svg zoom, center, and add orange fill
Oct 9, 2025
7d144ba
Update logo SVG dimensions and paths
Oct 9, 2025
ed63efc
Rename Release Workflow To Publish
Oct 9, 2025
f6a8257
Rename workflow to "Publish the package"
Oct 9, 2025
8a80162
Add release workflow for merged PRs
Oct 9, 2025
48d4729
Delete logo_8.png from the docs assets
Oct 9, 2025
4d0386c
Remove unused logo_8.png
Oct 9, 2025
c3efeb8
Add unit test for LockTraceWrapper basic tracing
Oct 9, 2025
e4f84cd
Add unit test for LockTraceWrapper context manager
Oct 9, 2025
eb6e2b1
Add structured tracer events and lock tracking
Oct 9, 2025
e3f6c8a
Adjust LockTraceWrapper.__exit__ signature
Oct 9, 2025
af3092f
Add type hint for thread stack mapping in tracer wrapper code
Oct 9, 2025
ce31c97
Add test for unknown tracer event types
Oct 9, 2025
b3cc720
Add Test Your Locks section to README
Oct 9, 2025
40e3aa7
Add notify and was_event_locked methods to README
Oct 9, 2025
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
72 changes: 41 additions & 31 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
name: Lint

on:
push
on: push

jobs:
build:

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

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
shell: bash
run: pip install -r requirements_dev.txt

- name: Run mypy
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 check locklib

- name: Run ruff for tests
shell: bash
run: ruff check tests
- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ github.workflow }}-

- name: Install dependencies
shell: bash
run: pip install -r requirements_dev.txt

- name: Install the library
shell: bash
run: pip install .

- name: Run ruff
shell: bash
run: ruff check locklib

- name: Run ruff for tests
shell: bash
run: ruff check tests

- name: Run mypy
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
34 changes: 34 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Publish the package

on:
push:
branches:
- main

jobs:
pypi-publish:
name: upload release to PyPI
runs-on: ubuntu-latest
# Specifying a GitHub environment is optional, but strongly encouraged
environment: release
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
shell: bash
run: pip install -r requirements_dev.txt

- name: Build the project
shell: bash
run: python -m build .

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
43 changes: 16 additions & 27 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
name: Release
name: Create Release on Merge

on:
push:
branches:
- main
pull_request:
types: [closed]

jobs:
pypi-publish:
name: upload release to PyPI
release:
if: >
github.event.pull_request.merged == true &&
github.base_ref == 'main' &&
github.head_ref == 'develop'
runs-on: ubuntu-latest
# Specifying a GitHub environment is optional, but strongly encouraged
environment: release
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
steps:
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
shell: bash
run: pip install -r requirements_dev.txt

- name: Build the project
shell: bash
run: python -m build .

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
tag_name: ${{ github.event.pull_request.title }}
release_name: ${{ github.event.pull_request.title }}
body: ${{ github.event.pull_request.body }}
69 changes: 38 additions & 31 deletions .github/workflows/tests_and_coverage.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,50 @@
name: New tests
name: Tests

on:
push
on: push

jobs:
build:

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

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
shell: bash
run: pip install -r requirements_dev.txt

- name: Install package
run: pip install .

- name: Run tests and show coverage on the command line
run: coverage run --source=locklib --omit="*tests*" -m pytest --cache-clear && coverage report -m --fail-under=100

- name: Upload reports to codecov
env:
CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
if: runner.os == 'Linux'
run: |
curl -Os https://uploader.codecov.io/latest/linux/codecov
find . -iregex "codecov.*"
chmod +x codecov
./codecov -t ${CODECOV_TOKEN}

- name: Run tests and show the branch coverage on the command line
run: coverage run --branch --source=locklib --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100
- name: Install dependencies
shell: bash
run: pip install -r requirements_dev.txt

- name: Install package
run: pip install .

- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ github.workflow }}-

- name: Run tests and show coverage on the command line
run: |
coverage run --source=locklib --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100
coverage xml

- name: Upload coverage to Coveralls
if: runner.os == 'Linux'
env:
COVERALLS_REPO_TOKEN: ${{secrets.COVERALLS_REPO_TOKEN}}
uses: coverallsapp/github-action@v2
with:
format: cobertura
file: coverage.xml

- name: Run tests and show the branch coverage on the command line
run: coverage run --branch --source=locklib --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=100
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
![logo](https://raw.githubusercontent.com/pomponchik/locklib/develop/docs/assets/logo_5.png)
![logo](https://raw.githubusercontent.com/pomponchik/locklib/develop/docs/assets/logo_7.svg)

[![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)
[![Coverage Status](https://coveralls.io/repos/github/pomponchik/locklib/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/locklib?branch=main)
[![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)
[![PyPI version](https://badge.fury.io/py/locklib.svg)](https://badge.fury.io/py/locklib)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pomponchik/locklib)

It contains several useful additions to the standard thread synchronization tools, such as lock protocols and locks with advanced functionality.

Expand All @@ -19,6 +20,7 @@ It contains several useful additions to the standard thread synchronization tool
- [**Installation**](#installation)
- [**Lock protocols**](#lock-protocols)
- [**SmartLock - deadlock is impossible with it**](#smartlock---deadlock-is-impossible-with-it)
- [**Test your locks**](#test-your-locks)


## Installation
Expand Down Expand Up @@ -170,3 +172,42 @@ If you want to catch the exception, import this from the `locklib` too:
```python
from locklib import DeadLockError
```


## Test your locks

Sometimes, when testing a code, you may need to detect if some action is taking place inside the lock. How to do this with a minimum of code? There is the `LockTraceWrapper` for this. It is a wrapper around a regular lock, which records it every time the code takes a lock or releases it. At the same time, the functionality of the wrapped lock is fully preserved.

It's easy to create an object of such a lock. Just pass any other lock to the class constructor:

```python
from threading import Lock
from locklib import LockTraceWrapper

lock = LockTraceWrapper(Lock())
```

You can use it in the same way as the wrapped lock:

```python
with lock:
...
```

Anywhere in your program, you can "inform" the lock that the action you need is being performed here:

```python
lock.notify('event_name')
```

And! Now you can easily identify if there were cases when an event with this identifier did not occur under the mutex. To do this, use the `was_event_locked` method:

```python
lock.was_event_locked('event_name')
```

If the `notify` method was called with the same parameter only when the lock activated, it will return `True`. If not, that is, if there was at least one case when the c method was called with such an identifier without an activated mutex, `False` will be returned.

How does it work? A modified [algorithm for determining the correct parenthesis sequence](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%81%D0%BA%D0%BE%D0%B1%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C) is used here. For each thread for which any events were registered (taking the mutex, releasing the mutex, and also calling the `notify` method), the check takes place separately, that is, we determine that it was the same thread that held the mutex when `notify` was called, and not some other one.

> ⚠️ The thread id is used to identify the streams. This id may be reused if the current thread ends, which in some cases may lead to incorrect identification of lock coverage for operations that were not actually covered by the lock. Make sure that this cannot happen during your test.
Loading
Loading