bidiwave 1.0.0__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.
- bidiwave-1.0.0/.github/workflows/ci.yml +19 -0
- bidiwave-1.0.0/.github/workflows/docs.yml +16 -0
- bidiwave-1.0.0/.github/workflows/release.yml +38 -0
- bidiwave-1.0.0/.github/workflows/test.yml +62 -0
- bidiwave-1.0.0/.gitignore +27 -0
- bidiwave-1.0.0/CHANGELOG.md +27 -0
- bidiwave-1.0.0/CONTEXT.md +233 -0
- bidiwave-1.0.0/PKG-INFO +127 -0
- bidiwave-1.0.0/README.md +90 -0
- bidiwave-1.0.0/bidiwave/__init__.py +71 -0
- bidiwave-1.0.0/bidiwave/_internal/__init__.py +0 -0
- bidiwave-1.0.0/bidiwave/_internal/logging.py +32 -0
- bidiwave-1.0.0/bidiwave/client.py +92 -0
- bidiwave-1.0.0/bidiwave/config.py +19 -0
- bidiwave-1.0.0/bidiwave/convenience/__init__.py +0 -0
- bidiwave-1.0.0/bidiwave/convenience/page.py +93 -0
- bidiwave-1.0.0/bidiwave/events/__init__.py +0 -0
- bidiwave-1.0.0/bidiwave/events/dispatcher.py +100 -0
- bidiwave-1.0.0/bidiwave/events/handlers.py +32 -0
- bidiwave-1.0.0/bidiwave/events/queue.py +62 -0
- bidiwave-1.0.0/bidiwave/exceptions.py +67 -0
- bidiwave-1.0.0/bidiwave/modules/__init__.py +0 -0
- bidiwave-1.0.0/bidiwave/modules/browsing.py +173 -0
- bidiwave-1.0.0/bidiwave/modules/script.py +59 -0
- bidiwave-1.0.0/bidiwave/modules/session.py +62 -0
- bidiwave-1.0.0/bidiwave/protocol/__init__.py +0 -0
- bidiwave-1.0.0/bidiwave/protocol/capabilities.py +45 -0
- bidiwave-1.0.0/bidiwave/protocol/commands.py +91 -0
- bidiwave-1.0.0/bidiwave/protocol/constants.py +36 -0
- bidiwave-1.0.0/bidiwave/protocol/events.py +86 -0
- bidiwave-1.0.0/bidiwave/protocol/remote_value.py +85 -0
- bidiwave-1.0.0/bidiwave/protocol/responses.py +45 -0
- bidiwave-1.0.0/bidiwave/protocol/results.py +38 -0
- bidiwave-1.0.0/bidiwave/transport/__init__.py +0 -0
- bidiwave-1.0.0/bidiwave/transport/connection.py +162 -0
- bidiwave-1.0.0/bidiwave/transport/correlation.py +37 -0
- bidiwave-1.0.0/bidiwave/transport/serializer.py +21 -0
- bidiwave-1.0.0/docs/api/browsing.md +5 -0
- bidiwave-1.0.0/docs/api/client.md +3 -0
- bidiwave-1.0.0/docs/api/config.md +3 -0
- bidiwave-1.0.0/docs/api/events.md +7 -0
- bidiwave-1.0.0/docs/api/exceptions.md +3 -0
- bidiwave-1.0.0/docs/api/remote-value.md +3 -0
- bidiwave-1.0.0/docs/api/script.md +3 -0
- bidiwave-1.0.0/docs/api/session.md +3 -0
- bidiwave-1.0.0/docs/cookbook.md +182 -0
- bidiwave-1.0.0/docs/error-handling.md +134 -0
- bidiwave-1.0.0/docs/index.md +46 -0
- bidiwave-1.0.0/docs/protocol-reference.md +331 -0
- bidiwave-1.0.0/docs/quick-start.md +105 -0
- bidiwave-1.0.0/docs/spike-notes.md +52 -0
- bidiwave-1.0.0/mkdocs.yml +67 -0
- bidiwave-1.0.0/pyproject.toml +69 -0
- bidiwave-1.0.0/spike.py +126 -0
- bidiwave-1.0.0/tests/__init__.py +0 -0
- bidiwave-1.0.0/tests/conftest.py +12 -0
- bidiwave-1.0.0/tests/contract/__init__.py +0 -0
- bidiwave-1.0.0/tests/integration/__init__.py +0 -0
- bidiwave-1.0.0/tests/integration/conftest.py +109 -0
- bidiwave-1.0.0/tests/integration/test_browsing.py +53 -0
- bidiwave-1.0.0/tests/integration/test_connection.py +23 -0
- bidiwave-1.0.0/tests/integration/test_ergonomics.py +63 -0
- bidiwave-1.0.0/tests/integration/test_events.py +69 -0
- bidiwave-1.0.0/tests/integration/test_hello_world.py +18 -0
- bidiwave-1.0.0/tests/integration/test_script.py +72 -0
- bidiwave-1.0.0/tests/integration/test_session.py +22 -0
- bidiwave-1.0.0/tests/unit/__init__.py +0 -0
- bidiwave-1.0.0/tests/unit/convenience/__init__.py +0 -0
- bidiwave-1.0.0/tests/unit/convenience/test_page.py +90 -0
- bidiwave-1.0.0/tests/unit/events/__init__.py +0 -0
- bidiwave-1.0.0/tests/unit/events/test_dispatcher.py +127 -0
- bidiwave-1.0.0/tests/unit/events/test_fluent_api.py +63 -0
- bidiwave-1.0.0/tests/unit/events/test_queue.py +73 -0
- bidiwave-1.0.0/tests/unit/protocol/__init__.py +0 -0
- bidiwave-1.0.0/tests/unit/protocol/test_capabilities.py +60 -0
- bidiwave-1.0.0/tests/unit/protocol/test_commands.py +73 -0
- bidiwave-1.0.0/tests/unit/protocol/test_events.py +58 -0
- bidiwave-1.0.0/tests/unit/protocol/test_remote_value.py +76 -0
- bidiwave-1.0.0/tests/unit/protocol/test_responses.py +59 -0
- bidiwave-1.0.0/tests/unit/test_context_managers.py +60 -0
- bidiwave-1.0.0/tests/unit/test_exceptions.py +50 -0
- bidiwave-1.0.0/tests/unit/transport/__init__.py +0 -0
- bidiwave-1.0.0/tests/unit/transport/test_correlation.py +51 -0
- bidiwave-1.0.0/tests/unit/transport/test_reconnect.py +94 -0
- bidiwave-1.0.0/tests/unit/transport/test_serializer.py +36 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: pip install -e ".[dev]"
|
|
18
|
+
- run: ruff check .
|
|
19
|
+
- run: mypy bidiwave/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
docs:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.12"
|
|
15
|
+
- run: pip install -e ".[docs]"
|
|
16
|
+
- run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- run: pip install -e ".[dev]"
|
|
20
|
+
- run: ruff check .
|
|
21
|
+
- run: mypy bidiwave/
|
|
22
|
+
- run: pytest tests/unit/ -c pyproject.toml
|
|
23
|
+
- run: python -m build
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish-pypi:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: pypi
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/download-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
unit-tests:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest]
|
|
16
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- run: pip install -e ".[dev]"
|
|
23
|
+
- run: ruff check .
|
|
24
|
+
- run: mypy bidiwave/
|
|
25
|
+
- run: pytest tests/unit/ --cov=bidiwave --cov-report=xml --cov-fail-under=90
|
|
26
|
+
- uses: codecov/codecov-action@v4
|
|
27
|
+
with:
|
|
28
|
+
file: ./coverage.xml
|
|
29
|
+
|
|
30
|
+
integration-chrome:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
strategy:
|
|
33
|
+
fail-fast: false
|
|
34
|
+
matrix:
|
|
35
|
+
python-version: ["3.12"]
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: ${{ matrix.python-version }}
|
|
41
|
+
- uses: browser-actions/setup-chrome@latest
|
|
42
|
+
with:
|
|
43
|
+
chrome-version: stable
|
|
44
|
+
- run: pip install -e ".[dev]"
|
|
45
|
+
- run: pytest tests/integration/ -m integration --browser=chrome
|
|
46
|
+
|
|
47
|
+
integration-firefox:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
strategy:
|
|
50
|
+
fail-fast: false
|
|
51
|
+
matrix:
|
|
52
|
+
python-version: ["3.12"]
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: actions/setup-python@v5
|
|
56
|
+
with:
|
|
57
|
+
python-version: ${{ matrix.python-version }}
|
|
58
|
+
- uses: browser-actions/setup-firefox@latest
|
|
59
|
+
with:
|
|
60
|
+
firefox-version: latest
|
|
61
|
+
- run: pip install -e ".[dev]"
|
|
62
|
+
- run: pytest tests/integration/ -m integration --browser=firefox
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
*.egg
|
|
9
|
+
.venv/
|
|
10
|
+
.venv-test/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
coverage.xml
|
|
18
|
+
htmlcov/
|
|
19
|
+
*.so
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
site/
|
|
25
|
+
|
|
26
|
+
# Driver binaries (downloaded on demand)
|
|
27
|
+
bin/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-07-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `BiDiClient` con `connect()`, `close()`, context manager (`async with`)
|
|
13
|
+
- `SessionModule` — `new()`, `status()`, `subscribe()`, `unsubscribe()`
|
|
14
|
+
- `BrowsingModule` — `create_context()`, `navigate()`, `close()`, `screenshot()`, `get_tree()`, `wait_for_selector()`, `wait_for_function()`, `open()`
|
|
15
|
+
- `ScriptModule` — `evaluate()`, `call_function()`, `disown()`
|
|
16
|
+
- `EventDispatcher` — `on()`, `off()`, fluent API, decorator mode, error isolation
|
|
17
|
+
- `Page` object — convenience layer con `evaluate()`, `screenshot()`, `wait_for_selector()`, `wait_for_function()`, `close()`
|
|
18
|
+
- `RemoteValue` con subtipos: `StringValue`, `NumberValue`, `BooleanValue`, `NullValue`, `UndefinedValue`, `BigIntValue`, `ObjectValue`, `ArrayValue`, `HandleValue`
|
|
19
|
+
- `Capabilities` — detection desde `session.status`
|
|
20
|
+
- `ClientConfig` — configuración tipada con Pydantic
|
|
21
|
+
- Reconnect con backoff exponencial
|
|
22
|
+
- Backpressure con drop policies (oldest, newest, block)
|
|
23
|
+
- Jerarquía de excepciones: `BiDiError`, `ConnectionError`, `TimeoutError`, `CapabilityError`, `CommandError`, `InvalidArgumentError`, `NoSuchFrameError`, `JavaScriptError`
|
|
24
|
+
- Cross-browser: Chrome, Firefox, Edge
|
|
25
|
+
- Logging estructurado
|
|
26
|
+
- CI: GitHub Actions con matrix Chrome + Firefox, Python 3.11/3.12/3.13
|
|
27
|
+
- Coverage > 90%
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# bidiwave — Contexto de desarrollo
|
|
2
|
+
|
|
3
|
+
## Estado actual
|
|
4
|
+
|
|
5
|
+
| Fase | Estado | Commit |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| 0. Setup + Spike | ✅ Completa | 1a047b1 |
|
|
8
|
+
| 1. Hello World | ✅ Completa | fdc10f5 |
|
|
9
|
+
| 2. Eventos | ✅ Completa | — |
|
|
10
|
+
| 3. Robustez | ✅ Completa | — |
|
|
11
|
+
| 4. Ergonomics | ✅ Completa | — |
|
|
12
|
+
| 5. Integration + CI | ✅ Completa | — |
|
|
13
|
+
| 6. Docs + Release | ✅ Completa | — |
|
|
14
|
+
|
|
15
|
+
## Phase 0 — Setup + Spike (completa)
|
|
16
|
+
|
|
17
|
+
- `pyproject.toml` con hatchling, ruff, mypy, pytest-asyncio
|
|
18
|
+
- Estructura: `bidiwave/{protocol,transport,modules}/`
|
|
19
|
+
- `spike.py` validó BiDi con Chrome y Edge via ChromeDriver/EdgeDriver
|
|
20
|
+
- `docs/spike-notes.md` con findings
|
|
21
|
+
- `bin/` gitignored (drivers descargados on-demand)
|
|
22
|
+
|
|
23
|
+
## Phase 1 — Hello World (completa)
|
|
24
|
+
|
|
25
|
+
### Archivos implementados
|
|
26
|
+
|
|
27
|
+
- `bidiwave/protocol/constants.py` — command names (`browsingContext.*`, `session.*`, `script.*`), event names, error codes
|
|
28
|
+
- `bidiwave/protocol/commands.py` — `Command`, `NewSessionParams`, `NavigateParams`, `EvaluateParams` (Pydantic v2, `extra="allow"`)
|
|
29
|
+
- `bidiwave/protocol/responses.py` — `SuccessResponse`, `ErrorResponse`, `ErrorData`, `parse_response` factory
|
|
30
|
+
- `bidiwave/protocol/capabilities.py` — `Capabilities` model, `detect_capabilities`
|
|
31
|
+
- `bidiwave/transport/serializer.py` — `serialize_command`, `deserialize_message`
|
|
32
|
+
- `bidiwave/transport/correlation.py` — `Correlator` (Future-based command/response matching)
|
|
33
|
+
- `bidiwave/transport/connection.py` — `Connection` (WebSocket receive loop, correlator, 30s timeout)
|
|
34
|
+
- `bidiwave/exceptions.py` — `BiDiError`, `ConnectionError`, `CommandError`
|
|
35
|
+
- `bidiwave/modules/session.py` — `SessionModule` (`new`, `status`)
|
|
36
|
+
- `bidiwave/modules/browsing.py` — `BrowsingModule` (`create_context`, `navigate`, `close`)
|
|
37
|
+
- `bidiwave/modules/script.py` — `ScriptModule` (`evaluate`)
|
|
38
|
+
- `bidiwave/client.py` — `BiDiClient.connect()` classmethod, `close()`
|
|
39
|
+
- `bidiwave/__init__.py` — exports públicos
|
|
40
|
+
- 28 unit tests en `tests/unit/`
|
|
41
|
+
- Integration test: `tests/integration/test_hello_world.py`
|
|
42
|
+
|
|
43
|
+
### Verificación
|
|
44
|
+
|
|
45
|
+
- `ruff check .` — pasa
|
|
46
|
+
- `mypy bidiwave/` — pasa (28 source files)
|
|
47
|
+
- `pytest tests/unit/ -c pyproject.toml` — 28 passed
|
|
48
|
+
- Integration test: navega a example.com, evalúa `document.title` → "Example Domain"
|
|
49
|
+
|
|
50
|
+
## Hallazgos clave
|
|
51
|
+
|
|
52
|
+
- Chrome/Edge necesitan ChromeDriver/EdgeDriver como proxy BiDi. El `--remote-debugging-port` expone CDP, no BiDi.
|
|
53
|
+
- Al conectar via ChromeDriver `webSocketUrl`, la sesión BiDi **ya existe**. `session.new` falla con "session already exists". Usar `session.status` para verificar.
|
|
54
|
+
- Los nombres de comandos son `browsingContext.*` (no `browsing.*` como decía el prompt original)
|
|
55
|
+
- `script.evaluate` anida el valor en `result["result"]["value"]`
|
|
56
|
+
- Chrome 149, Edge 149. ChromeDriver 149.0.7827.155, EdgeDriver 149.0.4022.98
|
|
57
|
+
- Driver paths: `bin/chromedriver-win64/chromedriver.exe`, `bin/edgedriver/msedgedriver.exe`
|
|
58
|
+
- pytest necesita `-c pyproject.toml` cuando se ejecuta desde `D:\Codigo\bidiwave` (el pyproject.toml de brainstorming interfiere)
|
|
59
|
+
|
|
60
|
+
## Phase 2 — Eventos (completa)
|
|
61
|
+
|
|
62
|
+
### Archivos implementados
|
|
63
|
+
|
|
64
|
+
- `protocol/events.py` — `Event`, `LogEntryAddedEvent`, `BrowsingContextCreatedEvent`, `BrowsingContextDestroyedEvent`, `BrowsingContextNavigatedEvent`, `ScriptMessageEvent`, `parse_event` factory
|
|
65
|
+
- `events/handlers.py` — `AsyncHandler` type, `Subscription` dataclass con weakref al dispatcher
|
|
66
|
+
- `events/queue.py` — `EventQueue` (asyncio.Queue wrapper, sin backpressure aún)
|
|
67
|
+
- `events/dispatcher.py` — `EventDispatcher` con `on()`, `off()`, `dispatch()`, error isolation
|
|
68
|
+
- `transport/connection.py` — modificado: receive loop ahora dispatcha eventos al `EventDispatcher`
|
|
69
|
+
- `modules/session.py` — añadido `subscribe()`, `unsubscribe()`
|
|
70
|
+
- `client.py` — añadido `on()`, `off()`, `on_log_entry()`, integración con `EventDispatcher`
|
|
71
|
+
- `__init__.py` — exports: `EventDispatcher`, `Subscription`, `AsyncHandler`
|
|
72
|
+
- 11 unit tests nuevos (6 dispatcher + 5 events) — total 39 passed
|
|
73
|
+
|
|
74
|
+
### Verificación
|
|
75
|
+
|
|
76
|
+
- `ruff check .` — pasa
|
|
77
|
+
- `mypy bidiwave/` — pasa (28 source files)
|
|
78
|
+
- `pytest tests/unit/ -c pyproject.toml` — 39 passed
|
|
79
|
+
- Pendiente: integration test contra Chrome real (console logs en tiempo real)
|
|
80
|
+
|
|
81
|
+
### Notas
|
|
82
|
+
|
|
83
|
+
- Los nombres de eventos se alinearon con `constants.py` y el spec real: `browsingContext.contextCreated` (no `browsing.contextCreated`)
|
|
84
|
+
- `BROWSING_CONTEXT_NAVIGATED` en constants.py es `browsingContext.navigationStarted` — el modelo `BrowsingContextNavigatedEvent` matchea ese nombre
|
|
85
|
+
- `Subscription` usa `weakref` al dispatcher para evitar reference cycles
|
|
86
|
+
- Error isolation: un handler que lanza excepción no afecta a otros handlers ni al receive loop
|
|
87
|
+
|
|
88
|
+
## Phase 3 — Robustez (completa)
|
|
89
|
+
|
|
90
|
+
### Archivos implementados / modificados
|
|
91
|
+
|
|
92
|
+
- `exceptions.py` — jerarquía completa: `TimeoutError`, `CapabilityError`, `ProtocolError`, `SessionError`, `InvalidArgumentError`, `NoSuchFrameError`, `NoSuchWindowError`, `JavaScriptError`, `ERROR_CODE_MAP`, `map_error()`
|
|
93
|
+
- `events/queue.py` — `EventQueue` con backpressure: drop policies `oldest`, `newest`, `block`, `dropped_count`
|
|
94
|
+
- `protocol/capabilities.py` — `Capabilities` con `supports_browsing/script/network/input`, `detect_capabilities` con heurística Firefox
|
|
95
|
+
- `transport/connection.py` — `TransportConfig` (timeout, max_retries, retry_delay, retry_backoff, max_queue, drop_policy), reconnect con backoff exponencial, `on_reconnect`/`on_disconnect` handlers, `map_error` en receive loop
|
|
96
|
+
- `protocol/commands.py` — `ScreenshotParams`, `CallFunctionParams`, `DisownParams`, `GetTreeParams`, `SubscribeParams`
|
|
97
|
+
- `protocol/constants.py` — añadido `SESSION_END`
|
|
98
|
+
- `modules/session.py` — añadido `end()`
|
|
99
|
+
- `modules/browsing.py` — añadido `screenshot()`, `get_tree()`
|
|
100
|
+
- `modules/script.py` — añadido `call_function()`, `disown()`
|
|
101
|
+
- `client.py` — `TransportConfig` param en `connect()`, `capabilities` property, `on_reconnect()`/`on_disconnect()`
|
|
102
|
+
- `__init__.py` — exports: `TransportConfig`, `Capabilities`, todas las excepciones nuevas
|
|
103
|
+
- 19 unit tests nuevos (5 queue + 4 capabilities + 6 exceptions + 4 reconnect) — total 58 passed
|
|
104
|
+
|
|
105
|
+
### Verificación
|
|
106
|
+
|
|
107
|
+
- `ruff check .` — pasa
|
|
108
|
+
- `mypy bidiwave/` — pasa (28 source files)
|
|
109
|
+
- `pytest tests/unit/ -c pyproject.toml` — 58 passed
|
|
110
|
+
- Pendiente: integration tests contra Chrome real (screenshot, get_tree, call_function, disown, reconnect)
|
|
111
|
+
|
|
112
|
+
### Notas
|
|
113
|
+
|
|
114
|
+
- `TransportConfig` usa Pydantic BaseModel para validación tipada
|
|
115
|
+
- Reconnect: backoff exponencial con `retry_delay * retry_backoff^attempt`, llama `on_reconnect` handlers tras reconectar
|
|
116
|
+
- `map_error` mapea códigos BiDi a subtipos de `CommandError` — el receive loop ahora usa `map_error` en vez de `CommandError` directo
|
|
117
|
+
- `detect_capabilities` usa heurística: Firefox soporta network/input, Chrome no (puede refinarse en Fase 5)
|
|
118
|
+
- `EventQueue` ahora es configurable pero no está integrada en el dispatcher aún (Fase 4 puede integrarla)
|
|
119
|
+
|
|
120
|
+
## Phase 4 — API Ergonomics (completa)
|
|
121
|
+
|
|
122
|
+
### Archivos implementados / modificados
|
|
123
|
+
|
|
124
|
+
- `protocol/remote_value.py` — `RemoteValue` base + subtipos: `StringValue`, `NumberValue`, `BooleanValue`, `NullValue`, `UndefinedValue`, `BigIntValue`, `ObjectValue`, `ArrayValue`, `HandleValue` con `parse()` factory usando `match`
|
|
125
|
+
- `protocol/results.py` — `Session`, `SessionStatus`, `Navigation`, `Screenshot` modelos tipados
|
|
126
|
+
- `config.py` — `ClientConfig` con Pydantic: timeout, max_retries, retry_delay, retry_backoff, max_queue, drop_policy, log_level
|
|
127
|
+
- `_internal/logging.py` — `setup_logging()` con formato estructurado opcional
|
|
128
|
+
- `modules/browsing.py` — `BrowsingContext` dataclass con `__aenter__`/`__aexit__`, `BrowsingModule` con `script_module` ref, `create_context()` retorna `BrowsingContext`, `navigate`/`screenshot`/`close` aceptan `BrowsingContext | str`, `wait_for_selector`, `wait_for_function`, `open()` retorna `Page`
|
|
129
|
+
- `modules/script.py` — `evaluate`/`call_function` retornan `RemoteValue`, aceptan `BrowsingContext | str`
|
|
130
|
+
- `modules/session.py` — `new()` retorna `Session`, `status()` retorna `SessionStatus`
|
|
131
|
+
- `convenience/page.py` — `Page` object: `evaluate`, `call`, `navigate`, `screenshot` (bytes), `wait_for_selector`, `wait_for_function`, `disown`, `close`, context manager
|
|
132
|
+
- `events/dispatcher.py` — Fluent API (`on()` retorna `Self`) + decorator mode (`@dispatcher.on("event")`)
|
|
133
|
+
- `client.py` — `__aenter__`/`__aexit__`, `ClientConfig` en `connect()`, `on_context_created`/`on_context_destroyed`, `BrowsingModule` con `script_module` ref
|
|
134
|
+
- `__init__.py` — exports: `ClientConfig`, `Page`, `BrowsingContext`, `RemoteValue` + todos los subtipos
|
|
135
|
+
- 25 unit tests nuevos (10 remote_value + 5 context_managers + 6 page + 4 fluent_api) — total 83 passed
|
|
136
|
+
- `pyproject.toml` — añadido `ignore = ["ASYNC109"]` (timeout es parámetro legítimo de la API pública)
|
|
137
|
+
|
|
138
|
+
### Verificación
|
|
139
|
+
|
|
140
|
+
- `ruff check .` — pasa
|
|
141
|
+
- `mypy bidiwave/` — pasa (28 source files)
|
|
142
|
+
- `pytest tests/unit/ -c pyproject.toml` — 83 passed
|
|
143
|
+
- Pendiente: integration tests con Chrome real (context managers, Page object, wait helpers, type narrowing)
|
|
144
|
+
|
|
145
|
+
### Notas
|
|
146
|
+
|
|
147
|
+
- `from __future__ import annotations` añadido a `browsing.py` y `remote_value.py` para resolver referencias forward
|
|
148
|
+
- `BrowsingModule` se define antes de `BrowsingContext` para que el dataclass pueda referenciarlo
|
|
149
|
+
- `Page` usa imports diferidos (`from bidiwave.convenience.page import Page` dentro de `open()`) para evitar circular imports
|
|
150
|
+
- `EventDispatcher.on()` ahora retorna `Self | Subscription | Callable` — fluent cuando se pasa handler, decorator cuando no
|
|
151
|
+
- `ClientConfig` reemplaza a `TransportConfig` en `BiDiClient.connect()` — construye `TransportConfig` internamente
|
|
152
|
+
|
|
153
|
+
## Phase 5 — Integration + CI (completa)
|
|
154
|
+
|
|
155
|
+
### Archivos implementados
|
|
156
|
+
|
|
157
|
+
- `tests/conftest.py` — markers: unit, integration, contract, slow, chrome, firefox
|
|
158
|
+
- `tests/integration/conftest.py` — fixtures: `chrome_bidi` (ChromeDriver Windows), `client` (indirect param), `context` (BrowsingContext auto-cleanup), `pytest_collection_modifyitems` para auto-marker integration
|
|
159
|
+
- `tests/integration/test_connection.py` — connect/close, context manager
|
|
160
|
+
- `tests/integration/test_session.py` — session.status, subscribe/unsubscribe
|
|
161
|
+
- `tests/integration/test_browsing.py` — create/close context, navigate, context manager, screenshot (PNG bytes), get_tree
|
|
162
|
+
- `tests/integration/test_script.py` — evaluate string/number/boolean/null, call_function, await_promise
|
|
163
|
+
- `tests/integration/test_events.py` — console log event, contextCreated, contextDestroyed (rewritten con nueva API)
|
|
164
|
+
- `tests/integration/test_ergonomics.py` — Page object, wait_for_selector, wait_for_function, type narrowing con match
|
|
165
|
+
- `tests/integration/test_hello_world.py` — rewritten con nueva API (StringValue, fixtures)
|
|
166
|
+
- `.github/workflows/test.yml` — CI matrix: unit (ubuntu+windows, py3.11/3.12/3.13, ruff+mypy+pytest+codecov), integration-chrome, integration-firefox
|
|
167
|
+
|
|
168
|
+
### Verificación
|
|
169
|
+
|
|
170
|
+
- `ruff check .` — pasa
|
|
171
|
+
- `mypy bidiwave/` — pasa (28 source files)
|
|
172
|
+
- `pytest tests/unit/ -c pyproject.toml` — 83 passed
|
|
173
|
+
- Integration tests: pendientes de ejecutar contra Chrome real (requieren ChromeDriver activo)
|
|
174
|
+
- CI: configurado para GitHub Actions (unit + integration Chrome + Firefox)
|
|
175
|
+
|
|
176
|
+
### Notas
|
|
177
|
+
|
|
178
|
+
- Fixtures usan indirect parametrization: `@pytest.mark.parametrize("client", ["chrome_bidi"], indirect=True)`
|
|
179
|
+
- `chrome_bidi` fixture lanza ChromeDriver, crea sesión WebDriver, retorna URL del WebSocket BiDi
|
|
180
|
+
- `client` fixture resuelve el nombre del fixture via `request.getfixturevalue()` para obtener la URL
|
|
181
|
+
- Integration tests auto-marcados con `@pytest.mark.integration` via `pytest_collection_modifyitems`
|
|
182
|
+
- CI usa `browser-actions/setup-chrome` y `browser-actions/setup-firefox` para integration tests
|
|
183
|
+
- Coverage: `--cov=bidiwave --cov-report=xml --cov-fail-under=90` en CI de unit tests
|
|
184
|
+
|
|
185
|
+
## Phase 6 — Docs + Release (completa)
|
|
186
|
+
|
|
187
|
+
### Archivos implementados
|
|
188
|
+
|
|
189
|
+
- `README.md` — reescrito con badges (CI, PyPI, Python, License), quick start con nueva API, console log monitoring, instrucciones para lanzar Chrome/Firefox
|
|
190
|
+
- `mkdocs.yml` — Material theme con dark/light mode, mkdocstrings auto-gen, nav completo
|
|
191
|
+
- `docs/index.md` — landing page con features y quick example
|
|
192
|
+
- `docs/quick-start.md` — instalación, lanzar browser, hello world, console logs, screenshot, configuración
|
|
193
|
+
- `docs/cookbook.md` — 7 recetas actualizadas a la nueva API (context managers, Page object)
|
|
194
|
+
- `docs/error-handling.md` — jerarquía de excepciones, escenarios, patrones, logging
|
|
195
|
+
- `docs/protocol-reference.md` — referencia completa de comandos, eventos y códigos de error
|
|
196
|
+
- `docs/api/` — 8 páginas mkdocstrings: client, session, browsing, script, events, remote-value, exceptions, config
|
|
197
|
+
- `CHANGELOG.md` — entrada v1.0.0 con todas las features
|
|
198
|
+
- `.github/workflows/docs.yml` — deploy a GitHub Pages on push to main
|
|
199
|
+
- `.github/workflows/release.yml` — build + publish a PyPI via Trusted Publishing on tag push
|
|
200
|
+
- `pyproject.toml` — version bumped to 1.0.0, classifier updated to Production/Stable
|
|
201
|
+
- `bidiwave/__init__.py` — `__version__ = "1.0.0"`
|
|
202
|
+
|
|
203
|
+
### Verificación
|
|
204
|
+
|
|
205
|
+
- `ruff check .` — pasa
|
|
206
|
+
- `mypy bidiwave/` — pasa (28 source files)
|
|
207
|
+
- `pytest tests/unit/ -c pyproject.toml` — 83 passed
|
|
208
|
+
- Docs deps ya presentes en `pyproject.toml`: mkdocs, mkdocs-material, mkdocstrings[python]
|
|
209
|
+
- Pendiente: `mkdocs build` local, `python -m build`, `pip install` en env limpio, tag v1.0.0
|
|
210
|
+
|
|
211
|
+
### Release checklist
|
|
212
|
+
|
|
213
|
+
- [ ] `mkdocs build` sin errores
|
|
214
|
+
- [ ] `python -m build` genera wheel y sdist
|
|
215
|
+
- [ ] `pip install dist/bidiwave-1.0.0-py3-none-any.whl` en env limpio
|
|
216
|
+
- [ ] Quick start guide reproduce el hello world
|
|
217
|
+
- [ ] Configurar Trusted Publishing en PyPI
|
|
218
|
+
- [ ] `git tag v1.0.0 && git push origin main --tags`
|
|
219
|
+
|
|
220
|
+
## Rutas de referencia (en el repo brainstorming)
|
|
221
|
+
|
|
222
|
+
- **Plan de desarrollo**: `D:\Codigo\brainstorming\ideas\libraries\bidiwave\docs\development-plan.md`
|
|
223
|
+
- **Arquitectura**: `D:\Codigo\brainstorming\ideas\libraries\bidiwave\architecture\README.md`
|
|
224
|
+
- **Patrones de diseño**: `D:\Codigo\brainstorming\ideas\libraries\bidiwave\design\design-patterns.md`
|
|
225
|
+
- **Spec de módulos**: `D:\Codigo\brainstorming\ideas\libraries\bidiwave\docs\modules-spec.md`
|
|
226
|
+
- **Referencia del protocolo**: `D:\Codigo\brainstorming\ideas\libraries\bidiwave\docs\protocol-reference.md`
|
|
227
|
+
- **Prompts por fase**: `D:\Codigo\brainstorming\ideas\libraries\bidiwave\prompts\` (phase-0-setup.md, phase-1-hello-world.md, etc.)
|
|
228
|
+
- **Spike notes**: `D:\Codigo\bidiwave\docs\spike-notes.md`
|
|
229
|
+
|
|
230
|
+
## Cómo continuar
|
|
231
|
+
|
|
232
|
+
Para continuar desarrollo, decirle a Cascade:
|
|
233
|
+
> "continúa con bidiwave, lee CONTEXT.md"
|
bidiwave-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bidiwave
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: WebDriver BiDi for Python — talk to any browser via W3C standard
|
|
5
|
+
Project-URL: Homepage, https://github.com/MathiasPaulenko/bidiwave
|
|
6
|
+
Project-URL: Repository, https://github.com/MathiasPaulenko/bidiwave
|
|
7
|
+
Project-URL: Issues, https://github.com/MathiasPaulenko/bidiwave/issues
|
|
8
|
+
Author: Mathias Paulenko
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: async,automation,bidi,browser,w3c,webdriver
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: anyio>=4.0
|
|
22
|
+
Requires-Dist: pydantic>=2.5
|
|
23
|
+
Requires-Dist: websockets>=12.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
31
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
32
|
+
Provides-Extra: docs
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
34
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
35
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# bidiwave
|
|
39
|
+
|
|
40
|
+
WebDriver BiDi for Python — talk to any browser via W3C standard.
|
|
41
|
+
|
|
42
|
+
[](https://github.com/MathiasPaulenko/bidiwave/actions/workflows/test.yml)
|
|
43
|
+
[](https://pypi.org/project/bidiwave/)
|
|
44
|
+
[](https://pypi.org/project/bidiwave/)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- **W3C WebDriver BiDi** — estándar, no CDP propietario
|
|
50
|
+
- **Cross-browser** — Chrome, Firefox, Edge (Safari cuando soporte BiDi)
|
|
51
|
+
- **Async-first** — `async/await` nativo con `asyncio`
|
|
52
|
+
- **Event streaming** — console logs, navegación, contexts en tiempo real
|
|
53
|
+
- **Type-safe** — Pydantic v2 models, type narrowing con `match`
|
|
54
|
+
- **Sin dependencias pesadas** — no requiere Selenium, no requiere Playwright
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install bidiwave
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
from bidiwave import BiDiClient, StringValue
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
async with await BiDiClient.connect("ws://localhost:9222/session") as client:
|
|
70
|
+
await client.session.new()
|
|
71
|
+
|
|
72
|
+
async with await client.browsing.open("https://example.com") as page:
|
|
73
|
+
# Evaluar JS
|
|
74
|
+
result = await page.evaluate("document.title")
|
|
75
|
+
match result:
|
|
76
|
+
case StringValue(value=title):
|
|
77
|
+
print(f"Title: {title}")
|
|
78
|
+
|
|
79
|
+
# Screenshot
|
|
80
|
+
screenshot = await page.screenshot()
|
|
81
|
+
with open("screenshot.png", "wb") as f:
|
|
82
|
+
f.write(screenshot)
|
|
83
|
+
|
|
84
|
+
asyncio.run(main())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Console log monitoring
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
async with await BiDiClient.connect(url) as client:
|
|
91
|
+
await client.session.new()
|
|
92
|
+
|
|
93
|
+
async def on_log(entry):
|
|
94
|
+
print(f"[{entry.level}] {entry.text}")
|
|
95
|
+
|
|
96
|
+
client.on("log.entryAdded", on_log)
|
|
97
|
+
await client.session.subscribe(["log.entryAdded"])
|
|
98
|
+
|
|
99
|
+
async with await client.browsing.open("https://example.com") as page:
|
|
100
|
+
await page.evaluate("console.log('hello!')")
|
|
101
|
+
await asyncio.sleep(2)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Lanzar un browser con BiDi
|
|
105
|
+
|
|
106
|
+
### Chrome
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
google-chrome --headless=new --remote-debugging-port=9222 --enable-bidi
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Firefox
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
firefox --headless --remote-debugging-port=9223 --no-remote
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Documentation
|
|
119
|
+
|
|
120
|
+
- [Quick Start](https://bidiwave.readthedocs.io/quick-start/)
|
|
121
|
+
- [API Reference](https://bidiwave.readthedocs.io/api/)
|
|
122
|
+
- [Cookbook](https://bidiwave.readthedocs.io/cookbook/)
|
|
123
|
+
- [Error Handling](https://bidiwave.readthedocs.io/error-handling/)
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
bidiwave-1.0.0/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# bidiwave
|
|
2
|
+
|
|
3
|
+
WebDriver BiDi for Python — talk to any browser via W3C standard.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/MathiasPaulenko/bidiwave/actions/workflows/test.yml)
|
|
6
|
+
[](https://pypi.org/project/bidiwave/)
|
|
7
|
+
[](https://pypi.org/project/bidiwave/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **W3C WebDriver BiDi** — estándar, no CDP propietario
|
|
13
|
+
- **Cross-browser** — Chrome, Firefox, Edge (Safari cuando soporte BiDi)
|
|
14
|
+
- **Async-first** — `async/await` nativo con `asyncio`
|
|
15
|
+
- **Event streaming** — console logs, navegación, contexts en tiempo real
|
|
16
|
+
- **Type-safe** — Pydantic v2 models, type narrowing con `match`
|
|
17
|
+
- **Sin dependencias pesadas** — no requiere Selenium, no requiere Playwright
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install bidiwave
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
from bidiwave import BiDiClient, StringValue
|
|
30
|
+
|
|
31
|
+
async def main():
|
|
32
|
+
async with await BiDiClient.connect("ws://localhost:9222/session") as client:
|
|
33
|
+
await client.session.new()
|
|
34
|
+
|
|
35
|
+
async with await client.browsing.open("https://example.com") as page:
|
|
36
|
+
# Evaluar JS
|
|
37
|
+
result = await page.evaluate("document.title")
|
|
38
|
+
match result:
|
|
39
|
+
case StringValue(value=title):
|
|
40
|
+
print(f"Title: {title}")
|
|
41
|
+
|
|
42
|
+
# Screenshot
|
|
43
|
+
screenshot = await page.screenshot()
|
|
44
|
+
with open("screenshot.png", "wb") as f:
|
|
45
|
+
f.write(screenshot)
|
|
46
|
+
|
|
47
|
+
asyncio.run(main())
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Console log monitoring
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
async with await BiDiClient.connect(url) as client:
|
|
54
|
+
await client.session.new()
|
|
55
|
+
|
|
56
|
+
async def on_log(entry):
|
|
57
|
+
print(f"[{entry.level}] {entry.text}")
|
|
58
|
+
|
|
59
|
+
client.on("log.entryAdded", on_log)
|
|
60
|
+
await client.session.subscribe(["log.entryAdded"])
|
|
61
|
+
|
|
62
|
+
async with await client.browsing.open("https://example.com") as page:
|
|
63
|
+
await page.evaluate("console.log('hello!')")
|
|
64
|
+
await asyncio.sleep(2)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Lanzar un browser con BiDi
|
|
68
|
+
|
|
69
|
+
### Chrome
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
google-chrome --headless=new --remote-debugging-port=9222 --enable-bidi
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Firefox
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
firefox --headless --remote-debugging-port=9223 --no-remote
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
- [Quick Start](https://bidiwave.readthedocs.io/quick-start/)
|
|
84
|
+
- [API Reference](https://bidiwave.readthedocs.io/api/)
|
|
85
|
+
- [Cookbook](https://bidiwave.readthedocs.io/cookbook/)
|
|
86
|
+
- [Error Handling](https://bidiwave.readthedocs.io/error-handling/)
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|