Skip to content

Commit 3db105e

Browse files
authored
Update getting started doc to follow connectrpc.com patterns (#11)
Signed-off-by: Anuraag Agrawal <[email protected]>
1 parent f4eb908 commit 3db105e

File tree

5 files changed

+220
-51
lines changed

5 files changed

+220
-51
lines changed

docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
::: connectrpc.client
44
::: connectrpc.code
5-
::: connectrpc.exceptions
5+
::: connectrpc.errors
66
::: connectrpc.interceptor
77
::: connectrpc.method
88
::: connectrpc.request

docs/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ async def main():
3333

3434
## More Examples
3535

36-
For more detailed examples, see the [Usage Guide](../usage.md).
36+
For more detailed examples, see the [Usage Guide](./usage.md).

docs/getting-started.md

Lines changed: 209 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,97 @@
11
# Getting Started
22

3-
## Installation
3+
Connect is a slim library for building HTTP APIs consumable anywhere, including browsers.
4+
You define your service with a Protocol Buffer schema, and Connect generates type-safe server
5+
and client code. Fill in your server's business logic and you're done — no hand-written
6+
marshaling, routing, or client code required!
47

5-
### Basic Client
8+
This fifteen-minute walkthrough helps you create a small Connect service in Python.
9+
It demonstrates what you'll be writing by hand, what Connect generates for you,
10+
and how to call your new API.
611

7-
For basic client functionality:
12+
## Prerequisites
13+
14+
- [uv](https://docs.astral.sh/uv/#installation) installed. Any package manager including pip can also be used.
15+
- [The Buf CLI](https://buf.build/docs/installation) installed, and include it in the `$PATH`.
16+
- We'll also use [cURL](https://curl.se/). It's available from Homebrew and most Linux package managers.
17+
18+
## Setup python environment
19+
20+
First, we'll setup the python environment and dependencies.
21+
22+
=== "ASGI"
23+
24+
```bash
25+
uv init
26+
uv add connect-python uvicorn
27+
```
28+
29+
=== "WSGI"
30+
31+
```bash
32+
uv init
33+
uv add connect-python gunicorn
34+
```
35+
36+
## Define a service
37+
38+
Now we're ready to write the Protocol Buffer schema that defines our service. In your shell,
839

940
```bash
10-
pip install connect-python
41+
mkdir -p proto/greet/v1
42+
touch proto/greet/v1/greet.proto
1143
```
1244

13-
### Code Generation
45+
Open `proto/greet/v1/greet.proto` in your editor and add:
1446

15-
For code generation additionally install the protoc plugin:
47+
```protobuf
48+
syntax = "proto3";
1649
17-
```bash
18-
pip install protoc-gen-connect-python
50+
package greet.v1;
51+
52+
message GreetRequest {
53+
string name = 1;
54+
}
55+
56+
message GreetResponse {
57+
string greeting = 1;
58+
}
59+
60+
service GreetService {
61+
rpc Greet(GreetRequest) returns (GreetResponse) {}
62+
}
1963
```
2064

21-
## Code Generation
65+
This file declares the `greet.v1` Protobuf package, a service called `GreetService`, and a single method
66+
called `Greet` with its request and response structures. These package, service, and method names will
67+
reappear soon in our HTTP API's URLs.
2268

23-
With a protobuf definition in hand, you can generate a client. This is
24-
easiest using buf, but you can also use protoc directly.
69+
## Generate code
2570

26-
Install the compiler (eg `pip install protoc-gen-connect-python`), and
27-
it can be referenced as `protoc-gen-connect_python`.
71+
We're going to generate our code using [Buf](https://buf.build/), a modern replacement for Google's protobuf compiler.
2872

29-
### Using Buf (Recommended)
73+
First, scaffold a basic [buf.yaml](https://buf.build/docs/configuration/v2/buf-yaml) by running `buf config init`.
74+
Then, edit `buf.yaml` to use our `proto` directory:
3075

31-
A reasonable `buf.gen.yaml`:
76+
```yaml hl_lines="2 3"
77+
version: v2
78+
modules:
79+
- path: proto
80+
lint:
81+
use:
82+
- DEFAULT
83+
breaking:
84+
use:
85+
- FILE
86+
```
87+
88+
We will use [remote plugins](https://buf.build/docs/bsr/remote-plugins/usage), a feature of the
89+
[Buf Schema Registry](https://buf.build/docs/tutorials/getting-started-with-bsr) for generating code. Tell buf how to
90+
generate code by creating a buf.gen.yaml:
91+
92+
```bash
93+
touch buf.gen.yaml
94+
```
3295

3396
```yaml
3497
version: v2
@@ -37,59 +100,157 @@ plugins:
37100
out: .
38101
- remote: buf.build/protocolbuffers/pyi
39102
out: .
40-
- local: .venv/bin/protoc-gen-connect_python
103+
- remote: buf.build/connectrpc/python
41104
out: .
42105
```
43106
44-
### Using protoc
107+
With those configuration files in place, you can lint your schema and generate code:
45108
46109
```bash
47-
protoc --plugin=protoc-gen-connect-python=.venv/bin/protoc-gen-connect-python \
48-
--connect-python_out=. \
49-
--python_out=. \
50-
--pyi_out=. \
51-
your_service.proto
110+
buf lint
111+
buf generate
112+
```
113+
114+
In the `greet` package, you should now see some generated Python:
115+
52116
```
117+
greet
118+
└── v1
119+
├── greet_connect.py
120+
└── greet_pb2.py
121+
└── greet_pb2.pyi
122+
```
123+
124+
The package `greet/v1` contains `greet_pb2.py` and `greet_pb2.pyi` which were generated by
125+
the [protocolbuffers/python](https://buf.build/protocolbuffers/python) and
126+
[protocolbuffers/pyi](https://buf.build/protocolbuffers/pyi) and contain `GreetRequest`
127+
and `GreetResponse` structs and the associated marshaling code. `greet_connect.py` was
128+
generated by [connectrpc/python](https://buf.build/connectrpc/python) and contains the
129+
WSGI and ASGI service interfaces and client code to access a Connect server. Feel free to
130+
poke around if you're interested - `greet_connect.py` is standard Python code.
131+
132+
## Implement service
53133

54-
## Example Service Definition
134+
The code we've generated takes care of the boring boilerplate, but we still need to implement our greeting logic.
135+
In the generated code, this is represented as the `greet_connect.GreetService` and `greet_connect.GreetServiceSync`
136+
interfaces for async ASGI and sync WSGI servers respectively. Since the interface is so small, we can do everything
137+
in one Python file. `touch server.py` and add:
55138

56-
If you have a proto definition like this:
139+
=== "ASGI"
57140

58-
```proto
59-
service ElizaService {
60-
rpc Say(SayRequest) returns (SayResponse) {}
61-
rpc Converse(stream ConverseRequest) returns (stream ConverseResponse) {}
62-
rpc Introduce(IntroduceRequest) returns (stream IntroduceResponse) {}
63-
rpc Pontificate(stream PontificateRequest) returns (PontificateResponse) {}
141+
```python
142+
from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication
143+
from greet.v1.greet_pb2 import GreetResponse
144+
145+
class Greeter(GreetService):
146+
async def greet(self, request, ctx):
147+
print("Request headers: ", ctx.request_headers())
148+
response = GreetResponse(greeting=f"Hello, {request.name}!")
149+
ctx.response_headers()["greet-version"] = "v1"
150+
return response
151+
152+
app = GreetServiceASGIApplication(Greeter())
153+
```
154+
155+
=== "WSGI"
156+
157+
```python
158+
from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication
159+
from greet.v1.greet_pb2 import GreetResponse
160+
161+
class Greeter(GreetServiceSync):
162+
def greet(self, request, ctx):
163+
print("Request headers: ", ctx.request_headers())
164+
response = GreetResponse(greeting=f"Hello, {request.name}!")
165+
ctx.response_headers()["greet-version"] = "v1"
166+
return response
167+
168+
app = GreetServiceWSGIApplication(Greeter())
169+
```
170+
171+
In a separate terminal window, you can now start your server:
172+
173+
=== "ASGI"
174+
175+
```bash
176+
uv run uvicorn server:app
177+
```
178+
179+
=== "WSGI"
180+
181+
```bash
182+
uv run gunicorn server:app
183+
```
184+
185+
## Make requests
186+
187+
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
188+
cURL installed, it's a one-liner:
189+
190+
```bash
191+
curl \
192+
--header "Content-Type: application/json" \
193+
--data '{"name": "Jane"}' \
194+
http://localhost:8000/greet.v1.GreetService/Greet
195+
```
196+
197+
This responds:
198+
199+
```json
200+
{
201+
"greeting": "Hello, Jane!"
64202
}
65203
```
66204

67-
## Generated Client
205+
We can also make requests using Connect's generated client. `touch client.py` and add:
206+
207+
=== "Async"
208+
209+
```python
210+
import asyncio
211+
212+
from greet.v1.greet_connect import GreetServiceClient
213+
from greet.v1.greet_pb2 import GreetRequest
68214

69-
Then the generated client will have methods like this (optional arguments have been elided for clarity):
215+
async def main():
216+
client = GreetServiceClient("http://localhost:8000")
217+
res = await client.greet(GreetRequest(name="Jane"))
218+
print(res.greeting)
70219

71-
```python
72-
class ElizaServiceClient:
73-
def __init__(self, url: str):
74-
...
220+
if __name__ == "__main__":
221+
asyncio.run(main())
222+
```
75223

76-
# Unary (no streams)
77-
def say(self, req: eliza_pb2.SayRequest) -> eliza_pb2.SayResponse:
78-
...
224+
=== "Sync"
79225

80-
# Bidirectional (both sides stream)
81-
def converse(self, req: Iterator[eliza_pb2.ConverseRequest]) -> Iterator[eliza_pb2.SayResponse]:
82-
...
226+
```python
227+
from greet.v1.greet_connect import GreetServiceClientSync
228+
from greet.v1.greet_pb2 import GreetRequest
83229

84-
# Server streaming (client sends one message, server sends a stream)
85-
def introduce(self, req: eliza_pb2.IntroduceRequest) -> Iterator[eliza_pb2.IntroduceResponse]:
86-
...
230+
def main():
231+
client = GreetServiceClientSync("http://localhost:8000")
232+
res = client.greet(GreetRequest(name="Jane"))
233+
print(res.greeting)
87234

88-
# Client streaming (client sends a stream, server sends one message back)
89-
def pontificate(self, req: Iterator[eliza_pb2.PontificateRequest]) -> eliza_pb2.PontificateResponse:
90-
...
235+
if __name__ == "__main__":
236+
main()
237+
```
238+
239+
With your server still running in a separate terminal window, you can now run your client:
240+
241+
```bash
242+
uv run python client.py
91243
```
92244

245+
Congratulations — you've built your first Connect service! 🎉
246+
247+
## So what?
248+
249+
With just a few lines of hand-written code, you've built a real API server that supports both the and Connect protocol.
250+
Unlike a hand-written REST service, you didn't need to design a URL hierarchy, hand-write request and response structs,
251+
manage your own marshaling, or parse typed values out of query parameters. More importantly, your users got an idiomatic,
252+
type-safe client without any extra work on your part.
253+
93254
## Next Steps
94255

95256
- Learn about [Usage](./usage.md) patterns

mkdocs.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ site_name: Connect Documentation
22

33
theme:
44
name: material
5+
features:
6+
- content.code.copy
7+
- content.tabs.link
58

69
plugins:
710
- mkdocstrings:
@@ -25,4 +28,6 @@ markdown_extensions:
2528
pygments_lang_class: true
2629
- pymdownx.inlinehilite
2730
- pymdownx.snippets
28-
- pymdownx.superfences
31+
- pymdownx.superfences:
32+
- pymdownx.tabbed:
33+
alternate_style: true

src/connectrpc/_headers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def __iter__(self) -> Iterator[str]:
5656
def __len__(self) -> int:
5757
return len(self._store)
5858

59+
def __repr__(self) -> str:
60+
return repr(list(self.allitems()))
61+
5962
def add(self, key: str, value: str) -> None:
6063
"""Add a header, appending to existing values without overwriting.
6164

0 commit comments

Comments
 (0)