From e935d87419e04fe391c2d59822061013c54002b4 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 24 Sep 2025 12:33:20 +0900 Subject: [PATCH 1/2] Update getting started doc to follow connectrpc.com patterns Signed-off-by: Anuraag Agrawal --- docs/api.md | 2 +- docs/examples.md | 2 +- docs/getting-started.md | 257 ++++++++++++++++++++++++++++++------- mkdocs.yml | 7 +- src/connectrpc/_headers.py | 3 + 5 files changed, 220 insertions(+), 51 deletions(-) diff --git a/docs/api.md b/docs/api.md index bc56465..ccdeb8a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2,7 +2,7 @@ ::: connectrpc.client ::: connectrpc.code -::: connectrpc.exceptions +::: connectrpc.errors ::: connectrpc.interceptor ::: connectrpc.method ::: connectrpc.request diff --git a/docs/examples.md b/docs/examples.md index 0fcb3a7..efabcc2 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -33,4 +33,4 @@ async def main(): ## More Examples -For more detailed examples, see the [Usage Guide](../usage.md). +For more detailed examples, see the [Usage Guide](./usage.md). diff --git a/docs/getting-started.md b/docs/getting-started.md index 36b6570..d82331a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,34 +1,97 @@ # Getting Started -## Installation +Connect is a slim library for building HTTP APIs consumable anywhere, including browsers. +You define your service with a Protocol Buffer schema, and Connect generates type-safe server +and client code. Fill in your server's business logic and you're done — no hand-written +marshaling, routing, or client code required! -### Basic Client +This fifteen-minute walkthrough helps you create a small Connect service in Python. +It demonstrates what you'll be writing by hand, what Connect generates for you, +and how to call your new API. -For basic client functionality: +## Prerequisites + +- [uv](https://docs.astral.sh/uv/#installation) installed. Any package manager including pip can also be used. +- [The Buf CLI](https://buf.build/docs/installation) installed, and include it in the `$PATH`. +- We'll also use [cURL](https://curl.se/). It's available from Homebrew and most Linux package managers. + +## Setup python environment + +First, we'll setup the python environment and dependencies. + +=== "ASGI" + + ```bash + uv init + uv add connect-python uvicorn + ``` + +=== "WSGI" + + ```bash + uv init + uv add connect-python gunicorn + ``` + +## Define a service + +Now we're ready to write the Protocol Buffer schema that defines our service. In your shell, ```bash -pip install connect-python +mkdir -p proto/greet/v1 +touch proto/greet/v1/greet.proto ``` -### Code Generation +Open `proto/greet/v1/greet.proto` in your editor and add: -For code generation additionally install the protoc plugin: +```protobuf +syntax = "proto3"; -```bash -pip install protoc-gen-connect-python +package greet.v1; + +message GreetRequest { + string name = 1; +} + +message GreetResponse { + string greeting = 1; +} + +service GreetService { + rpc Greet(GreetRequest) returns (GreetResponse) {} +} ``` -## Code Generation +This file declares the `greet.v1` Protobuf package, a service called `GreetService`, and a single method +called `Greet` with its request and response structures. These package, service, and method names will +reappear soon in our HTTP API's URLs. -With a protobuf definition in hand, you can generate a client. This is -easiest using buf, but you can also use protoc directly. +## Generate code -Install the compiler (eg `pip install protoc-gen-connect-python`), and -it can be referenced as `protoc-gen-connect_python`. +We're going to generate our code using [Buf](https://buf.build/), a modern replacement for Google's protobuf compiler. -### Using Buf (Recommended) +First, scaffold a basic [buf.yaml](https://buf.build/docs/configuration/v2/buf-yaml) by running `buf config init`. +Then, edit `buf.yaml` to use our `proto` directory: -A reasonable `buf.gen.yaml`: +```yaml hl_lines="2 3" +version: v2 +modules: + - path: proto +lint: + use: + - DEFAULT +breaking: + use: + - FILE +``` + +We will use [remote plugins](https://buf.build/docs/bsr/remote-plugins/usage), a feature of the +[Buf Schema Registry](https://buf.build/docs/tutorials/getting-started-with-bsr) for generating code. Tell buf how to +generate code by creating a buf.gen.yaml: + +```bash +touch buf.gen.yaml +``` ```yaml version: v2 @@ -37,59 +100,157 @@ plugins: out: . - remote: buf.build/protocolbuffers/pyi out: . - - local: .venv/bin/protoc-gen-connect_python + - remote: buf.build/connectrpc/python out: . ``` -### Using protoc +With those configuration files in place, you can lint your schema and generate code: ```bash -protoc --plugin=protoc-gen-connect-python=.venv/bin/protoc-gen-connect-python \ - --connect-python_out=. \ - --python_out=. \ - --pyi_out=. \ - your_service.proto +buf lint +buf generate +``` + +In the `greet` package, you should now see some generated Python: + ``` +greet + └── v1 + ├── greet_connect.py + └── greet_pb2.py + └── greet_pb2.pyi +``` + +The package `greet/v1` contains `greet_pb2.py` and `greet_pb2.pyi` which were generated by +the [protocolbuffers/python](https://buf.build/protocolbuffers/python) and +[protocolbuffers/pyi](https://buf.build/protocolbuffers/pyi) and contain `GreetRequest` +and `GreetResponse` structs and the associated marshaling code. `greet_connect.py` was +generated by [connectrpc/python](https://buf.build/connectrpc/python) and contains the +WSGI and ASGI service interfaces and client code to access a Connect server. Feel free to +poke around if you're interested - `greet_connect.py` is standard Python code. + +## Implement service -## Example Service Definition +The code we've generated takes care of the boring boilerplate, but we still need to implement our greeting logic. +In the generated code, this is represented as the `greet_connect.GreetService` and `greet_connect.GreetServiceSync` +interfaces for async ASGI and sync WSGI servers respectively. Since the interface is so small, we can do everything +in one Python file. `touch server.py` and add: -If you have a proto definition like this: +=== "ASGI" -```proto -service ElizaService { - rpc Say(SayRequest) returns (SayResponse) {} - rpc Converse(stream ConverseRequest) returns (stream ConverseResponse) {} - rpc Introduce(IntroduceRequest) returns (stream IntroduceResponse) {} - rpc Pontificate(stream PontificateRequest) returns (PontificateResponse) {} + ```python + from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication + from greet.v1.greet_pb2 import GreetResponse + + class Greeter(GreetService): + async def greet(self, request, ctx): + print("Request headers: ", ctx.request_headers()) + response = GreetResponse(greeting=f"Hello, {request.name}!") + ctx.response_headers()["greet-version"] = "v1" + return response + + app = GreetServiceASGIApplication(Greeter()) + ``` + +=== "WSGI" + + ```python + from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication + from greet.v1.greet_pb2 import GreetResponse + + class Greeter(GreetServiceSync): + def greet(self, request, ctx): + print("Request headers: ", ctx.request_headers()) + response = GreetResponse(greeting=f"Hello, {request.name}!") + ctx.response_headers()["greet-version"] = "v1" + return response + + app = GreetServiceWSGIApplication(Greeter()) + ``` + +In a separate terminal window, you can now start your server: + +=== "ASGI" + + ```bash + uv run uvicorn server:app + ``` + +=== "WSGI" + + ```bash + uv run gunicorn server:app + ``` + +## Make requests + +The simplest way to consume your new API is an HTTP/1.1 POST with a JSON payload. If you have a recent version of +cURL installed, it's a one-liner: + +```bash +curl \ + --header "Content-Type: application/json" \ + --data '{"name": "Jane"}' \ + http://localhost:8000/greet.v1.GreetService/Greet +``` + +This responds: + +```json +{ + "greeting": "Hello, Jane!" } ``` -## Generated Client +We can also make requests using Connect's generated client. `touch client.py` and add: + +=== "Async" + + ```python + import asyncio + + from greet.v1.greet_connect import GreetServiceClient + from greet.v1.greet_pb2 import GreetRequest -Then the generated client will have methods like this (optional arguments have been elided for clarity): + async def main(): + client = GreetServiceClient("http://localhost:8000") + res = await client.greet(GreetRequest(name="Jane")) + print(res.greeting) -```python -class ElizaServiceClient: - def __init__(self, url: str): - ... + if __name__ == "__main__": + asyncio.run(main()) + ``` - # Unary (no streams) - def say(self, req: eliza_pb2.SayRequest) -> eliza_pb2.SayResponse: - ... +=== "Sync" - # Bidirectional (both sides stream) - def converse(self, req: Iterator[eliza_pb2.ConverseRequest]) -> Iterator[eliza_pb2.SayResponse]: - ... + ```python + from greet.v1.greet_connect import GreetServiceClientSync + from greet.v1.greet_pb2 import GreetRequest - # Server streaming (client sends one message, server sends a stream) - def introduce(self, req: eliza_pb2.IntroduceRequest) -> Iterator[eliza_pb2.IntroduceResponse]: - ... + def main(): + client = GreetServiceClientSync("http://localhost:8000") + res = client.greet(GreetRequest(name="Jane")) + print(res.greeting) - # Client streaming (client sends a stream, server sends one message back) - def pontificate(self, req: Iterator[eliza_pb2.PontificateRequest]) -> eliza_pb2.PontificateResponse: - ... + if __name__ == "__main__": + main() + ``` + +With your server still running in a separate termainl window, you can now run your client: + +```bash +uv run python client.py ``` +Congratulations — you've built your first Connect service! 🎉 + +## So what? + +With just a few lines of hand-written code, you've built a real API server that supports both the and Connect protocol. +Unlike a hand-written REST service, you didn't need to design a URL hierarchy, hand-write request and response structs, +manage your own marshaling, or parse typed values out of query parameters. More importantly, your users got an idiomatic, +type-safe client without any extra work on your part. + ## Next Steps - Learn about [Usage](./usage.md) patterns diff --git a/mkdocs.yml b/mkdocs.yml index 7894676..a050096 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,9 @@ site_name: Connect Documentation theme: name: material + features: + - content.code.copy + - content.tabs.link plugins: - mkdocstrings: @@ -25,4 +28,6 @@ markdown_extensions: pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + - pymdownx.tabbed: + alternate_style: true diff --git a/src/connectrpc/_headers.py b/src/connectrpc/_headers.py index 9120628..e962989 100644 --- a/src/connectrpc/_headers.py +++ b/src/connectrpc/_headers.py @@ -56,6 +56,9 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self._store) + def __repr__(self) -> str: + return repr(list(self.allitems())) + def add(self, key: str, value: str) -> None: """Add a header, appending to existing values without overwriting. From 620a9c04ab295ea5d30a471c934df01bcf855496 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 24 Sep 2025 12:36:12 +0900 Subject: [PATCH 2/2] Typo Signed-off-by: Anuraag Agrawal --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index d82331a..98e6990 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -236,7 +236,7 @@ We can also make requests using Connect's generated client. `touch client.py` an main() ``` -With your server still running in a separate termainl window, you can now run your client: +With your server still running in a separate terminal window, you can now run your client: ```bash uv run python client.py