Skip to content
Open
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ build-client-python:

sort -uo ${CLIENTS_OUTPUT_DIR}/fga-python-sdk/.openapi-generator/FILES{,}

make run-in-docker sdk_language=python image=busybox:${BUSYBOX_DOCKER_TAG} command="/bin/sh -c 'patch -p1 /module/openfga_sdk/api/open_fga_api.py /config/clients/python/patches/open_fga_api.py.patch && \
patch -p1 /module/docs/OpenFgaApi.md /config/clients/python/patches/OpenFgaApi.md.patch'"
make run-in-docker sdk_language=python image=busybox:${BUSYBOX_DOCKER_TAG} command="/bin/sh -c 'patch -p1 /module/docs/OpenFgaApi.md /config/clients/python/patches/OpenFgaApi.md.patch'"

make run-in-docker sdk_language=python image=ghcr.io/astral-sh/uv:python${PYTHON_DOCKER_TAG}-alpine command="/bin/sh -c 'export UV_LINK_MODE=copy && \
uv sync && \
Expand Down
4 changes: 3 additions & 1 deletion config/clients/python/.openapi-generator-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ openfga_sdk/api/open_fga_api
openfga_sdk/sync/open_fga_api.py
openfga_sdk/api_client.py
openfga_sdk/configuration.py
openfga_sdk/constants.py
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't ignore constants

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh the reason I removed it was because of the sdk version and user agent driven from the generator
Since now we are using release please to handle and bump that, but you're right there's a lot of other common configs in this that are controlled from the generator.
Maybe we move the versions to a different file?
Or sync it back once in a while 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have extracted it to a version file that is SDK maintained @rhamzeh

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to fail in CI, investigating, maybe my sequencing is not correct

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

