|
| 1 | +# ruff: noqa: E402 |
| 2 | + |
| 3 | +""" |
| 4 | +execute_api_request example |
| 5 | +
|
| 6 | +Requires a running OpenFGA server (default: http://localhost:8080). |
| 7 | + export FGA_API_URL=http://localhost:8080 # optional, this is the default |
| 8 | + python3 execute_api_request_example.py |
| 9 | +""" |
| 10 | + |
| 11 | +import asyncio |
| 12 | +import os |
| 13 | +import sys |
| 14 | + |
| 15 | + |
| 16 | +sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", "..")) |
| 17 | +sys.path.insert(0, sdk_path) |
| 18 | + |
| 19 | +from openfga_sdk import ( |
| 20 | + ClientConfiguration, |
| 21 | + CreateStoreRequest, |
| 22 | + Metadata, |
| 23 | + ObjectRelation, |
| 24 | + OpenFgaClient, |
| 25 | + RelationMetadata, |
| 26 | + RelationReference, |
| 27 | + TypeDefinition, |
| 28 | + Userset, |
| 29 | + Usersets, |
| 30 | + WriteAuthorizationModelRequest, |
| 31 | +) |
| 32 | +from openfga_sdk.client.models import ( |
| 33 | + ClientCheckRequest, |
| 34 | + ClientTuple, |
| 35 | + ClientWriteRequest, |
| 36 | +) |
| 37 | +from openfga_sdk.credentials import Credentials |
| 38 | + |
| 39 | + |
| 40 | +async def main(): |
| 41 | + api_url = os.getenv("FGA_API_URL", "http://localhost:8080") |
| 42 | + |
| 43 | + configuration = ClientConfiguration( |
| 44 | + api_url=api_url, |
| 45 | + credentials=Credentials(), |
| 46 | + ) |
| 47 | + |
| 48 | + async with OpenFgaClient(configuration) as fga_client: |
| 49 | + print("=== Setup ===") |
| 50 | + |
| 51 | + store = await fga_client.create_store( |
| 52 | + CreateStoreRequest(name="execute_api_request_test") |
| 53 | + ) |
| 54 | + fga_client.set_store_id(store.id) |
| 55 | + print(f"Created store: {store.id}") |
| 56 | + |
| 57 | + model_resp = await fga_client.write_authorization_model( |
| 58 | + WriteAuthorizationModelRequest( |
| 59 | + schema_version="1.1", |
| 60 | + type_definitions=[ |
| 61 | + TypeDefinition(type="user"), |
| 62 | + TypeDefinition( |
| 63 | + type="document", |
| 64 | + relations=dict( |
| 65 | + writer=Userset(this=dict()), |
| 66 | + viewer=Userset( |
| 67 | + union=Usersets( |
| 68 | + child=[ |
| 69 | + Userset(this=dict()), |
| 70 | + Userset( |
| 71 | + computed_userset=ObjectRelation( |
| 72 | + object="", relation="writer" |
| 73 | + ) |
| 74 | + ), |
| 75 | + ] |
| 76 | + ) |
| 77 | + ), |
| 78 | + ), |
| 79 | + metadata=Metadata( |
| 80 | + relations=dict( |
| 81 | + writer=RelationMetadata( |
| 82 | + directly_related_user_types=[ |
| 83 | + RelationReference(type="user"), |
| 84 | + ] |
| 85 | + ), |
| 86 | + viewer=RelationMetadata( |
| 87 | + directly_related_user_types=[ |
| 88 | + RelationReference(type="user"), |
| 89 | + ] |
| 90 | + ), |
| 91 | + ) |
| 92 | + ), |
| 93 | + ), |
| 94 | + ], |
| 95 | + ) |
| 96 | + ) |
| 97 | + auth_model_id = model_resp.authorization_model_id |
| 98 | + fga_client.set_authorization_model_id(auth_model_id) |
| 99 | + print(f"Created model: {auth_model_id}") |
| 100 | + |
| 101 | + await fga_client.write( |
| 102 | + ClientWriteRequest( |
| 103 | + writes=[ |
| 104 | + ClientTuple( |
| 105 | + user="user:anne", |
| 106 | + relation="writer", |
| 107 | + object="document:roadmap", |
| 108 | + ), |
| 109 | + ] |
| 110 | + ) |
| 111 | + ) |
| 112 | + print("Wrote tuple: user:anne writer document:roadmap") |
| 113 | + |
| 114 | + print("\n=== execute_api_request ===\n") |
| 115 | + |
| 116 | + # 1. ListStores |
| 117 | + print("1. ListStores (GET /stores)") |
| 118 | + raw = await fga_client.execute_api_request( |
| 119 | + operation_name="ListStores", |
| 120 | + method="GET", |
| 121 | + path="/stores", |
| 122 | + query_params={"page_size": 100}, |
| 123 | + ) |
| 124 | + sdk = await fga_client.list_stores() |
| 125 | + body = raw.json() |
| 126 | + assert raw.status == 200, f"Expected 200, got {raw.status}" |
| 127 | + assert "stores" in body |
| 128 | + assert len(body["stores"]) == len(sdk.stores) |
| 129 | + print(f" {len(body['stores'])} stores (status {raw.status})") |
| 130 | + |
| 131 | + # 2. GetStore |
| 132 | + print("2. GetStore (GET /stores/{{store_id}})") |
| 133 | + raw = await fga_client.execute_api_request( |
| 134 | + operation_name="GetStore", |
| 135 | + method="GET", |
| 136 | + path="/stores/{store_id}", |
| 137 | + path_params={"store_id": store.id}, |
| 138 | + ) |
| 139 | + sdk = await fga_client.get_store() |
| 140 | + body = raw.json() |
| 141 | + assert raw.status == 200 |
| 142 | + assert body["id"] == sdk.id |
| 143 | + assert body["name"] == sdk.name |
| 144 | + print(f" id={body['id']}, name={body['name']}") |
| 145 | + |
| 146 | + # 3. ReadAuthorizationModels |
| 147 | + print( |
| 148 | + "3. ReadAuthorizationModels (GET /stores/{{store_id}}/authorization-models)" |
| 149 | + ) |
| 150 | + raw = await fga_client.execute_api_request( |
| 151 | + operation_name="ReadAuthorizationModels", |
| 152 | + method="GET", |
| 153 | + path="/stores/{store_id}/authorization-models", |
| 154 | + path_params={"store_id": store.id}, |
| 155 | + ) |
| 156 | + sdk = await fga_client.read_authorization_models() |
| 157 | + body = raw.json() |
| 158 | + assert raw.status == 200 |
| 159 | + assert len(body["authorization_models"]) == len(sdk.authorization_models) |
| 160 | + print(f" {len(body['authorization_models'])} models") |
| 161 | + |
| 162 | + # 4. Check |
| 163 | + print("4. Check (POST /stores/{{store_id}}/check)") |
| 164 | + raw = await fga_client.execute_api_request( |
| 165 | + operation_name="Check", |
| 166 | + method="POST", |
| 167 | + path="/stores/{store_id}/check", |
| 168 | + path_params={"store_id": store.id}, |
| 169 | + body={ |
| 170 | + "tuple_key": { |
| 171 | + "user": "user:anne", |
| 172 | + "relation": "viewer", |
| 173 | + "object": "document:roadmap", |
| 174 | + }, |
| 175 | + "authorization_model_id": auth_model_id, |
| 176 | + }, |
| 177 | + ) |
| 178 | + sdk = await fga_client.check( |
| 179 | + ClientCheckRequest( |
| 180 | + user="user:anne", |
| 181 | + relation="viewer", |
| 182 | + object="document:roadmap", |
| 183 | + ) |
| 184 | + ) |
| 185 | + body = raw.json() |
| 186 | + assert raw.status == 200 |
| 187 | + assert body["allowed"] == sdk.allowed |
| 188 | + print(f" allowed={body['allowed']}") |
| 189 | + |
| 190 | + # 5. Read |
| 191 | + print("5. Read (POST /stores/{{store_id}}/read)") |
| 192 | + raw = await fga_client.execute_api_request( |
| 193 | + operation_name="Read", |
| 194 | + method="POST", |
| 195 | + path="/stores/{store_id}/read", |
| 196 | + path_params={"store_id": store.id}, |
| 197 | + body={ |
| 198 | + "tuple_key": { |
| 199 | + "user": "user:anne", |
| 200 | + "object": "document:", |
| 201 | + }, |
| 202 | + }, |
| 203 | + ) |
| 204 | + body = raw.json() |
| 205 | + assert raw.status == 200 |
| 206 | + assert "tuples" in body |
| 207 | + assert len(body["tuples"]) >= 1 |
| 208 | + print(f" {len(body['tuples'])} tuples returned") |
| 209 | + |
| 210 | + # 6. CreateStore |
| 211 | + print("6. CreateStore (POST /stores)") |
| 212 | + raw = await fga_client.execute_api_request( |
| 213 | + operation_name="CreateStore", |
| 214 | + method="POST", |
| 215 | + path="/stores", |
| 216 | + body={"name": "executor_test_store"}, |
| 217 | + ) |
| 218 | + body = raw.json() |
| 219 | + assert raw.status == 201, f"Expected 201, got {raw.status}" |
| 220 | + assert "id" in body |
| 221 | + new_store_id = body["id"] |
| 222 | + print(f" created store: {new_store_id}") |
| 223 | + |
| 224 | + # 7. DeleteStore |
| 225 | + print("7. DeleteStore (DELETE /stores/{{store_id}})") |
| 226 | + raw = await fga_client.execute_api_request( |
| 227 | + operation_name="DeleteStore", |
| 228 | + method="DELETE", |
| 229 | + path="/stores/{store_id}", |
| 230 | + path_params={"store_id": new_store_id}, |
| 231 | + ) |
| 232 | + assert raw.status == 204, f"Expected 204, got {raw.status}" |
| 233 | + print(f" deleted store: {new_store_id} (status 204)") |
| 234 | + |
| 235 | + # 8. Custom headers |
| 236 | + print("8. Custom headers (GET /stores/{{store_id}})") |
| 237 | + raw = await fga_client.execute_api_request( |
| 238 | + operation_name="GetStoreWithHeaders", |
| 239 | + method="GET", |
| 240 | + path="/stores/{store_id}", |
| 241 | + path_params={"store_id": store.id}, |
| 242 | + headers={"X-Custom-Header": "test-value"}, |
| 243 | + ) |
| 244 | + assert raw.status == 200 |
| 245 | + print(f" custom headers accepted (status {raw.status})") |
| 246 | + |
| 247 | + # 9. StreamedListObjects |
| 248 | + print( |
| 249 | + "9. StreamedListObjects (POST /stores/{{store_id}}/streamed-list-objects)" |
| 250 | + ) |
| 251 | + chunks = [] |
| 252 | + async for chunk in fga_client.execute_streamed_api_request( |
| 253 | + operation_name="StreamedListObjects", |
| 254 | + method="POST", |
| 255 | + path="/stores/{store_id}/streamed-list-objects", |
| 256 | + path_params={"store_id": store.id}, |
| 257 | + body={ |
| 258 | + "type": "document", |
| 259 | + "relation": "viewer", |
| 260 | + "user": "user:anne", |
| 261 | + "authorization_model_id": auth_model_id, |
| 262 | + }, |
| 263 | + ): |
| 264 | + chunks.append(chunk) |
| 265 | + assert len(chunks) >= 1, f"Expected at least 1 chunk, got {len(chunks)}" |
| 266 | + objects = [c["result"]["object"] for c in chunks if "result" in c] |
| 267 | + assert "document:roadmap" in objects, f"Expected document:roadmap in {objects}" |
| 268 | + print(f" {len(chunks)} chunks, objects={objects}") |
| 269 | + |
| 270 | + # Cleanup |
| 271 | + print("\n=== Cleanup ===") |
| 272 | + await fga_client.delete_store() |
| 273 | + print(f"Deleted test store: {store.id}") |
| 274 | + |
| 275 | + print("\nAll examples completed successfully.\n") |
| 276 | + |
| 277 | + |
| 278 | +asyncio.run(main()) |
0 commit comments