Skip to content

Commit 4f3eeb4

Browse files
authored
fix: custom header precedence (#230)
* fix: custom header precedence * fix: custom header precedence * test: add tests per feedback
1 parent 5bdc932 commit 4f3eeb4

File tree

10 files changed

+1243
-147
lines changed

10 files changed

+1243
-147
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fopenfga%2Fpython-sdk.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fopenfga%2Fpython-sdk?ref=badge_shield)
77
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/openfga/python-sdk/badge)](https://securityscorecards.dev/viewer/?uri=github.com/openfga/python-sdk)
88
[![Join our community](https://img.shields.io/badge/slack-cncf_%23openfga-40abb8.svg?logo=slack)](https://openfga.dev/community)
9-
[![X](https://img.shields.io/twitter/follow/openfga?color=%23179CF0&logo=twitter&style=flat-square "@openfga on Twitter")](https://x.com/openfga)
9+
[![X](https://img.shields.io/twitter/follow/openfga?color=%23179CF0&logo=x&style=flat-square "@openfga on X")](https://x.com/openfga)
1010

1111
This is an autogenerated python SDK for OpenFGA. It provides a wrapper around the [OpenFGA API definition](https://openfga.dev/api).
1212

@@ -64,7 +64,7 @@ OpenFGA is designed to make it easy for application builders to model their perm
6464

6565
- [OpenFGA Documentation](https://openfga.dev/docs)
6666
- [OpenFGA API Documentation](https://openfga.dev/api/service)
67-
- [Twitter](https://twitter.com/openfga)
67+
- [X](https://x.com/openfga)
6868
- [OpenFGA Community](https://openfga.dev/community)
6969
- [Zanzibar Academy](https://zanzibar.academy)
7070
- [Google's Zanzibar Paper (2019)](https://research.google/pubs/pub48190/)

openfga_sdk/api_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ async def __call_api(
178178
start = float(time.time())
179179

180180
# header parameters
181-
header_params = header_params or {}
182-
header_params.update(self.default_headers)
181+
header_params = {**self.default_headers, **(header_params or {})}
183182
if self.cookie:
184183
header_params["Cookie"] = self.cookie
185184
if header_params:

openfga_sdk/client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def set_heading_if_not_set(
106106
_options["headers"] = {}
107107

108108
if type(_options["headers"]) is dict:
109-
if type(_options["headers"].get(name)) not in [int, str]:
109+
if _options["headers"].get(name) is None:
110110
_options["headers"][name] = value
111111

112112
return _options

openfga_sdk/sync/api_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ def __call_api(
177177
start = float(time.time())
178178

179179
# header parameters
180-
header_params = header_params or {}
181-
header_params.update(self.default_headers)
180+
header_params = {**self.default_headers, **(header_params or {})}
182181
if self.cookie:
183182
header_params["Cookie"] = self.cookie
184183
if header_params:

openfga_sdk/sync/client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def set_heading_if_not_set(
107107
_options["headers"] = {}
108108

109109
if type(_options["headers"]) is dict:
110-
if type(_options["headers"].get(name)) not in [int, str]:
110+
if _options["headers"].get(name) is None:
111111
_options["headers"][name] = value
112112

113113
return _options

test/api/open_fga_api_test.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,123 @@ async def test_read_with_type_only_object(self, mock_request):
20482048
)
20492049
await api_client.close()
20502050

2051+
@patch.object(rest.RESTClientObject, "request")
2052+
async def test_check_custom_header_override_default_header(self, mock_request):
2053+
"""Test case for per-request custom header overriding default header
2054+
2055+
Per-request custom headers should override default headers with the same name
2056+
"""
2057+
2058+
# First, mock the response
2059+
response_body = '{"allowed": true}'
2060+
mock_request.return_value = mock_response(response_body, 200)
2061+
2062+
configuration = self.configuration
2063+
configuration.store_id = store_id
2064+
async with openfga_sdk.ApiClient(configuration) as api_client:
2065+
# Set a default header
2066+
api_client.set_default_header("X-Custom-Header", "default-value")
2067+
api_instance = open_fga_api.OpenFgaApi(api_client)
2068+
body = CheckRequest(
2069+
tuple_key=TupleKey(
2070+
object="document:2021-budget",
2071+
relation="reader",
2072+
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
2073+
),
2074+
)
2075+
# Make request with per-request custom header that should override the default
2076+
api_response = await api_instance.check(
2077+
body=body,
2078+
_headers={"X-Custom-Header": "per-request-value"},
2079+
)
2080+
self.assertIsInstance(api_response, CheckResponse)
2081+
self.assertTrue(api_response.allowed)
2082+
# Make sure the API was called with the per-request header value, not the default
2083+
expected_headers = urllib3.response.HTTPHeaderDict(
2084+
{
2085+
"Accept": "application/json",
2086+
"Content-Type": "application/json",
2087+
"User-Agent": "openfga-sdk python/0.9.6",
2088+
"X-Custom-Header": "per-request-value", # Should be the per-request value
2089+
}
2090+
)
2091+
mock_request.assert_called_once_with(
2092+
"POST",
2093+
"http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check",
2094+
headers=expected_headers,
2095+
query_params=[],
2096+
post_params=[],
2097+
body={
2098+
"tuple_key": {
2099+
"object": "document:2021-budget",
2100+
"relation": "reader",
2101+
"user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
2102+
}
2103+
},
2104+
_preload_content=ANY,
2105+
_request_timeout=None,
2106+
)
2107+
2108+
@patch.object(rest.RESTClientObject, "request")
2109+
async def test_check_per_request_header_and_default_header_coexist(
2110+
self, mock_request
2111+
):
2112+
"""Test case for per-request custom header and default header coexisting
2113+
2114+
Per-request custom headers should be merged with default headers
2115+
"""
2116+
2117+
# First, mock the response
2118+
response_body = '{"allowed": true}'
2119+
mock_request.return_value = mock_response(response_body, 200)
2120+
2121+
configuration = self.configuration
2122+
configuration.store_id = store_id
2123+
async with openfga_sdk.ApiClient(configuration) as api_client:
2124+
# Set a default header
2125+
api_client.set_default_header("X-Default-Header", "default-value")
2126+
api_instance = open_fga_api.OpenFgaApi(api_client)
2127+
body = CheckRequest(
2128+
tuple_key=TupleKey(
2129+
object="document:2021-budget",
2130+
relation="reader",
2131+
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
2132+
),
2133+
)
2134+
# Make request with per-request custom header (different from default)
2135+
api_response = await api_instance.check(
2136+
body=body,
2137+
_headers={"X-Per-Request-Header": "per-request-value"},
2138+
)
2139+
self.assertIsInstance(api_response, CheckResponse)
2140+
self.assertTrue(api_response.allowed)
2141+
# Make sure both headers are present in the request
2142+
expected_headers = urllib3.response.HTTPHeaderDict(
2143+
{
2144+
"Accept": "application/json",
2145+
"Content-Type": "application/json",
2146+
"User-Agent": "openfga-sdk python/0.9.6",
2147+
"X-Default-Header": "default-value", # Default header preserved
2148+
"X-Per-Request-Header": "per-request-value", # Per-request header added
2149+
}
2150+
)
2151+
mock_request.assert_called_once_with(
2152+
"POST",
2153+
"http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check",
2154+
headers=expected_headers,
2155+
query_params=[],
2156+
post_params=[],
2157+
body={
2158+
"tuple_key": {
2159+
"object": "document:2021-budget",
2160+
"relation": "reader",
2161+
"user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
2162+
}
2163+
},
2164+
_preload_content=ANY,
2165+
_request_timeout=None,
2166+
)
2167+
20512168

20522169
if __name__ == "__main__":
20532170
unittest.main()

0 commit comments

Comments
 (0)