diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b036621..5129ec3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..935bac10 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include CHANGELOG.md +include RELEASING.md diff --git a/README.md b/README.md index 799a6a6b..d97cb07a 100644 --- a/README.md +++ b/README.md @@ -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? @@ -61,10 +63,36 @@ 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=` +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 @@ -72,18 +100,28 @@ 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", @@ -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) @@ -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 ``` @@ -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. @@ -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). diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..5964dcd7 --- /dev/null +++ b/RELEASING.md @@ -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. diff --git a/pyproject.toml b/pyproject.toml index 17f2c82c..e75e6cb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/uv.lock b/uv.lock index 2f9186f9..321fa093 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "cdpify" -version = "0.4.0" +version = "0.5.0" source = { editable = "." } [package.optional-dependencies]