Skip to content

Commit 3a0c67c

Browse files
authored
Merge pull request #296 from authzed/add-materialize-support
add Materialize support to authzed-py
2 parents ef30ad7 + 94b8544 commit 3a0c67c

File tree

4 files changed

+524
-1
lines changed

4 files changed

+524
-1
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Developers create a schema that models their permissions requirements and use a
1515

1616
Supported client API versions:
1717

18-
- [v1](https://docs.authzed.com/reference/api#authzedapiv1)
18+
- [v1](https://docs.authzed.com/reference/api#authzedapiv1) - Core SpiceDB API for permissions checks, schema management, and relationship operations
19+
- [materialize/v0](https://buf.build/authzed/api/docs/main:authzed.api.materialize.v0) - Materialize API for building materialized permission views
1920

2021
You can find more info on each API on the [Authzed API reference documentation].
2122
Additionally, Protobuf API documentation can be found on the [Buf Registry Authzed API repository].
@@ -114,3 +115,12 @@ client = InsecureClient(
114115
"my super secret token"
115116
)
116117
```
118+
119+
## Materialize API
120+
121+
The authzed-py supports Authzed Materialize API.
122+
The Materialize API allows you to build and maintain materialized views of your permissions data in your own systems for high-performance lookups.
123+
124+
Learn more in the **[Materialize API Quickstart Guide]** that can be found the examples directory.
125+
126+
[Materialize API Quickstart Guide]: /examples/materialize/QUICKSTART.md

examples/materialize/QUICKSTART.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Materialize API Quick Start
2+
3+
The Materialize API allows you to build and maintain a materialized view of SpiceDB permissions in your own data store for high-performance lookups.
4+
5+
## Installation
6+
7+
```bash
8+
# Install authzed with UV
9+
uv add authzed
10+
11+
# Or with pip
12+
pip install authzed
13+
```
14+
15+
## Basic Usage
16+
17+
### 1. Initial Backfill with LookupPermissionSets
18+
19+
Use `LookupPermissionSets` to perform the initial backfill of permission sets:
20+
21+
```python
22+
from authzed.api.materialize.v0 import (
23+
Client,
24+
LookupPermissionSetsRequest,
25+
)
26+
from grpcutil import bearer_token_credentials
27+
28+
# Create client
29+
client = Client(
30+
"grpc.authzed.com:443",
31+
bearer_token_credentials("your_materialize_token"),
32+
)
33+
34+
# Fetch permission sets
35+
request = LookupPermissionSetsRequest(limit=1000)
36+
response_stream = client.LookupPermissionSets(request)
37+
38+
for response in response_stream:
39+
change = response.change
40+
parent = change.parent_set
41+
42+
# Store in your database
43+
print(f"Parent: {parent.object_type}:{parent.object_id}#{parent.permission_or_relation}")
44+
45+
if change.HasField("child_member"):
46+
member = change.child_member
47+
print(f" -> Member: {member.object_type}:{member.object_id}")
48+
```
49+
50+
### 2. Watch for Updates with WatchPermissionSets
51+
52+
After backfilling, watch for ongoing changes:
53+
54+
```python
55+
from authzed.api.materialize.v0 import (
56+
Client,
57+
WatchPermissionSetsRequest,
58+
PermissionSetChange,
59+
)
60+
from grpcutil import bearer_token_credentials
61+
62+
client = Client(
63+
"grpc.authzed.com:443",
64+
bearer_token_credentials("your_materialize_token"),
65+
)
66+
67+
request = WatchPermissionSetsRequest(
68+
optional_starting_after=last_revision, # From your last LookupPermissionSets backfill
69+
)
70+
71+
response_stream = client.WatchPermissionSets(request)
72+
73+
for response in response_stream:
74+
change = response.change
75+
76+
# Handle the operation type
77+
if change.operation == PermissionSetChange.SET_OPERATION_ADDED:
78+
# Add to materialized view
79+
pass
80+
elif change.operation == PermissionSetChange.SET_OPERATION_REMOVED:
81+
# Remove from materialized view
82+
pass
83+
```
84+
85+
### 3. Local Development
86+
87+
For local Materialize instances without TLS:
88+
89+
```python
90+
from authzed.api.materialize.v0 import (
91+
InsecureClient,
92+
LookupPermissionSetsRequest,
93+
)
94+
95+
client = InsecureClient("localhost:50051", "your_local_token")
96+
97+
request = LookupPermissionSetsRequest(limit=100)
98+
response_stream = client.LookupPermissionSets(request)
99+
100+
for response in response_stream:
101+
# Process responses
102+
pass
103+
```
104+
105+
### 4. Async Usage
106+
107+
```python
108+
import asyncio
109+
from authzed.api.materialize.v0 import (
110+
AsyncClient,
111+
LookupPermissionSetsRequest,
112+
)
113+
from grpcutil import bearer_token_credentials
114+
115+
async def fetch_permission_sets():
116+
client = AsyncClient(
117+
"grpc.authzed.com:443",
118+
bearer_token_credentials("your_materialize_token"),
119+
)
120+
request = LookupPermissionSetsRequest(limit=1000)
121+
122+
response_stream = client.LookupPermissionSets(request)
123+
124+
async for response in response_stream:
125+
# Process responses asynchronously
126+
pass
127+
128+
asyncio.run(fetch_permission_sets())
129+
```
130+
131+
## Pagination
132+
133+
### Synchronous Pagination
134+
135+
Handle large datasets with cursor-based pagination:
136+
137+
```python
138+
from authzed.api.materialize.v0 import (
139+
Client,
140+
LookupPermissionSetsRequest,
141+
)
142+
from grpcutil import bearer_token_credentials
143+
144+
client = Client(
145+
"grpc.authzed.com:443",
146+
bearer_token_credentials("your_materialize_token"),
147+
)
148+
149+
cursor = None
150+
total_processed = 0
151+
152+
while True:
153+
request = LookupPermissionSetsRequest(
154+
limit=1000,
155+
optional_starting_after_cursor=cursor,
156+
)
157+
158+
count = 0
159+
for response in client.LookupPermissionSets(request):
160+
# Process response
161+
change = response.change
162+
parent = change.parent_set
163+
print(f"Processed: {parent.object_type}:{parent.object_id}#{parent.permission_or_relation}")
164+
165+
count += 1
166+
total_processed += 1
167+
cursor = response.cursor if response.HasField("cursor") else None
168+
169+
print(f"Batch complete: {count} permission sets (total: {total_processed})")
170+
171+
# If we got fewer results than limit, we're done
172+
if count < 1000:
173+
break
174+
175+
print(f"Pagination complete! Total: {total_processed}")
176+
```
177+
178+
### Async Pagination
179+
180+
For async/await usage with automatic pagination:
181+
182+
```python
183+
import asyncio
184+
import grpc
185+
from authzed.api.materialize.v0 import (
186+
AsyncClient,
187+
LookupPermissionSetsRequest,
188+
)
189+
from grpcutil import bearer_token_credentials
190+
191+
async def paginate_permission_sets():
192+
client = AsyncClient(
193+
"grpc.authzed.com:443",
194+
bearer_token_credentials("your_materialize_token"),
195+
)
196+
197+
cursor = None
198+
total_processed = 0
199+
batch_size = 1000
200+
201+
while True:
202+
request = LookupPermissionSetsRequest(
203+
limit=batch_size,
204+
optional_starting_after_cursor=cursor,
205+
)
206+
207+
count = 0
208+
209+
try:
210+
response_stream = client.LookupPermissionSets(request)
211+
212+
async for response in response_stream:
213+
# Process response
214+
change = response.change
215+
parent = change.parent_set
216+
print(f"Processed: {parent.object_type}:{parent.object_id}#{parent.permission_or_relation}")
217+
218+
count += 1
219+
total_processed += 1
220+
221+
# Update cursor for next batch
222+
if response.HasField("cursor"):
223+
cursor = response.cursor
224+
225+
print(f"Batch complete: {count} permission sets (total: {total_processed})")
226+
227+
# If we got fewer results than limit, we're done
228+
if count < batch_size:
229+
break
230+
231+
except grpc.aio.AioRpcError as e:
232+
print(f"Error: {e.code()}: {e.details()}")
233+
break
234+
235+
print(f"Pagination complete! Total: {total_processed}")
236+
237+
# Run the async function
238+
asyncio.run(paginate_permission_sets())
239+
```
240+
241+
## Client Types
242+
243+
### Overview
244+
245+
The package provides multiple client types for different use cases:
246+
247+
| Client | Sync/Async | Detection | Best For |
248+
|--------|-----------|-----------|----------|
249+
| `Client` | Both | Auto-detect | General use, adapts to context |
250+
| `SyncClient` | Sync only | Explicit | Synchronous code, better type hints |
251+
| `AsyncClient` | Async only | Explicit | Async code with asyncio, better type hints |
252+
| `InsecureClient` | Both | Auto-detect | Local dev without TLS |
253+
254+
## Resources
255+
256+
- [Materialize Documentation](https://authzed.com/docs/authzed/concepts/authzed-materialize)
257+
- [Materialize API Reference](https://buf.build/authzed/api/docs/main:authzed.api.materialize.v0)

0 commit comments

Comments
 (0)