Skip to content

Commit ac3395c

Browse files
evansimsewanharris
andauthored
feat: add headers configuration property (#232)
* docs: add custom headers guidance to README * feat: add `headers` configuration property --------- Co-authored-by: Ewan Harris <[email protected]>
1 parent 8e38d75 commit ac3395c

File tree

8 files changed

+746
-0
lines changed

8 files changed

+746
-0
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,66 @@ async def main():
200200
return api_response
201201
```
202202

203+
### Custom Headers
204+
205+
#### Default Headers
206+
207+
You can configure default headers that will be sent with every request by passing them to the `ClientConfiguration`:
208+
209+
```python
210+
from openfga_sdk import ClientConfiguration, OpenFgaClient
211+
212+
213+
async def main():
214+
configuration = ClientConfiguration(
215+
api_url=FGA_API_URL,
216+
store_id=FGA_STORE_ID,
217+
authorization_model_id=FGA_MODEL_ID,
218+
headers={
219+
"X-Custom-Header": "default-value",
220+
"X-Request-Source": "my-app",
221+
},
222+
)
223+
224+
async with OpenFgaClient(configuration) as fga_client:
225+
api_response = await fga_client.read_authorization_models()
226+
return api_response
227+
```
228+
229+
#### Per-Request Headers
230+
231+
You can also send custom headers on a per-request basis by using the `options` parameter. Per-request headers will override any default headers with the same name.
232+
233+
```python
234+
from openfga_sdk import ClientConfiguration, OpenFgaClient
235+
from openfga_sdk.client.models import ClientCheckRequest
236+
237+
238+
async def main():
239+
configuration = ClientConfiguration(
240+
api_url=FGA_API_URL,
241+
store_id=FGA_STORE_ID,
242+
authorization_model_id=FGA_MODEL_ID,
243+
)
244+
245+
async with OpenFgaClient(configuration) as fga_client:
246+
# Add custom headers to a specific request
247+
result = await fga_client.check(
248+
body=ClientCheckRequest(
249+
user="user:anne",
250+
relation="viewer",
251+
object="document:roadmap",
252+
),
253+
options={
254+
"headers": {
255+
"X-Request-ID": "123e4567-e89b-12d3-a456-426614174000",
256+
"X-Custom-Header": "custom-value", # Overrides default header value
257+
}
258+
}
259+
)
260+
return result
261+
```
262+
203263
#### Synchronous Client
204264

205265
To run outside of an async context, the project exports a synchronous client

openfga_sdk/client/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ def __init__(self, configuration: ClientConfiguration):
168168
self._api_client = ApiClient(configuration)
169169
self._api = OpenFgaApi(self._api_client)
170170

171+
# Set default headers from configuration
172+
if configuration.headers:
173+
for header_name, header_value in configuration.headers.items():
174+
self._api_client.set_default_header(header_name, header_value)
175+
171176
async def __aenter__(self):
172177
return self
173178

openfga_sdk/client/configuration.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(
5353
]
5454
| None
5555
) = None,
56+
headers: dict[str, str] | None = None,
5657
):
5758
super().__init__(
5859
api_scheme,
@@ -64,6 +65,7 @@ def __init__(
6465
api_url=api_url,
6566
timeout_millisec=timeout_millisec,
6667
telemetry=telemetry,
68+
headers=headers,
6769
)
6870
self._authorization_model_id = authorization_model_id
6971

openfga_sdk/configuration.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def __init__(
202202
| None
203203
) = None,
204204
timeout_millisec: int | None = None,
205+
headers: dict[str, str] | None = None,
205206
):
206207
"""Constructor"""
207208
self._url = api_url
@@ -326,6 +327,10 @@ def __init__(
326327
"""Telemetry configuration
327328
"""
328329

330+
self._headers = headers or {}
331+
"""Default headers to be sent with every request
332+
"""
333+
329334
def __deepcopy__(self, memo):
330335
cls = self.__class__
331336
result = cls.__new__(cls)
@@ -656,6 +661,17 @@ def is_valid(self):
656661
f"timeout_millisec not within reasonable range (0,60000), {self._timeout_millisec}"
657662
)
658663

664+
if self._headers is not None:
665+
for key, value in self._headers.items():
666+
if not isinstance(key, str):
667+
raise FgaValidationException(
668+
f"header keys must be strings, got {type(key).__name__} for key {key}"
669+
)
670+
if not isinstance(value, str):
671+
raise FgaValidationException(
672+
f"header values must be strings, got {type(value).__name__} for key '{key}'"
673+
)
674+
659675
@property
660676
def api_scheme(self):
661677
"""Return connection is https or http."""
@@ -740,3 +756,31 @@ def timeout_millisec(self, value):
740756
Update timeout milliseconds
741757
"""
742758
self._timeout_millisec = value
759+
760+
@property
761+
def headers(self) -> dict[str, str]:
762+
"""
763+
Return default headers to be sent with every request.
764+
765+
Headers are key-value pairs that will be included in all API requests.
766+
Common use cases include correlation IDs, API versioning, and tenant identification.
767+
"""
768+
return self._headers
769+
770+
@headers.setter
771+
def headers(self, value: dict[str, str] | None) -> None:
772+
"""
773+
Update default headers to be sent with every request.
774+
775+
Args:
776+
value: Dictionary of header names to values, or None to clear headers.
777+
Both keys and values must be strings.
778+
779+
Raises:
780+
FgaValidationException: If value is not a dict or None.
781+
"""
782+
if value is not None and not isinstance(value, dict):
783+
raise FgaValidationException(
784+
f"headers must be a dict or None, got {type(value).__name__}"
785+
)
786+
self._headers = value or {}

openfga_sdk/sync/client/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ def __init__(self, configuration: ClientConfiguration) -> None:
169169
self._api_client = ApiClient(configuration)
170170
self._api = OpenFgaApi(self._api_client)
171171

172+
# Set default headers from configuration
173+
if configuration.headers:
174+
for header_name, header_value in configuration.headers.items():
175+
self._api_client.set_default_header(header_name, header_value)
176+
172177
def __enter__(self):
173178
return self
174179

0 commit comments

Comments
 (0)