Skip to content
Merged
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
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
All notable changes to this project are documented here.
Breaking changes are listed first in each release.

## [Unreleased]
## [0.5.0] - Unreleased

Release preparation; the publication date will be recorded when 0.5.0 ships.
See the [migration guide](README.md#upgrading-from-040) before upgrading.

### Breaking changes

Expand All @@ -20,10 +23,11 @@ Breaking changes are listed first in each release.
nested models. Unknown fields and future enum values remain supported. Use
`decode_cdp(Model, payload, strict=False)` for the previous tolerant behavior
that logs a warning and fills missing required fields with `None`.
- Command timeouts now cover sending and receiving. `CdpClient.default_timeout`
is enforced for every transport and inherited by session views. Omit the raw
`execute(timeout=...)` argument to inherit the default; pass `None` to disable
it. Set `default_timeout=None` to disable the client default entirely.
- Command timeouts now cover sending and receiving. The
`CdpClient(default_timeout=...)` setting is enforced for every transport and
inherited by session views. Omit the raw `execute(timeout=...)` argument to
inherit the default; pass `None` to disable it. Set `default_timeout=None` to
disable the client default entirely.
- `WebSocketTransport` no longer accepts `default_timeout`; configure it on
`CdpClient` instead. Standalone transport calls have no deadline unless
`execute(timeout=seconds)` is specified. Generated methods retain only CDP's
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include CHANGELOG.md
include RELEASING.md
54 changes: 50 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ without working with raw JSON messages.

- [Why cdpify?](#why-cdpify)
- [Installation](#installation)
- [Upgrading from 0.4.0](#upgrading-from-040)
- [Quick start](#quick-start)
- [Supported domains](#supported-domains)
- [Listening for events](#listening-for-events)
- [Working with target sessions](#working-with-target-sessions)
- [Configuration](#configuration)
- [Development](#development)
- [Releasing](RELEASING.md)
- [Resources](#resources)

## Why cdpify?
Expand Down Expand Up @@ -61,29 +63,65 @@ Install the generator dependencies only when regenerating protocol modules:
pip install "cdpify[generator]"
```

## Upgrading from 0.4.0

Version 0.5.0 includes breaking changes. Review these before updating:

| Area | Update for 0.5.0 |
| --- | --- |
| Error imports | Replace `cdpify.exceptions` with `cdpify.error`, or import errors from `cdpify`. The old module is removed. |
| Optional `Any` values | Use `UNSET` to omit a value. `None` now sends JSON `null`; ordinary optional scalar fields still omit `None`. |
| Required response fields | Generated responses and typed events now raise `CDPDecodeError` when required fields are missing. For tolerant manual decoding, use `decode_cdp(Model, payload, strict=False)`. |
| Command deadlines | Configure `default_timeout` on `CdpClient`, including for custom transports. The budget covers sending and receiving. `WebSocketTransport` no longer accepts `default_timeout`. |
| Bounded event buffers | Overflow now raises `CDPEventOverflowError`. Set `overflow="drop_oldest"` to keep lossy buffering. The default buffer remains unlimited. |
| WebSocket compression | Compression now defaults to `None`. Set `compression="deflate"` to enable it. |

Generated methods accept only CDP parameters. For an individual caller deadline,
wrap the call in `asyncio.timeout(seconds)`; a CDP parameter named `timeout`
keeps the units and meaning defined by the protocol. The raw `execute()` method
accepts a client timeout in seconds: omit it to inherit the client default or
pass `None` to disable that deadline for the call.

See the [0.5.0 changelog](CHANGELOG.md#050---unreleased) for the complete release
notes, including raw event streams and performance changes.

## Quick start

Start Chrome or Chromium with remote debugging enabled, then obtain a page's
`webSocketDebuggerUrl` from `http://localhost:9222/json`.
`webSocketDebuggerUrl` from `http://localhost:9222/json`. Launch with
`--remote-debugging-port=9222 --user-data-dir=<dedicated-profile-directory>`
and open a tab first. The scripts in [examples](examples) can launch Chrome
with these options for you, for example `uv run python examples/navigate.py`
from a development checkout.

```python
import asyncio
import json
from urllib.request import urlopen

from cdpify import CdpClient
from cdpify.domains.page import LoadEventFiredEvent


def get_websocket_url() -> str:
with urlopen("http://localhost:9222/json", timeout=5) as response:
return json.load(response)[0]["webSocketDebuggerUrl"]
return next(
target["webSocketDebuggerUrl"]
for target in json.load(response)
if target.get("type") == "page"
)


async def main() -> None:
ws_url = get_websocket_url()

async with CdpClient(ws_url) as client:
await client.page.navigate(url="https://example.com")
await client.page.enable()
async with client.listen(LoadEventFiredEvent) as loaded:
navigation = await client.page.navigate(url="https://example.com")
if navigation.error_text:
raise RuntimeError(navigation.error_text)
await loaded.next(timeout=10)

result = await client.runtime.evaluate(
expression="document.title",
Expand Down Expand Up @@ -180,6 +218,7 @@ triggers, subscribe first and then send the command:
from cdpify.domains.page.events import LoadEventFiredEvent


await client.page.enable()
async with client.listen(LoadEventFiredEvent) as events:
await client.page.navigate(url="https://example.com")
await events.next(timeout=10)
Expand Down Expand Up @@ -461,6 +500,7 @@ Install the project and its development dependencies with
uv sync --dev
uv run pytest
uv run ruff check . --exclude cdpify/domains
uv run ruff format --check . --exclude cdpify/domains
uv run pyright
uv run python scripts/check_generated.py
```
Expand Down Expand Up @@ -501,9 +541,13 @@ uv run python -m cdpify.generator
Generate only selected domains by repeating `--domain`:

```bash
uv run python -m cdpify.generator --domain Page --domain Runtime
uv run python -m cdpify.generator --domain Page --domain Runtime --output-dir build/selected-domains
```

Generation replaces the entire output directory, including for selected domains.
Use a separate `--output-dir` for partial generation to keep the full bundled
client intact. Selected domains may also reference types from other domains.

Use `--spec-dir` and `--output-dir` to override where downloaded specifications
and generated modules are written.

Expand All @@ -516,6 +560,8 @@ once a week and opens a pull request when generated code changes.
- [Chrome DevTools Protocol documentation](https://chromedevtools.github.io/devtools-protocol/)
- [Chrome DevTools Protocol repository](https://github.com/ChromeDevTools/devtools-protocol)
- [Examples](examples)
- [Changelog and migration notes](CHANGELOG.md)
- [Release checklist](RELEASING.md)

The code-generation approach was inspired by
[`cdp-use`](https://github.com/browser-use/cdp-use).
76 changes: 76 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Releasing cdpify

Version 0.5.0 is prepared in `pyproject.toml` and `uv.lock`. Its
[changelog entry](CHANGELOG.md#050---unreleased) remains marked **Unreleased**
until publication. Release preparation does not publish a package or create a tag.

## Validate the release candidate

Run from the repository root with Python 3.12 or newer:

```bash
uv sync --dev --extra orjson --locked
uv run --extra orjson pytest
uv run ruff check . --exclude cdpify/domains
uv run ruff format --check . --exclude cdpify/domains
uv run --extra orjson pyright
uv run python scripts/check_generated.py
uv run --extra orjson pytest -m integration -v
uv run --extra orjson python benchmarks/runtime.py --iterations 1000 --samples 3
```

The unit suite exercises both JSON backends when `orjson` is installed. The
integration suite needs Chrome or Chromium; set `CHROME_PATH` if discovery does
not find it. Confirm that these tests run rather than skip. They launch a
headless browser with an isolated profile. CI additionally tests Python 3.12,
3.13 and 3.14 on Ubuntu, Windows and macOS; require those checks to pass on the
release commit.

Use the bundled-spec drift check above for release validation. The generator
CLI downloads the latest upstream specs and replaces its output directory;
review any protocol refresh separately before including it in a release.

Build both distributions and validate their package metadata:

```bash
uv build
uvx twine check dist/cdpify-0.5.0-py3-none-any.whl dist/cdpify-0.5.0.tar.gz
```

Smoke-test the wheel from outside the source checkout in a clean environment.
Verify that the core imports without extras, that `cdpify.error` and generated
domains import, and that the wheel contains `cdpify/py.typed` and the generator's
`templates/*.jinja2` files. Install the `websocket` extra for a real connection
and the `generator` extra when testing generation. Check the source distribution
too: it must include the README, changelog and this checklist, and build a wheel
with the same version and package files.

## Publish when ready

1. Review the [0.4.0 migration guide](README.md#upgrading-from-040) and the
complete 0.5.0 changelog against the changes since `v0.4.0`.
2. Replace `Unreleased` in the 0.5.0 changelog heading with the actual publication
date (`YYYY-MM-DD`), remove the release-preparation sentence, and update links
to that heading and the release-status paragraph in this checklist. Commit
the release changes and merge them into `main` after CI passes.
3. Rebuild and validate both distributions from that exact commit. Use the
explicit 0.5.0 filenames so older files in `dist/` are excluded.
4. Tag the validated commit and push the tag:

```bash
git tag -a v0.5.0 -m "Release 0.5.0"
git push origin v0.5.0
```

5. Publish the artifacts using the project's PyPI credentials:

```bash
uv publish dist/cdpify-0.5.0-py3-none-any.whl dist/cdpify-0.5.0.tar.gz
```

6. Create a GitHub release for `v0.5.0` using the changelog's 0.5.0 section as
release notes, with breaking changes first. Verify the installed version
from PyPI in a clean environment with `pip install "cdpify[websocket]==0.5.0"`.

The repository currently has CI and weekly spec-update workflows; neither
publishes to PyPI. Pushing a tag alone does not publish the package.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cdpify"
version = "0.4.0"
version = "0.5.0"
description = "A Python client for the Chrome DevTools Protocol (CDP) optimized for DX."
readme = "README.md"
requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading