From b32058176f57071fe73eb7dff0fc3cdd72450805 Mon Sep 17 00:00:00 2001 From: John Mitsch Date: Fri, 29 May 2026 11:55:10 -0400 Subject: [PATCH 1/2] refactor(python)!: rename import package sdk -> quicknode_sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python distribution has always been published as `quicknode-sdk` on PyPI, but the import name was the generic `sdk`. That name collides with any local `sdk/` directory and reads poorly in third-party code — every other language binding in this repo is namespaced (`quicknode-sdk` crate in Rust, `@quicknode/sdk` in Node, `quicknode_sdk` in Ruby). Rename the import path so callers write: from quicknode_sdk import QuicknodeSdk The PyPI distribution name (`quicknode-sdk`) and the Rust core crate name do not change — only the Python import name. No backwards-compat shim: we're still on the 0.1.x line per repo policy. Changes: - maturin `module-name` in both pyproject.toml files - python/sdk/ directory renamed to python/quicknode_sdk/ - internal `from sdk._core import ...` imports - all five python/examples/*.py scripts - python/README.md (6 import examples + error-class import line) - root README.md project-structure tree - CLAUDE.md build/consistency notes - Justfile python-build recipe paths - .gitignore paths Verified: `just python-build` succeeds, `from quicknode_sdk import ...` works, `import sdk` raises ModuleNotFoundError, `cargo check && just lint` pass. --- .gitignore | 6 +++--- CLAUDE.md | 12 ++++++------ Justfile | 2 +- README.md | 2 +- crates/python/pyproject.toml | 2 +- pyproject.toml | 2 +- python/README.md | 14 +++++++------- python/examples/admin.py | 2 +- python/examples/kvstore_e2e.py | 2 +- python/examples/streams.py | 2 +- python/examples/streams_e2e.py | 2 +- python/examples/webhooks_e2e.py | 2 +- python/{sdk => quicknode_sdk}/__init__.py | 2 +- python/{sdk => quicknode_sdk}/__init__.pyi | 6 +++--- python/{sdk => quicknode_sdk}/_core/__init__.pyi | 0 .../init_manual_override.pyi | 6 +++--- python/{sdk => quicknode_sdk}/py.typed | 0 17 files changed, 32 insertions(+), 32 deletions(-) rename python/{sdk => quicknode_sdk}/__init__.py (99%) rename python/{sdk => quicknode_sdk}/__init__.pyi (98%) rename python/{sdk => quicknode_sdk}/_core/__init__.pyi (100%) rename python/{sdk => quicknode_sdk}/init_manual_override.pyi (98%) rename python/{sdk => quicknode_sdk}/py.typed (100%) diff --git a/.gitignore b/.gitignore index ded76b8..dbd651c 100644 --- a/.gitignore +++ b/.gitignore @@ -53,9 +53,9 @@ build/ *.dSYM/ # Maturin -python/sdk/*.so -python/sdk/__pycache__/ -python/sdk/*.dSYM/ +python/quicknode_sdk/*.so +python/quicknode_sdk/__pycache__/ +python/quicknode_sdk/*.dSYM/ # Node.js node_modules/ diff --git a/CLAUDE.md b/CLAUDE.md index b3fe58a..066a008 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ cargo run --example admin -p quicknode-sdk # Run example (requires QN_AP ```bash just python-setup # Create .venv and uv sync (one-time) just python-build # Compile bindings + generate stubs -cp python/sdk/init_manual_override.pyi python/sdk/__init__.pyi # Manually override __init__ so we can overwrite the commands +cp python/quicknode_sdk/init_manual_override.pyi python/quicknode_sdk/__init__.pyi # Manually override __init__ so we can overwrite the commands ``` Both recipes are shell-agnostic — they invoke `maturin` via `uvx`, so no venv activation is required and they work in bash, zsh, or fish without per-shell setup. @@ -60,11 +60,11 @@ This is a polyglot SDK: one Rust core library with Python, Node.js, and Ruby bin ### Workspace Layout - `crates/core` — Pure Rust business logic (HTTP client, request/response types, errors) -- `crates/python` — PyO3 wrapper crate, compiles to `sdk._core` Python extension +- `crates/python` — PyO3 wrapper crate, compiles to `quicknode_sdk._core` Python extension - `crates/node` — napi-rs wrapper crate, compiles to native `.node` module - `crates/ruby` — Magnus wrapper crate, compiles to native `.bundle`/`.so` module - `crates/python-stubs` — Generates `.pyi` type stub files -- `python/sdk/` — Python package directory (distributed via maturin) +- `python/quicknode_sdk/` — Python package directory (distributed via maturin) - `npm/` — Node.js package directory - `ruby/` — Ruby package directory (`lib/quicknode_sdk.rb` entry point, `examples/`) @@ -133,7 +133,7 @@ Each binding owns its mapping in a dedicated `errors.rs` file: When adding a new `SdkError` variant: 1. Add the variant to `crates/core/src/errors.rs` and update `http_kind()` if it's transport-level. 2. Update the `match` in each binding's `map_*_err` function — the compiler will flag missing arms in Python and Ruby (Node's match is also exhaustive on the kind string). -3. If the new variant should surface as a new exception class, add it to all three bindings + `npm/errors.js` + the exports in `python/sdk/__init__.py` + `npm/sdk.d.ts` + `npm/sdk.mjs`. +3. If the new variant should surface as a new exception class, add it to all three bindings + `npm/errors.js` + the exports in `python/quicknode_sdk/__init__.py` + `npm/sdk.d.ts` + `npm/sdk.mjs`. 4. Update examples in all four languages to demonstrate the new class if user-facing. ### Python Binding Pattern @@ -163,12 +163,12 @@ Core clients are tested using mocked API calls with wiremock. All functions maki - **Discriminated unions (Rust enum-with-data)** cannot be annotated with `#[pyclass]` or `#[napi(object)]`. Pattern: keep the enum pure-Rust in `crates/core` with `#[serde(tag = "...", content = "...")]`, then in each binding crate provide a typed surface that converts to/from the enum at the FFI boundary. Any core struct that flattens such an enum (e.g. via `#[serde(flatten)]`) also loses its `#[pyclass]` / `#[napi(object)]` and needs a wrapper in each binding. Reference implementations: - `crates/core/src/streams/stream.rs::DestinationAttributes` — stream destinations, wrapped by `crates/{python,node}/src/streams_destination.rs`. - `crates/core/src/webhooks/webhook.rs::TemplateArgs` — webhook filter templates, wrapped by `crates/{python,node}/src/webhooks_template.rs`. Ruby accepts the wire JSON directly (`{"templateId":..., "templateArgs":...}`) and relies on serde to deserialize into the enum. -- `python/sdk/__init__.py` is **manually maintained** — it is NOT auto-generated. Every new public struct/type must be added to both the `from sdk._core import (...)` block and the `__all__` list in this file +- `python/quicknode_sdk/__init__.py` is **manually maintained** — it is NOT auto-generated. Every new public struct/type must be added to both the `from quicknode_sdk._core import (...)` block and the `__all__` list in this file - When adding a new type with `#[cfg_attr(feature = "node", napi(object))]`, also add it to the named `export type { ... }` block in `npm/sdk.d.ts` — this is the user-facing type file and is not auto-updated by napi-rs - When adding a new `#[napi(string_enum)]` Rust enum, it generates a TypeScript `const enum` in `npm/index.d.ts`. In `npm/sdk.d.ts`, these must be re-exported using a regular `export { ... }` (not `export type { ... }`), otherwise TypeScript consumers cannot use them as values (e.g., `StreamDataset.Block`) - When updating `sdk.js` wrapper methods, verify the argument types match the underlying napi-rs constructor/method signature (object vs primitive) - When adding a new export to `sdk.js`, also add it to the named exports in `npm/sdk.mjs` — ESM named exports cannot be spread dynamically and must be listed explicitly -- `python/sdk/__init__.pyi` is overwritten by `just python-build` — edit `init_manual_override.pyi` instead +- `python/quicknode_sdk/__init__.pyi` is overwritten by `just python-build` — edit `init_manual_override.pyi` instead - `ruby/sig/quicknode_sdk.rbs` is **manually maintained** — it is NOT auto-generated. It provides RBS type signatures so editor LSPs (VSCode Ruby LSP, Solargraph, RubyMine, Steep) autocomplete method names and keyword argument keys for `QuicknodeSdk::Admin/Streams/Webhooks/KvStore/DestinationAttributes` and the exception classes. Every change to method registration in `crates/ruby/src/lib.rs` (new method, renamed key, new arg, removed arg, type change) must be mirrored here in the same PR. Responses are typed as `untyped` because they're wrapped in `QuicknodeSdk::IndifferentHash` at the Ruby boundary — that's intentional, do not try to type response shapes. - Always update examples alongside the code changes diff --git a/Justfile b/Justfile index 47b6b7f..9d70acf 100644 --- a/Justfile +++ b/Justfile @@ -2,7 +2,7 @@ python-setup: uv venv && uv sync python-build: - uvx maturin develop && cargo run -p sdk-python-stubs && cp python/sdk/init_manual_override.pyi python/sdk/__init__.pyi + uvx maturin develop && cargo run -p sdk-python-stubs && cp python/quicknode_sdk/init_manual_override.pyi python/quicknode_sdk/__init__.pyi node-build: cd ./npm && npm install && npm run build && npm run test && cd .. diff --git a/README.md b/README.md index f9dfe07..833a823 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ sdk/ │ ├── python/ # PyO3 bindings │ ├── node/ # napi-rs bindings │ └── ruby/ # magnus bindings -├── python/sdk/ # Python package with type hints +├── python/quicknode_sdk/ # Python package with type hints ├── npm/ # Node.js package with TypeScript types ├── ruby/ # Ruby package └── pyproject.toml # maturin build config diff --git a/crates/python/pyproject.toml b/crates/python/pyproject.toml index 6fe7df3..d44bb1f 100644 --- a/crates/python/pyproject.toml +++ b/crates/python/pyproject.toml @@ -2,5 +2,5 @@ name = "quicknode-sdk" [tool.maturin] -module-name = "sdk._core" +module-name = "quicknode_sdk._core" python-source = "../../python" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b500ede..3d3ad54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ Issues = "https://github.com/quicknode/sdk/issues" [tool.maturin] manifest-path = "crates/python/Cargo.toml" -module-name = "sdk._core" +module-name = "quicknode_sdk._core" python-source = "python" features = ["extension-module"] include = [ diff --git a/python/README.md b/python/README.md index f5ac866..e694a7f 100644 --- a/python/README.md +++ b/python/README.md @@ -58,7 +58,7 @@ Construct the SDK once, then reach into the four sub-clients (`admin`, `streams` ```python # Python import asyncio -from sdk import QuicknodeSdk +from quicknode_sdk import QuicknodeSdk async def main(): qn = QuicknodeSdk.from_env() @@ -76,7 +76,7 @@ There are two ways to configure the SDK. ```python # Python -from sdk import QuicknodeSdk, SdkFullConfig, HttpConfig +from quicknode_sdk import QuicknodeSdk, SdkFullConfig, HttpConfig qn = QuicknodeSdk(SdkFullConfig(api_key="your-key", http=HttpConfig(timeout_secs=30))) ``` @@ -111,7 +111,7 @@ quicknode-sdk-/ (-; - Date: Fri, 29 May 2026 11:57:47 -0400 Subject: [PATCH 2/2] docs: note pre-1.0 breaking-change policy in all READMEs While the SDK is on the 0.x line, any release may contain breaking changes. Add a short callout pointing readers at the GitHub release notes so upgrades aren't surprising. Mirrored byte-identically across the root README and all four per-language READMEs to satisfy the polyglot consistency rule in CLAUDE.md. --- README.md | 2 ++ crates/core/README.md | 2 ++ npm/README.md | 2 ++ python/README.md | 2 ++ ruby/README.md | 2 ++ 5 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 833a823..0c3ac14 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A unified SDK for building on Quicknode. Rust SDK with Python, Node.js, and Ruby bindings. +> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading. + ## Table of Contents - [Per-language docs](#per-language-docs) diff --git a/crates/core/README.md b/crates/core/README.md index 3626955..48154f1 100644 --- a/crates/core/README.md +++ b/crates/core/README.md @@ -4,6 +4,8 @@ The core Rust crate for the Quicknode SDK. This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process. +> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading. + ## Table of Contents - [Installation](#installation) diff --git a/npm/README.md b/npm/README.md index 9a662e7..5c85e1a 100644 --- a/npm/README.md +++ b/npm/README.md @@ -4,6 +4,8 @@ Node.js / TypeScript bindings for the Quicknode SDK. This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process. +> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading. + ## Table of Contents - [Installation](#installation) diff --git a/python/README.md b/python/README.md index e694a7f..093a79f 100644 --- a/python/README.md +++ b/python/README.md @@ -4,6 +4,8 @@ Python bindings for the Quicknode SDK. This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process. +> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading. + ## Table of Contents - [Installation](#installation) diff --git a/ruby/README.md b/ruby/README.md index ebef052..91b9b87 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -4,6 +4,8 @@ Ruby bindings for the Quicknode SDK. This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process. +> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading. + ## Table of Contents - [Installation](#installation)