Skip to content

bleak/pairing: add the pairing callback API#1992

Open
JPHutchins wants to merge 1 commit into
hbldh:developfrom
JPHutchins:pairing-api
Open

bleak/pairing: add the pairing callback API#1992
JPHutchins wants to merge 1 commit into
hbldh:developfrom
JPHutchins:pairing-api

Conversation

@JPHutchins

Copy link
Copy Markdown
Contributor

Split out of #1990 — the backend-agnostic foundation, with no wiring yet so it can be reviewed on its own.

What

Adds a new bleak.pairing module:

  • PairingCallbacks — a NamedTuple of optional async handlers: confirm(device, passkey) (Numeric Comparison) and request_passkey(device) / display_passkey(device, passkey) (Passkey Entry, both directions).
  • IOCapability — the advertised Security Manager I/O capability.
  • io_capability(callbacks) — derives the capability from which callbacks are set (e.g. confirm implies a display).
  • MAX_PASSKEY — the 6-digit passkey ceiling.

Which callbacks are provided drives the negotiated pairing ceremony; with none, Just Works is accepted automatically (unchanged behavior). The backends consume this in follow-up PRs.

Testing

  • Unit tests covering io_capability() across every callback combination.
  • mypy + pyright (strict) clean.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 1, 2026 00:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new pairing API surface (callbacks + IO capability derivation) and a focused unit test suite to validate capability selection from provided callbacks.

Changes:

  • Introduces bleak.pairing with PairingCallbacks, IOCapability, and io_capability().
  • Documents BLE pairing ceremonies and how callbacks map to advertised IO capabilities.
  • Adds parametrized tests to validate io_capability() behavior across callback combinations.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
bleak/pairing.py Adds pairing types + capability derivation function and documentation.
tests/test_pairing.py Adds parametrized tests for callback-to-capability mapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bleak/pairing.py
Comment thread bleak/pairing.py Outdated
Comment thread bleak/pairing.py Outdated
@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.98%. Comparing base (ae2f589) to head (1cb129e).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1992      +/-   ##
===========================================
+ Coverage    52.45%   52.98%   +0.52%     
===========================================
  Files           43       44       +1     
  Lines         4097     4143      +46     
  Branches       504      508       +4     
===========================================
+ Hits          2149     2195      +46     
  Misses        1817     1817              
  Partials       131      131              
Flag Coverage Δ
bluez-integration-py310 39.80% <97.82%> (+0.65%) ⬆️
bluez-integration-py311 39.80% <97.82%> (+0.65%) ⬆️
bluez-integration-py312 39.80% <97.82%> (+0.65%) ⬆️
bluez-integration-py313 39.80% <97.82%> (+0.65%) ⬆️
bluez-integration-py314 38.33% <97.82%> (+0.68%) ⬆️
macos-latest-py310 20.97% <97.82%> (+0.93%) ⬆️
macos-latest-py311 20.97% <97.82%> (+0.93%) ⬆️
macos-latest-py312 20.97% <97.82%> (+0.93%) ⬆️
macos-latest-py313 20.97% <97.82%> (+0.93%) ⬆️
macos-latest-py314 20.81% <97.82%> (+0.96%) ⬆️
ubuntu-latest-py310 24.83% <97.82%> (+0.89%) ⬆️
ubuntu-latest-py311 24.83% <97.82%> (+0.89%) ⬆️
ubuntu-latest-py312 24.83% <97.82%> (+0.89%) ⬆️
ubuntu-latest-py313 24.83% <97.82%> (+0.89%) ⬆️
ubuntu-latest-py314 22.98% <97.82%> (+0.93%) ⬆️
windows-latest-py310 19.67% <97.82%> (+0.95%) ⬆️
windows-latest-py311 19.67% <97.82%> (+0.95%) ⬆️
windows-latest-py312 19.67% <97.82%> (+0.95%) ⬆️
windows-latest-py313 19.67% <97.82%> (+0.95%) ⬆️
windows-latest-py314 19.42% <97.82%> (+0.97%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment thread bleak/pairing.py
Comment thread bleak/pairing.py Outdated
"""The largest valid BLE passkey; passkeys are six decimal digits (000000-999999)."""


class PairingCallbacks(NamedTuple):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this was explained in the other PR, but why is this a NamedTuple instead of a regular class?

I think a regular class would be nice since it would have named parameters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedTuple has named fields, it's a struct. This is better than a class because it's fit for the specific purpose: a collection of optional functions.

The other option would be a frozen dataclass, or both + a protocol to allow users to create their own object. I believe that would allow users to define a class with static methods and properties to fulfill the protocol, but that's very clunky compared to a struct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: the specific question, NamedTuples do support named parameters:

# Passkey Entry (input) — type in the value the peer is displaying
async def request_passkey(device: BLEDevice) -> int | None:
    raw = input(f"Enter the passkey shown on {device.name}: ")
    return int(raw) if raw else None

callbacks = PairingCallbacks(request_passkey=request_passkey)
# every field passed by name, order irrelevant
callbacks = PairingCallbacks(
    display_passkey=display_passkey,
    request_passkey=request_passkey,
)

Usage

>>> callbacks = PairingCallbacks(confirm=confirm)
>>> callbacks.confirm is confirm
True
>>> callbacks.request_passkey is None      # unset fields default to None
True
>>> io_capability(callbacks)
<IOCapability.DISPLAY_YES_NO: 2>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the suggestion of a protocol, but I don't think that works technically with optional methods.

dataclass doesn't seem quite right since we don't care about equality and things like that (we are just dealing with functions).

Re: the parameters, I meant the parameters of the Callable types.

Anyway, in general I do tend to prefer functional style programming over object oriented, so in that regard the NamedTuple is nice. I see pros and cons each way and not a clear "best" option.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: the parameters, I meant the parameters of the Callable types.

Aw, good point! I'm going to experiment with a protocol union that allows users to impl ABCs that satisfy some subset of the callbacks. The lib code will be slightly more complex, but the user code will be more self-documenting.

Comment thread bleak/pairing.py Outdated


@runtime_checkable
class CanConfirm(Protocol):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class CanConfirm(Protocol):
class SupportsConfirm(Protocol):

There seems to be a convention already in Python to use Supports for protocols.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixed.

Comment thread bleak/pairing.py Outdated
"""


class ConfirmBase(ABC, CanConfirm):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need these ABCs? It seems like we could just make the protocol @runtime_checkable and inherit from that directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigating!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seem to be narrowing on something elegant: combining abstractmethod with the runtime_checkable. Appears to be PEP 544 compliant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed that version, I like it a lot.

This is the foundation the BlueZ and WinRT pairing implementations build on.
Supersedes the bleak.agent module from hbldh#1864 / hbldh#1990 (renamed to
bleak.pairing; "agent" is a BlueZ-ism).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread bleak/_compat.py
Comment thread bleak/_compat.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants