From ff6d7323c95ba15a1f641601833192fbc983b1de Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 25 Nov 2024 12:31:26 -0700 Subject: [PATCH 1/6] Support free-threaded Python 3.13 --- .github/workflows/ci.yml | 9 ++++++--- src/_bcrypt/src/lib.rs | 2 +- tests/test_bcrypt.py | 1 + tox.ini | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59f46203..f938cfe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: PYTHON: - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.13", TOXENV: "py313"} + - {VERSION: "3.13t", TOXENV: "py313t"} MACOS: - macos-13 - macos-latest @@ -24,7 +25,7 @@ jobs: - uses: actions/checkout@v4.2.2 - name: Setup python id: setup-python - uses: actions/setup-python@v5.3.0 + uses: quansight-labs/setup-python@v5.3.1 with: python-version: ${{ matrix.PYTHON.VERSION }} - uses: actions/cache@v4.2.0 @@ -53,12 +54,13 @@ jobs: PYTHON: - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.13", TOXENV: "py313"} + - {VERSION: "3.13t", TOXENV: "py313t"} name: "Python ${{ matrix.PYTHON.VERSION }} on ${{ matrix.WINDOWS.WINDOWS }}" steps: - uses: actions/checkout@v4.2.2 - name: Setup python id: setup-python - uses: actions/setup-python@v5.3.0 + uses: quansight-labs/setup-python@v5.3.1 with: python-version: ${{ matrix.PYTHON.VERSION }} architecture: ${{ matrix.WINDOWS.ARCH }} @@ -92,6 +94,7 @@ jobs: - {VERSION: "3.11", TOXENV: "py311"} - {VERSION: "3.12", TOXENV: "py312"} - {VERSION: "3.13", TOXENV: "py313"} + - {VERSION: "3.13", TOXENV: "py313t"} - {VERSION: "pypy-3.9", TOXENV: "pypy3"} - {VERSION: "pypy-3.10", TOXENV: "pypy3"} @@ -104,7 +107,7 @@ jobs: - uses: actions/checkout@v4.2.2 - name: Setup python id: setup-python - uses: actions/setup-python@v5.3.0 + uses: quansight-labs/setup-python@v5.3.1 with: python-version: ${{ matrix.PYTHON.VERSION }} - uses: actions/cache@v4.2.0 diff --git a/src/_bcrypt/src/lib.rs b/src/_bcrypt/src/lib.rs index 3159bf95..127f2e78 100644 --- a/src/_bcrypt/src/lib.rs +++ b/src/_bcrypt/src/lib.rs @@ -182,7 +182,7 @@ fn kdf<'p>( }) } -#[pyo3::pymodule] +#[pyo3::pymodule(gil_used = false)] mod _bcrypt { use pyo3::types::PyModuleMethods; diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py index b0e0182a..08633cf9 100644 --- a/tests/test_bcrypt.py +++ b/tests/test_bcrypt.py @@ -464,6 +464,7 @@ def test_kdf_no_warn_rounds(): bcrypt.kdf(b"password", b"salt", 10, 10, True) +@pytest.mark.thread_unsafe() def test_kdf_warn_rounds(): with pytest.warns(UserWarning): bcrypt.kdf(b"password", b"salt", 10, 10) diff --git a/tox.ini b/tox.ini index dfe3e5bb..844feae9 100644 --- a/tox.ini +++ b/tox.ini @@ -6,10 +6,11 @@ extras = tests deps = coverage + pytest-run-parallel passenv = RUSTUP_HOME commands = - coverage run -m pytest --strict-markers {posargs} + coverage run -m pytest --parallel-threads=10 --strict-markers {posargs} coverage combine coverage report -m --fail-under 100 From 61fe5b636fe2c18e1dab8c5ac9249a0ac6c554b8 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 25 Nov 2024 13:00:50 -0700 Subject: [PATCH 2/6] fix issues with tox.ini --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f938cfe1..9ee9adfd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: PYTHON: - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.13", TOXENV: "py313"} - - {VERSION: "3.13t", TOXENV: "py313t"} + - {VERSION: "3.13t", TOXENV: "py313"} MACOS: - macos-13 - macos-latest @@ -54,7 +54,7 @@ jobs: PYTHON: - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.13", TOXENV: "py313"} - - {VERSION: "3.13t", TOXENV: "py313t"} + - {VERSION: "3.13t", TOXENV: "py313"} name: "Python ${{ matrix.PYTHON.VERSION }} on ${{ matrix.WINDOWS.WINDOWS }}" steps: - uses: actions/checkout@v4.2.2 @@ -94,7 +94,7 @@ jobs: - {VERSION: "3.11", TOXENV: "py311"} - {VERSION: "3.12", TOXENV: "py312"} - {VERSION: "3.13", TOXENV: "py313"} - - {VERSION: "3.13", TOXENV: "py313t"} + - {VERSION: "3.13t", TOXENV: "py313"} - {VERSION: "pypy-3.9", TOXENV: "pypy3"} - {VERSION: "pypy-3.10", TOXENV: "pypy3"} From c72b363077f9291b49954f279c6d24bdbad87536 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 26 Nov 2024 12:37:30 -0700 Subject: [PATCH 3/6] add multithreading test --- README.rst | 2 +- tests/test_bcrypt.py | 48 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index d2e76984..86e02c10 100644 --- a/README.rst +++ b/README.rst @@ -285,7 +285,7 @@ Compatibility ------------- This library should be compatible with py-bcrypt and it will run on Python -3.6+, and PyPy 3. +3.8+ (including free-threaded builds), and PyPy 3. Security -------- diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py index 08633cf9..5f677791 100644 --- a/tests/test_bcrypt.py +++ b/tests/test_bcrypt.py @@ -1,6 +1,11 @@ -import pytest +import random +import uuid + +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass import bcrypt +import pytest _test_vectors = [ ( @@ -171,7 +176,7 @@ ] -def test_gensalt_basic(monkeypatch): +def test_gensalt_basic(): salt = bcrypt.gensalt() assert salt.startswith(b"$2b$12$") @@ -219,7 +224,7 @@ def test_gensalt_bad_prefix(): bcrypt.gensalt(prefix=b"bad") -def test_gensalt_2a_prefix(monkeypatch): +def test_gensalt_2a_prefix(): salt = bcrypt.gensalt(prefix=b"2a") assert salt.startswith(b"$2a$12$") @@ -495,3 +500,40 @@ def test_2a_wraparound_bug(): ) == b"$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi" ) + + +@pytest.mark.thread_unsafe() +def test_multithreading(): + def get_id(): + return uuid.uuid4().bytes + + class User: + def __init__(self, id_, pw): + self.id_ = id_ + self.salt = bcrypt.gensalt(4) + self.hash_ = bcrypt.hashpw(pw, self.salt) + self.key = bcrypt.kdf(pw, self.salt, 32, 50) + assert self.check(pw) + + def check(self, pw): + return bcrypt.checkpw(pw, self.hash_) + + # use UUIDs as both ID and passwords + NUM_USERS = 50 + ids = [get_id() for _ in range(NUM_USERS)] + pws = {id_: get_id() for id_, _ in zip(ids, range(NUM_USERS))} + + user_creator = ThreadPoolExecutor(max_workers=4) + + def create_user(id_, pw): + return id_, User(id_, pw) + + creator_futures = [ + user_creator.submit(create_user, id_, pw) for id_, pw in pws.items()] + + users = [future.result() for future in creator_futures] + + for id_, user in users: + assert bcrypt.hashpw(pws[id_], user.salt) == user.hash_ + assert user.check(pws[id_]) + assert bcrypt.kdf(pws[id_], user.salt, 32, 50) == user.key From 2a0c879d7500ee1d25b7c01c4496b04c397a503f Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 26 Nov 2024 12:45:45 -0700 Subject: [PATCH 4/6] fix pep8 --- tests/test_bcrypt.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py index 5f677791..b9a4d8ed 100644 --- a/tests/test_bcrypt.py +++ b/tests/test_bcrypt.py @@ -1,12 +1,10 @@ -import random import uuid - from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -import bcrypt import pytest +import bcrypt + _test_vectors = [ ( b"Kk4DQuMMfZL9o", @@ -519,9 +517,9 @@ def check(self, pw): return bcrypt.checkpw(pw, self.hash_) # use UUIDs as both ID and passwords - NUM_USERS = 50 - ids = [get_id() for _ in range(NUM_USERS)] - pws = {id_: get_id() for id_, _ in zip(ids, range(NUM_USERS))} + num_users = 50 + ids = [get_id() for _ in range(num_users)] + pws = {id_: get_id() for id_, _ in zip(ids, range(num_users))} user_creator = ThreadPoolExecutor(max_workers=4) From 918756178ed39c5fd19b87c74d7fec6c3b49fffe Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Tue, 26 Nov 2024 12:49:31 -0700 Subject: [PATCH 5/6] fix ruff format --- tests/test_bcrypt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py index b9a4d8ed..c447c3f6 100644 --- a/tests/test_bcrypt.py +++ b/tests/test_bcrypt.py @@ -527,7 +527,8 @@ def create_user(id_, pw): return id_, User(id_, pw) creator_futures = [ - user_creator.submit(create_user, id_, pw) for id_, pw in pws.items()] + user_creator.submit(create_user, id_, pw) for id_, pw in pws.items() + ] users = [future.result() for future in creator_futures] From c65835f50bdaea6a7a9e0d619462c2b3ddfe8254 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Fri, 6 Dec 2024 10:58:21 -0700 Subject: [PATCH 6/6] name linux CI run based on python VERSION --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ee9adfd..e44b12b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: - {VERSION: "3.13", TOXENV: "py313", RUST_VERSION: "1.64.0"} - {VERSION: "3.13", TOXENV: "py313", RUST_VERSION: "beta"} - {VERSION: "3.13", TOXENV: "py313", RUST_VERSION: "nightly"} - name: "${{ matrix.PYTHON.TOXENV }} on linux, Rust ${{ matrix.PYTHON.RUST_VERSION || 'stable' }}" + name: "${{ matrix.PYTHON.VERSION }} on linux, Rust ${{ matrix.PYTHON.RUST_VERSION || 'stable' }}" steps: - uses: actions/checkout@v4.2.2 - name: Setup python