Skip to content

Commit 369c93b

Browse files
authored
feat(python-sdk): add headers configuration property (#630)
2 parents b60861c + 35b4b34 commit 369c93b

8 files changed

Lines changed: 746 additions & 0 deletions

File tree

config/clients/python/template/README_initializing.mustache

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,66 @@ async def main():
7676
return api_response
7777
```
7878

79+
### Custom Headers
80+
81+
#### Default Headers
82+
83+
You can configure default headers that will be sent with every request by passing them to the `ClientConfiguration`:
84+
85+
```python
86+
from {{packageName}} import ClientConfiguration, OpenFgaClient
87+
88+
89+
async def main():
90+
configuration = ClientConfiguration(
91+
api_url=FGA_API_URL,
92+
store_id=FGA_STORE_ID,
93+
authorization_model_id=FGA_MODEL_ID,
94+
headers={
95+
"X-Custom-Header": "default-value",
96+
"X-Request-Source": "my-app",
97+
},
98+
)
99+
100+
async with OpenFgaClient(configuration) as fga_client:
101+
api_response = await fga_client.read_authorization_models()
102+
return api_response
103+
```
104+
105+
#### Per-Request Headers
106+
107+
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.
108+
109+
```python
110+
from {{packageName}} import ClientConfiguration, OpenFgaClient
111+
from {{packageName}}.client.models import ClientCheckRequest
112+
113+
114+
async def main():
115+
configuration = ClientConfiguration(
116+
api_url=FGA_API_URL,
117+
store_id=FGA_STORE_ID,
118+
authorization_model_id=FGA_MODEL_ID,
119+
)
120+
121+
async with OpenFgaClient(configuration) as fga_client:
122+
# Add custom headers to a specific request
123+
result = await fga_client.check(
124+
body=ClientCheckRequest(
125+
user="user:anne",
126+
relation="viewer",
127+
object="document:roadmap",
128+
),
129+
options={
130+
"headers": {
131+
"X-Request-ID": "123e4567-e89b-12d3-a456-426614174000",
132+
"X-Custom-Header": "custom-value", # Overrides default header value
133+
}
134+
}
135+
)
136+
return result
137+
```
138+
79139
#### Synchronous Client
80140

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

config/clients/python/template/src/client/client.py.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ class OpenFgaClient:
135135
self._api_client = ApiClient(configuration)
136136
self._api = OpenFgaApi(self._api_client)
137137

138+
# Set default headers from configuration
139+
if configuration.headers:
140+
for header_name, header_value in configuration.headers.items():
141+
self._api_client.set_default_header(header_name, header_value)
142+
138143
{{#asyncio}}
139144
async def __aenter__(self):
140145
return self

config/clients/python/template/src/client/configuration.py.mustache

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class ClientConfiguration(Configuration):
4242
]
4343
| None
4444
) = None,
45+
headers: dict[str, str] | None = None,
4546
):
4647
super().__init__(
4748
api_scheme,
@@ -53,6 +54,7 @@ class ClientConfiguration(Configuration):
5354
api_url=api_url,
5455
timeout_millisec=timeout_millisec,
5556
telemetry=telemetry,
57+
headers=headers,
5658
)
5759
self._authorization_model_id = authorization_model_id
5860

config/clients/python/template/src/configuration.py.mustache

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ class Configuration:
193193
| None
194194
) = None,
195195
timeout_millisec: int | None = None,
196+
headers: dict[str, str] | None = None,
196197
):
197198
"""Constructor"""
198199
self._url = api_url
@@ -317,6 +318,10 @@ class Configuration:
317318
"""Telemetry configuration
318319
"""
319320

321+
self._headers = headers or {}
322+
"""Default headers to be sent with every request
323+
"""
324+
320325
def __deepcopy__(self, memo):
321326
cls = self.__class__
322327
result = cls.__new__(cls)
@@ -647,6 +652,17 @@ class Configuration:
647652
f"timeout_millisec not within reasonable range (0,60000), {self._timeout_millisec}"
648653
)
649654

655+
if self._headers is not None:
656+
for key, value in self._headers.items():
657+
if not isinstance(key, str):
658+
raise FgaValidationException(
659+
f"header keys must be strings, got {type(key).__name__} for key {key}"
660+
)
661+
if not isinstance(value, str):
662+
raise FgaValidationException(
663+
f"header values must be strings, got {type(value).__name__} for key '{key}'"
664+
)
665+
650666
@property
651667
def api_scheme(self):
652668
"""Return connection is https or http."""
@@ -731,3 +747,31 @@ class Configuration:
731747
Update timeout milliseconds
732748
"""
733749
self._timeout_millisec = value
750+
751+
@property
752+
def headers(self) -> dict[str, str]:
753+
"""
754+
Return default headers to be sent with every request.
755+
756+
Headers are key-value pairs that will be included in all API requests.
757+
Common use cases include correlation IDs, API versioning, and tenant identification.
758+
"""
759+
return self._headers
760+
761+
@headers.setter
762+
def headers(self, value: dict[str, str] | None) -> None:
763+
"""
764+
Update default headers to be sent with every request.
765+
766+
Args:
767+
value: Dictionary of header names to values, or None to clear headers.
768+
Both keys and values must be strings.
769+
770+
Raises:
771+
FgaValidationException: If value is not a dict or None.
772+
"""
773+
if value is not None and not isinstance(value, dict):
774+
raise FgaValidationException(
775+
f"headers must be a dict or None, got {type(value).__name__}"
776+
)
777+
self._headers = value or {}

config/clients/python/template/src/sync/client/client.py.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ class OpenFgaClient:
135135
self._api_client = ApiClient(configuration)
136136
self._api = OpenFgaApi(self._api_client)
137137

138+
# Set default headers from configuration
139+
if configuration.headers:
140+
for header_name, header_value in configuration.headers.items():
141+
self._api_client.set_default_header(header_name, header_value)
142+
138143
def __enter__(self):
139144
return self
140145

0 commit comments

Comments
 (0)