openfga_sdk/exceptions.py
openfga_sdk/rest.py
openfga_sdk/rest.py
VERSION.txt
3 changes: 2 additions & 1 deletion config/clients/python/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"sdkId": "python",
"gitRepoId": "python-sdk",
"packageName": "openfga_sdk",
"packageVersion": "0.9.9",
"packageVersion": "0.10.3",
"packageDescription": "Python SDK for OpenFGA",
"packageDetailedDescription": "This is an autogenerated python SDK for OpenFGA. It provides a wrapper around the [OpenFGA API definition](https://openfga.dev/api).",
"fossaComplianceNoticeId": "2f8a8629-b46c-435e-b8cd-1174a674fb4b",
Expand All @@ -12,6 +12,7 @@
"pythonMinimumRuntime": "3.10",
"openTelemetryDocumentation": "docs/opentelemetry.md",
"supportsStreamedListObjects": "streamed_list_objects",
"supportsCallingOtherEndpoints": true,
"files": {
"src/constants.mustache": {
"destinationFilename": "openfga_sdk/constants.py",
Expand Down
91 changes: 91 additions & 0 deletions config/clients/python/template/README_calling_api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -962,3 +962,94 @@ body = [ClientAssertion(

response = await fga_client.write_assertions(body, options)
```

### Calling Other Endpoints

In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `execute_api_request` method available on the `OpenFgaClient`. It allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the HTTP method, path, body, query parameters, and path parameters, while still honoring the client configuration (authentication, telemetry, retries, and error handling).

For streaming endpoints (e.g. `streamed-list-objects`), use `execute_streamed_api_request` instead. It returns an `AsyncIterator` (or `Iterator` in the sync client) that yields one parsed JSON object per chunk.

This is useful when:
- You want to call a new endpoint that is not yet supported by the SDK
- You are using an earlier version of the SDK that doesn't yet support a particular endpoint
- You have a custom endpoint deployed that extends the OpenFGA API

#### Example: Calling a Custom Endpoint with POST

```python
# Call a custom endpoint using path parameters
response = await fga_client.execute_api_request(
operation_name="CustomEndpoint", # For telemetry/logging
method="POST",
path="/stores/{store_id}/custom-endpoint",
path_params={"store_id": FGA_STORE_ID},
body={
"user": "user:bob",
"action": "custom_action",
"resource": "resource:123",
},
query_params={
"page_size": 20,
},
)

# Access the response data
if response.status == 200:
result = response.json()
print(f"Response: {result}")
```

#### Example: Calling an existing endpoint with GET

```python
# Get a list of stores with query parameters
stores_response = await fga_client.execute_api_request(
operation_name="ListStores",
method="GET",
path="/stores",
query_params={
"page_size": 10,
"continuation_token": "eyJwayI6...",
},
)

stores = stores_response.json()
print("Stores:", stores)
```

#### Example: Calling a Streaming Endpoint

```python
# Stream objects visible to a user
async for chunk in fga_client.execute_streamed_api_request(
operation_name="StreamedListObjects",
method="POST",
path="/stores/{store_id}/streamed-list-objects",
path_params={"store_id": FGA_STORE_ID},
body={
"type": "document",
"relation": "viewer",
"user": "user:anne",
"authorization_model_id": FGA_MODEL_ID,
},
):
# Each chunk has the shape {"result": {"object": "..."}} or {"error": {...}}
if "result" in chunk:
print(chunk["result"]["object"]) # e.g. "document:roadmap"
```

#### Example: Using Path Parameters

Path parameters are specified in the path using `{param_name}` syntax and must all be provided explicitly via `path_params` (URL-encoded automatically):

```python
response = await fga_client.execute_api_request(
operation_name="GetAuthorizationModel",
method="GET",
path="/stores/{store_id}/authorization-models/{model_id}",
path_params={
"store_id": "your-store-id",
"model_id": "your-model-id",
},
)
```
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[![pypi](https://img.shields.io/pypi/v/{{packageName}}.svg?style=flat)](https://pypi.org/project/{{packageName}})
[![Socket Badge](https://badge.socket.dev/pypi/package/openfga-sdk/{{packageVersion}})](https://socket.dev/pypi/package/openfga-sdk)
[![Socket Badge](https://badge.socket.dev/pypi/package/openfga-sdk/{{packageVersion}})](https://socket.dev/pypi/package/openfga-sdk) <!-- x-release-please-version -->
[![DeepWiki](https://img.shields.io/badge/DeepWiki-openfga%2Fpython--sdk-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/openfga/python-sdk)
37 changes: 33 additions & 4 deletions config/clients/python/template/README_initializing.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ async def main():
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
return api_response
```

Expand All @@ -43,7 +42,6 @@ async def main():
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
return api_response
```

Expand All @@ -63,16 +61,47 @@ async def main():
method='client_credentials',
configuration=CredentialConfiguration(
api_issuer=FGA_API_TOKEN_ISSUER,
api_audience=FGA_API_AUDIENCE,
api_audience=FGA_API_AUDIENCE, # optional, required for Auth0; omit for standard OAuth2
client_id=FGA_CLIENT_ID,
client_secret=FGA_CLIENT_SECRET,
# scopes="read write", # optional, space-separated OAuth2 scopes
)
)
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
return api_response
```

> **Note:** `api_issuer` accepts either a hostname (e.g., `issuer.fga.example`, which defaults to `https://<hostname>/oauth/token`) or a full token endpoint URL (e.g., `https://oauth.fga.example/token`). Use the full URL when your OAuth2 provider uses a non-standard token endpoint path.

#### OAuth2 Client Credentials (Standard OAuth2)

For OAuth2 providers that use `scope` instead of `audience`:

```python
from {{packageName}} import ClientConfiguration, OpenFgaClient
from {{packageName}}.credentials import Credentials, CredentialConfiguration


async def main():
configuration = ClientConfiguration(
api_url=FGA_API_URL, # required
store_id=FGA_STORE_ID, # optional
authorization_model_id=FGA_MODEL_ID, # optional
credentials=Credentials(
method='client_credentials',
configuration=CredentialConfiguration(
api_issuer="https://oauth.fga.example/token", # full token endpoint URL
client_id=FGA_CLIENT_ID,
client_secret=FGA_CLIENT_SECRET,
scopes="email profile", # space-separated OAuth2 scopes
)
)
)
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
return api_response
```

Expand Down
6 changes: 5 additions & 1 deletion config/clients/python/template/pyproject.toml.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,15 @@ testpaths = [
"integration",
]

addopts = "--cov=openfga_sdk --cov-report term-missing --cov-report xml --cov-report html"
addopts = "--cov=openfga_sdk --cov-report term-missing --cov-report xml --cov-report html --strict-markers"

asyncio_mode = "strict"
asyncio_default_fixture_loop_scope = "function"

markers = [
"integration: marks tests as integration tests requiring a running OpenFGA server",
]

[tool.mypy]
python_version = "{{pythonMinimumRuntime}}"
packages = "openfga_sdk"
Expand Down
28 changes: 15 additions & 13 deletions config/clients/python/template/src/__init__.py.mustache
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
{{>partial_header}}

__version__ = "{{packageVersion}}"

from {{packageName}}.client.client import OpenFgaClient
from {{packageName}}.client.configuration import ClientConfiguration

{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classFilename}} import {{classname}}
{{/apis}}{{/apiInfo}}
from {{packageName}}.api_client import ApiClient
from {{packageName}}.client.client import OpenFgaClient
from {{packageName}}.client.configuration import ClientConfiguration
from {{packageName}}.client.models.raw_response import RawResponse
from {{packageName}}.configuration import Configuration

from {{packageName}}.exceptions import OpenApiException
from {{packageName}}.exceptions import FgaValidationException
from {{packageName}}.exceptions import ApiValueError
from {{packageName}}.exceptions import ApiKeyError
from {{packageName}}.exceptions import ApiAttributeError
from {{packageName}}.exceptions import ApiException

from {{packageName}}.constants import SDK_VERSION
from {{packageName}}.exceptions import (
ApiAttributeError,
ApiException,
ApiKeyError,
ApiValueError,
FgaValidationException,
OpenApiException,
)
{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}
{{/model}}{{/models}}
from {{packageName}}.telemetry.configuration import (
Expand All @@ -31,9 +30,12 @@ from {{packageName}}.telemetry.configuration import (
__import__('sys').setrecursionlimit({{{.}}})
{{/recursionLimit}}

__version__ = SDK_VERSION

__all__ = [
"OpenFgaClient",
"ClientConfiguration",
"RawResponse",
{{#apiInfo}}{{#apis}}"{{classname}}",
{{/apis}}{{/apiInfo}}
"ApiClient",
Expand Down
Loading
Loading