Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand":
"python -m pip install '.[build,test,development,documentation]'",
"python -m pip install '.[all]' --group all",

// Configure tool-specific properties.
"customizations": {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/code-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ".[build,test,development]"
python -m pip install ".[all]" --group all
- name: Check
run: |
invoke project.pre-commit
2 changes: 1 addition & 1 deletion .github/workflows/python-avatar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install .[avatar]
python -m pip install .[all,avatar]
- name: Rootcanal
run: nohup python -m rootcanal > rootcanal.log &
- name: Test
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ".[build,test,development,documentation]"
python -m pip install ".[all]" --group all
- name: Test
run: |
invoke test
Expand All @@ -62,7 +62,7 @@ jobs:
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ".[build,test,development,documentation]"
python -m pip install ".[all]" --group all
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ To install package dependencies needed to run the bumble examples, execute the f

```
python -m pip install --upgrade pip
python -m pip install ".[test,development,documentation]"
python -m pip install ".[all]" --group dev
```

### Examples
Expand Down
4 changes: 1 addition & 3 deletions apps/auracast.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
try:
import lc3 # type: ignore # pylint: disable=E0401
except ImportError as e:
raise ImportError(
"Try `python -m pip install \"git+https://github.com/google/liblc3.git\"`."
) from e
raise ImportError("Try `python -m pip install '.[auracast]'`.") from e

import bumble.device
import bumble.logging
Expand Down
2 changes: 1 addition & 1 deletion apps/lea_unicast/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
try:
import lc3 # type: ignore # pylint: disable=E0401
except ImportError as e:
raise ImportError("Try `python -m pip install \".[lc3]\"`.") from e
raise ImportError("Try `python -m pip install \".[auracast]\"`.") from e

import aiohttp.web
import click
Expand Down
52 changes: 26 additions & 26 deletions bumble/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import json
import logging
import os
import pathlib
from typing import TYPE_CHECKING, Any

from typing_extensions import Self
Expand Down Expand Up @@ -248,29 +249,26 @@ class without a namespace. With the default namespace, reading from a file will
DEFAULT_NAMESPACE = '__DEFAULT__'
DEFAULT_BASE_NAME = "keys"

def __init__(self, namespace, filename=None):
self.namespace = namespace if namespace is not None else self.DEFAULT_NAMESPACE
def __init__(
self, namespace: str | None = None, filename: str | None = None
) -> None:
self.namespace = namespace or self.DEFAULT_NAMESPACE

if filename is None:
# Use a default for the current user
if filename:
self.filename = pathlib.Path(filename).resolve()
self.directory_name = self.filename.parent
else:
import platformdirs # Deferred import

# Import here because this may not exist on all platforms
# pylint: disable=import-outside-toplevel
import appdirs
base_dir = platformdirs.user_data_path(self.APP_NAME, self.APP_AUTHOR)
self.directory_name = base_dir / self.KEYS_DIR

self.directory_name = os.path.join(
appdirs.user_data_dir(self.APP_NAME, self.APP_AUTHOR), self.KEYS_DIR
)
base_name = self.DEFAULT_BASE_NAME if namespace is None else self.namespace
json_filename = (
f'{base_name}.json'.lower().replace(':', '-').replace('/p', '-p')
)
self.filename = os.path.join(self.directory_name, json_filename)
else:
self.filename = filename
self.directory_name = os.path.dirname(os.path.abspath(self.filename))
base_name = self.namespace if namespace else self.DEFAULT_BASE_NAME
safe_name = base_name.lower().replace(':', '-').replace('/', '-')

self.filename = self.directory_name / f"{safe_name}.json"

logger.debug(f'JSON keystore: {self.filename}')
logger.debug('JSON keystore: %s', self.filename)

@classmethod
def from_device(
Expand All @@ -293,7 +291,9 @@ def from_device(

return cls(namespace, filename)

async def load(self):
async def load(
self,
) -> tuple[dict[str, dict[str, dict[str, Any]]], dict[str, dict[str, Any]]]:
# Try to open the file, without failing. If the file does not exist, it
# will be created upon saving.
try:
Expand All @@ -312,17 +312,17 @@ async def load(self):
return next(iter(db.items()))

# Finally, just create an empty key map for the namespace
key_map = {}
key_map: dict[str, dict[str, Any]] = {}
db[self.namespace] = key_map
return (db, key_map)

async def save(self, db):
async def save(self, db: dict[str, dict[str, dict[str, Any]]]) -> None:
# Create the directory if it doesn't exist
if not os.path.exists(self.directory_name):
os.makedirs(self.directory_name, exist_ok=True)

# Save to a temporary file
temp_filename = self.filename + '.tmp'
temp_filename = self.filename.with_name(self.filename.name + ".tmp")
with open(temp_filename, 'w', encoding='utf-8') as output:
json.dump(db, output, sort_keys=True, indent=4)

Expand All @@ -334,16 +334,16 @@ async def delete(self, name: str) -> None:
del key_map[name]
await self.save(db)

async def update(self, name, keys):
async def update(self, name: str, keys: PairingKeys) -> None:
db, key_map = await self.load()
key_map.setdefault(name, {}).update(keys.to_dict())
await self.save(db)

async def get_all(self):
async def get_all(self) -> list[tuple[str, PairingKeys]]:
_, key_map = await self.load()
return [(name, PairingKeys.from_dict(keys)) for (name, keys) in key_map.items()]

async def delete_all(self):
async def delete_all(self) -> None:
db, key_map = await self.load()
key_map.clear()
await self.save(db)
Expand Down
76 changes: 48 additions & 28 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ license-files = ["LICENSE"]
authors = [{ name = "Google", email = "bumble-dev@google.com" }]
requires-python = ">=3.10"
dependencies = [
"aiohttp ~= 3.8; platform_system!='Emscripten'",
"appdirs >= 1.4; platform_system!='Emscripten'",
"click >= 8.1.3; platform_system!='Emscripten'",
"cryptography >= 44.0.3; platform_system!='Emscripten' and platform_system!='Android'",
# Pyodide bundles a version of cryptography that is built for wasm, which may not match the
# versions available on PyPI. Relax the version requirement since it's better than being
Expand All @@ -25,31 +22,52 @@ dependencies = [
# updated. Relax the version requirement since it's better than being completely unable
# to import the package in case of version mismatch.
"cryptography >= 42.0.8; platform_system=='Android'",
"grpcio >= 1.62.1; platform_system!='Emscripten'",
"humanize >= 4.6.0; platform_system!='Emscripten'",
"libusb1 >= 2.0.1; platform_system!='Emscripten'",
"libusb-package == 1.0.26.1; platform_system!='Emscripten' and platform_system!='Android'",
"platformdirs >= 3.10.0; platform_system!='Emscripten'",
"prompt_toolkit >= 3.0.16; platform_system!='Emscripten'",
"prettytable >= 3.6.0; platform_system!='Emscripten'",
"protobuf >= 3.12.4; platform_system!='Emscripten'",
"pyee >= 13.0.0",
"pyserial-asyncio >= 0.5; platform_system!='Emscripten'",
"pyserial >= 3.5; platform_system!='Emscripten'",
"pyusb >= 1.2; platform_system!='Emscripten'",
"tomli ~= 2.2.1; platform_system!='Emscripten' and python_version<'3.11'",
"websockets >= 15.0.1; platform_system!='Emscripten'",
"platformdirs >= 3.10.0; platform_system!='Emscripten'",
]

[project.optional-dependencies]
build = ["build >= 0.7"]
test = [
avatar = [
"pandora-avatar == 0.0.10",
"rootcanal == 1.11.1 ; python_version>='3.10'",
]
pandora = ["bt-test-interfaces >= 0.0.6"]
auracast = [
"lc3py >= 1.1.3; python_version>='3.10' and ((platform_system=='Linux' and platform_machine=='x86_64') or (platform_system=='Darwin' and platform_machine=='arm64'))",
"sounddevice >= 0.5.1",
]
app = [
"aiohttp ~= 3.8",
"click >= 8.1.3",
"humanize >= 4.6.0",
"prompt_toolkit >= 3.0.16",
"prettytable >= 3.6.0",
"tomli ~= 2.2.1; python_version<'3.11'",
]
transport = [
"grpcio >= 1.62.1",
"libusb1 >= 2.0.1",
"libusb-package == 1.0.26.1; platform_system!='Android'",
"protobuf >= 3.12.4",
"pyserial-asyncio >= 0.5",
"pyserial >= 3.5",
"pyusb >= 1.2",
"websockets >= 15.0.1",
]
all = [
"bumble[auracast]",
"bumble[app]",
"bumble[transport]",
]


[dependency-groups]
dev = [
"build >= 0.7",
"pytest >= 8.2",
"pytest-asyncio >= 0.23.5",
"pytest-html >= 3.2.0",
"coverage >= 6.4",
]
development = [
"black ~= 25.1",
"bt-test-interfaces >= 0.0.6",
"grpcio-tools >= 1.62.1",
Expand All @@ -64,21 +82,23 @@ development = [
"types-invoke >= 1.7.3",
"types-protobuf >= 4.21.0",
]
avatar = [
"pandora-avatar == 0.0.10",
"rootcanal == 1.11.1 ; python_version>='3.10'",
]
pandora = ["bt-test-interfaces >= 0.0.6"]
documentation = [
"mkdocs >= 1.6.0",
"mkdocs-material >= 9.6",
"mkdocstrings[python] >= 0.27.0",
]
auracast = [
"lc3py >= 1.1.3; python_version>='3.10' and ((platform_system=='Linux' and platform_machine=='x86_64') or (platform_system=='Darwin' and platform_machine=='arm64'))",
"sounddevice >= 0.5.1",
all = [
{include-group = "dev"},
{include-group = "documentation"},
]

[tool.uv]
default-groups = [
"dev",
"documentation",
]


[project.scripts]
bumble-auracast = "bumble.apps.auracast:main"
bumble-ble-rpa-tool = "bumble.apps.ble_rpa_tool:main"
Expand Down
Loading