pact-python-ffi 0.5.12.0__tar.gz → 0.5.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pact-python-ffi
3
- Version: 0.5.12.0
3
+ Version: 0.5.12.1
4
4
  Summary: Python bindings for the Pact FFI library
5
5
  Project-URL: Bug Tracker, https://github.com/pact-foundation/pact-python/issues
6
6
  Project-URL: Changelog, https://github.com/pact-foundation/pact-python/blob/main/pact-python-ffi/CHANGELOG.md
@@ -28,6 +28,7 @@ Classifier: Programming Language :: Python :: 3.14
28
28
  Classifier: Programming Language :: Python :: 3.15
29
29
  Classifier: Topic :: Software Development :: Testing
30
30
  Requires-Python: >=3.11
31
+ Requires-Dist: cffi>=2.1.1; python_version >= '3.15'
31
32
  Requires-Dist: cffi~=2.0
32
33
  Description-Content-Type: text/markdown
33
34
 
@@ -12,6 +12,7 @@ import os
12
12
  import shutil
13
13
  import subprocess
14
14
  import sys
15
+ import sysconfig
15
16
  import tarfile
16
17
  import tempfile
17
18
  import urllib.request
@@ -30,6 +31,21 @@ PKG_DIR = Path(__file__).parent.resolve() / "src" / "pact_ffi"
30
31
  PACT_RELEASE_URL = "https://github.com/pact-foundation/pact-reference/releases/download/pact_ffi-v{version}"
31
32
 
32
33
 
34
+ def _stable_abi() -> str:
35
+ """
36
+ Determine the stable ABI targeted by the current interpreter.
37
+
38
+ CFFI compiles against `abi3t` on free-threaded CPython 3.15+ and against
39
+ `abi3` otherwise. Free-threaded interpreters do not load `abi3` extensions.
40
+
41
+ Returns:
42
+ Either `abi3` or `abi3t`.
43
+ """
44
+ if sysconfig.get_config_var("Py_GIL_DISABLED"):
45
+ return "abi3t"
46
+ return "abi3"
47
+
48
+
33
49
  class UnsupportedPlatformError(RuntimeError):
34
50
  """
35
51
  Custom error raised when the current platform is not supported.
@@ -199,12 +215,12 @@ class PactBuildHook(BuildHookInterface[Any]):
199
215
  # Compile the FFI extension
200
216
  extension = self._compile(lib, header)
201
217
 
202
- # Copy into the package directory, using the ABI3 marking for broad
203
- # compatibility.
218
+ # Copy into the package directory, using the stable ABI marking for
219
+ # broad compatibility.
204
220
  # NOTE: Windows does _not_ use the version infixation
205
221
  name = extension.name.split(".")[0]
206
222
  suffix = extension.suffix
207
- infix = ".abi3" if os.name != "nt" else ""
223
+ infix = f".{_stable_abi()}" if os.name != "nt" else ""
208
224
  extension_dest = f"{name}{infix}{suffix}"
209
225
  shutil.copy(extension, PKG_DIR / extension_dest)
210
226
 
@@ -474,13 +490,17 @@ class PactBuildHook(BuildHookInterface[Any]):
474
490
  """
475
491
  Infer the tag for the current build.
476
492
 
477
- The bindings are built to target ABI3, which is compatible with multiple
478
- Python versions. As a result, we generate `py{version}-abi3-{platform}`
479
- tags for the wheels.
493
+ The bindings are built to target the stable ABI, which is compatible
494
+ with multiple Python versions. As a result, we generate
495
+ `cp{version}-abi3-{platform}` tags for the wheels.
480
496
 
481
497
  While the ABI3 interface was introduced in Python 3.2, we target the
482
498
  earliest supported version of Python in the Python wrapper.
483
499
 
500
+ A free-threaded build targets `abi3t`, which requires Python 3.15. Its
501
+ wheel takes the `cp{version}-abi3.abi3t-{platform}` tag, as the
502
+ extension also loads on GIL-enabled builds.
503
+
484
504
  Returns:
485
505
  The tag for the current build.
486
506
  """
@@ -501,4 +521,5 @@ class PactBuildHook(BuildHookInterface[Any]):
501
521
  else:
502
522
  raise UnsupportedPlatformError(platform)
503
523
 
504
- return f"cp{python_version}-abi3-{platform}"
524
+ abi = "abi3.abi3t" if _stable_abi() == "abi3t" else "abi3"
525
+ return f"cp{python_version}-{abi}-{platform}"
@@ -90,6 +90,7 @@ from __future__ import annotations
90
90
  import inspect
91
91
  import json
92
92
  import logging
93
+ import threading
93
94
  import typing
94
95
  import warnings
95
96
  from collections.abc import Callable
@@ -2522,8 +2523,10 @@ def logger_apply() -> int:
2522
2523
 
2523
2524
 
2524
2525
  # The CFFI callback must outlive the registration, as the Pact library holds a
2525
- # raw function pointer to it.
2526
+ # raw function pointer to it. The lock keeps the registered callback and the
2527
+ # stored reference in step when several threads register at once.
2526
2528
  _log_callback: Callable[..., None] | None = None
2529
+ _log_callback_lock = threading.Lock()
2527
2530
 
2528
2531
 
2529
2532
  def register_log_callback(callback: LogCallback | None) -> None:
@@ -2556,8 +2559,9 @@ def register_log_callback(callback: LogCallback | None) -> None:
2556
2559
  global _log_callback # noqa: PLW0603
2557
2560
 
2558
2561
  if callback is None:
2559
- lib.pactffi_register_log_callback(ffi.NULL)
2560
- _log_callback = None
2562
+ with _log_callback_lock:
2563
+ lib.pactffi_register_log_callback(ffi.NULL)
2564
+ _log_callback = None
2561
2565
  return
2562
2566
 
2563
2567
  def _decode(ptr: cffi.FFI.CData) -> str:
@@ -2580,8 +2584,9 @@ def register_log_callback(callback: LogCallback | None) -> None:
2580
2584
  )
2581
2585
 
2582
2586
  new_callback = ffi.callback("LogCallback", _callback)
2583
- lib.pactffi_register_log_callback(new_callback)
2584
- _log_callback = new_callback
2587
+ with _log_callback_lock:
2588
+ lib.pactffi_register_log_callback(new_callback)
2589
+ _log_callback = new_callback
2585
2590
 
2586
2591
 
2587
2592
  def set_log_callback_level(level_filter: LevelFilter | str) -> None:
@@ -8028,8 +8033,10 @@ def set_test_run_id(test_run_id: str | None) -> None:
8028
8033
 
8029
8034
 
8030
8035
  # The CFFI callback must outlive the registration, as the Pact library holds a
8031
- # raw function pointer to it.
8036
+ # raw function pointer to it. The lock keeps the registered callback and the
8037
+ # stored reference in step when several threads register at once.
8032
8038
  _plugin_log_callback: Callable[..., None] | None = None
8039
+ _plugin_log_callback_lock = threading.Lock()
8033
8040
 
8034
8041
 
8035
8042
  def register_plugin_log_callback(callback: PluginLogCallback | None) -> None:
@@ -8056,8 +8063,9 @@ def register_plugin_log_callback(callback: PluginLogCallback | None) -> None:
8056
8063
  global _plugin_log_callback # noqa: PLW0603
8057
8064
 
8058
8065
  if callback is None:
8059
- lib.pactffi_register_plugin_log_callback(ffi.NULL)
8060
- _plugin_log_callback = None
8066
+ with _plugin_log_callback_lock:
8067
+ lib.pactffi_register_plugin_log_callback(ffi.NULL)
8068
+ _plugin_log_callback = None
8061
8069
  return
8062
8070
 
8063
8071
  def _decode(ptr: cffi.FFI.CData) -> str:
@@ -8083,8 +8091,9 @@ def register_plugin_log_callback(callback: PluginLogCallback | None) -> None:
8083
8091
  "void(*)(const char *, const char *, const char *, const char *, const char *)",
8084
8092
  _callback,
8085
8093
  )
8086
- lib.pactffi_register_plugin_log_callback(new_callback)
8087
- _plugin_log_callback = new_callback
8094
+ with _plugin_log_callback_lock:
8095
+ lib.pactffi_register_plugin_log_callback(new_callback)
8096
+ _plugin_log_callback = new_callback
8088
8097
 
8089
8098
 
8090
8099
  def get_plugin_logs(plugin_instance_id: str) -> list[dict[str, Any]]:
@@ -1,5 +1,5 @@
1
1
  # Generated at build time from the project version. Do not edit.
2
- __version__ = version = '0.5.12.0'
2
+ __version__ = version = '0.5.12.1'
3
3
  __version_tuple__ = version_tuple = tuple(
4
4
  int(part) if part.isdigit() else part for part in __version__.split(".")
5
5
  )
@@ -2,7 +2,7 @@
2
2
 
3
3
  [project]
4
4
  name = "pact-python-ffi"
5
- version = "0.5.12.0"
5
+ version = "0.5.12.1"
6
6
  description = "Python bindings for the Pact FFI library"
7
7
  readme = "README.md"
8
8
  license = "MIT"
@@ -31,7 +31,11 @@ classifiers = [
31
31
 
32
32
  requires-python = ">=3.11"
33
33
 
34
- dependencies = ["cffi~=2.0"]
34
+ dependencies = [
35
+ # Earlier releases do not support Python 3.15.
36
+ "cffi>=2.1.1 ; python_version >= '3.15'",
37
+ "cffi~=2.0",
38
+ ]
35
39
 
36
40
  [project.urls]
37
41
  "Bug Tracker" = "https://github.com/pact-foundation/pact-python/issues"
@@ -41,12 +45,12 @@ dependencies = ["cffi~=2.0"]
41
45
  "Repository" = "https://github.com/pact-foundation/pact-python"
42
46
 
43
47
  [dependency-groups]
44
- dev = ["ruff==0.17.1", { include-group = "test" }, { include-group = "types" }]
48
+ dev = ["ruff==0.17.2", { include-group = "test" }, { include-group = "types" }]
45
49
  test = ["pytest~=9.0", "pytest-cov~=7.0"]
46
50
  types = ["mypy==2.4.0"]
47
51
 
48
52
  [build-system]
49
- requires = ["cffi", "hatchling", "packaging", "setuptools"]
53
+ requires = ["cffi>=2.1.1", "hatchling", "packaging", "setuptools"]
50
54
  build-backend = "hatchling.build"
51
55
 
52
56
  [tool]
@@ -68,6 +72,10 @@ build-backend = "hatchling.build"
68
72
  pact-ffi = ["/src/pact_ffi"]
69
73
  tests = ["/tests"]
70
74
 
75
+ [tool.coverage.run]
76
+ # Tests which apply a process-wide logger run in a separate interpreter.
77
+ patch = ["subprocess"]
78
+
71
79
  [tool.coverage.report]
72
80
  exclude_lines = [
73
81
  "@(abc\\.)?abstractmethod", # Ignore abstract methods
@@ -116,7 +124,7 @@ __version_tuple__ = version_tuple = tuple(
116
124
  dependency-groups = ["test"]
117
125
 
118
126
  [[tool.hatch.envs.test.matrix]]
119
- python = ["3.11", "3.12", "3.13", "3.14", "3.15"]
127
+ python = ["3.11", "3.12", "3.13", "3.14", "3.15", "3.15t"]
120
128
 
121
129
  ## PyTest Configuration
122
130
  [tool.pytest]