From 3e8be76a974d368693e37de3cc25b2d23ee403d3 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Fri, 3 Oct 2025 14:49:35 -0500 Subject: [PATCH 1/3] fix: custom header precedence --- README.md | 4 +- openfga_sdk/api_client.py | 3 +- openfga_sdk/client/client.py | 2 +- openfga_sdk/sync/api_client.py | 3 +- openfga_sdk/sync/client/client.py | 2 +- test/api/open_fga_api_test.py | 117 ++++++++++++ test/client/client_test.py | 99 +++++++++- test/sync/client/client_test.py | 99 +++++++++- test/sync/open_fga_api_test.py | 115 ++++++++++++ uv.lock | 288 ++++++++++++++++-------------- 10 files changed, 585 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 7f7ca97..fccd493 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![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) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/openfga/python-sdk/badge)](https://securityscorecards.dev/viewer/?uri=github.com/openfga/python-sdk) [![Join our community](https://img.shields.io/badge/slack-cncf_%23openfga-40abb8.svg?logo=slack)](https://openfga.dev/community) -[![X](https://img.shields.io/twitter/follow/openfga?color=%23179CF0&logo=twitter&style=flat-square "@openfga on Twitter")](https://x.com/openfga) +[![X](https://img.shields.io/twitter/follow/openfga?color=%23179CF0&logo=x&style=flat-square "@openfga on X")](https://x.com/openfga) This is an autogenerated python SDK for OpenFGA. It provides a wrapper around the [OpenFGA API definition](https://openfga.dev/api). @@ -64,7 +64,7 @@ OpenFGA is designed to make it easy for application builders to model their perm - [OpenFGA Documentation](https://openfga.dev/docs) - [OpenFGA API Documentation](https://openfga.dev/api/service) -- [Twitter](https://twitter.com/openfga) +- [X](https://x.com/openfga) - [OpenFGA Community](https://openfga.dev/community) - [Zanzibar Academy](https://zanzibar.academy) - [Google's Zanzibar Paper (2019)](https://research.google/pubs/pub48190/) diff --git a/openfga_sdk/api_client.py b/openfga_sdk/api_client.py index 5a54500..d428caa 100644 --- a/openfga_sdk/api_client.py +++ b/openfga_sdk/api_client.py @@ -178,8 +178,7 @@ async def __call_api( start = float(time.time()) # header parameters - header_params = header_params or {} - header_params.update(self.default_headers) + header_params = {**self.default_headers, **(header_params or {})} if self.cookie: header_params["Cookie"] = self.cookie if header_params: diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 2e0459d..749a04a 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -106,7 +106,7 @@ def set_heading_if_not_set( _options["headers"] = {} if type(_options["headers"]) is dict: - if type(_options["headers"].get(name)) not in [int, str]: + if _options["headers"].get(name) is None: _options["headers"][name] = value return _options diff --git a/openfga_sdk/sync/api_client.py b/openfga_sdk/sync/api_client.py index a588092..196e83b 100644 --- a/openfga_sdk/sync/api_client.py +++ b/openfga_sdk/sync/api_client.py @@ -177,8 +177,7 @@ def __call_api( start = float(time.time()) # header parameters - header_params = header_params or {} - header_params.update(self.default_headers) + header_params = {**self.default_headers, **(header_params or {})} if self.cookie: header_params["Cookie"] = self.cookie if header_params: diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 732196c..e900b78 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -107,7 +107,7 @@ def set_heading_if_not_set( _options["headers"] = {} if type(_options["headers"]) is dict: - if type(_options["headers"].get(name)) not in [int, str]: + if _options["headers"].get(name) is None: _options["headers"][name] = value return _options diff --git a/test/api/open_fga_api_test.py b/test/api/open_fga_api_test.py index 42a43cf..62f1556 100644 --- a/test/api/open_fga_api_test.py +++ b/test/api/open_fga_api_test.py @@ -2048,6 +2048,123 @@ async def test_read_with_type_only_object(self, mock_request): ) await api_client.close() + @patch.object(rest.RESTClientObject, "request") + async def test_check_custom_header_override_default_header(self, mock_request): + """Test case for per-request custom header overriding default header + + Per-request custom headers should override default headers with the same name + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with openfga_sdk.ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Custom-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header that should override the default + api_response = await api_instance.check( + body=body, + _headers={"X-Custom-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the per-request header value, not the default + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Custom-Header": "per-request-value", # Should be the per-request value + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + + @patch.object(rest.RESTClientObject, "request") + async def test_check_per_request_header_and_default_header_coexist( + self, mock_request + ): + """Test case for per-request custom header and default header coexisting + + Per-request custom headers should be merged with default headers + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with openfga_sdk.ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Default-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header (different from default) + api_response = await api_instance.check( + body=body, + _headers={"X-Per-Request-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure both headers are present in the request + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Default-Header": "default-value", # Default header preserved + "X-Per-Request-Header": "per-request-value", # Per-request header added + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + if __name__ == "__main__": unittest.main() diff --git a/test/client/client_test.py b/test/client/client_test.py index a7117c9..286d72e 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -21,7 +21,7 @@ from openfga_sdk import rest from openfga_sdk.client import ClientConfiguration -from openfga_sdk.client.client import OpenFgaClient +from openfga_sdk.client.client import OpenFgaClient, set_heading_if_not_set from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ClientBatchCheckItem from openfga_sdk.client.models.batch_check_request import ClientBatchCheckRequest @@ -3273,3 +3273,100 @@ def test_configuration_authorization_model_id_invalid(self): authorization_model_id="abcd", ) self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_set_heading_if_not_set_when_none_provided(self): + """Should set header when no options provided""" + result = set_heading_if_not_set(None, "X-Test-Header", "default-value") + + self.assertIsNotNone(result) + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_empty_options_provided(self): + """Should set header when empty options dict provided""" + result = set_heading_if_not_set({}, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_no_headers_in_options(self): + """Should set header when options dict has no headers key""" + options = {"page_size": 10} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + self.assertEqual(result["page_size"], 10) + + def test_set_heading_if_not_set_when_headers_empty(self): + """Should set header when headers dict is empty""" + options = {"headers": {}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_override_existing_custom_header(self): + """Should NOT override when custom header already exists - this is the critical test for the bug fix""" + options = {"headers": {"X-Test-Header": "custom-value"}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Custom header should be preserved, NOT overridden by default + self.assertEqual(result["headers"]["X-Test-Header"], "custom-value") + + def test_set_heading_if_not_set_preserves_other_headers_when_setting_new_header( + self, + ): + """Should preserve existing headers when setting a new one""" + options = {"headers": {"X-Existing-Header": "existing-value"}} + result = set_heading_if_not_set(options, "X-New-Header", "new-value") + + self.assertEqual(result["headers"]["X-Existing-Header"], "existing-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + def test_set_heading_if_not_set_handles_integer_header_values(self): + """Should not override existing integer header values""" + options = {"headers": {"X-Retry-Count": 5}} + result = set_heading_if_not_set(options, "X-Retry-Count", 1) + + # Existing integer value should be preserved + self.assertEqual(result["headers"]["X-Retry-Count"], 5) + + def test_set_heading_if_not_set_handles_non_dict_headers_value(self): + """Should convert non-dict headers value to dict""" + options = {"headers": "invalid"} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIsInstance(result["headers"], dict) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_mutate_when_header_exists(self): + """Should return same dict when header already exists""" + options = {"headers": {"X-Test-Header": "custom-value"}} + original_value = options["headers"]["X-Test-Header"] + + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Should return the same modified dict + self.assertIs(result, options) + # Value should not have changed + self.assertEqual(result["headers"]["X-Test-Header"], original_value) + + def test_set_heading_if_not_set_multiple_headers_with_mixed_states(self): + """Should handle multiple headers, some existing and some new""" + options = { + "headers": { + "X-Custom-Header": "custom-value", + "X-Another-Header": "another-value", + } + } + + # Try to set a custom header (should not override) + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "custom-value") + + # Try to set a new header (should be added) + result = set_heading_if_not_set(result, "X-New-Header", "new-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + # Original headers should still exist + self.assertEqual(result["headers"]["X-Another-Header"], "another-value") diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index 6e4b790..67ebff5 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -85,7 +85,7 @@ WriteAuthorizationModelResponse, ) from openfga_sdk.sync import rest -from openfga_sdk.sync.client.client import OpenFgaClient +from openfga_sdk.sync.client.client import OpenFgaClient, set_heading_if_not_set store_id = "01YCP46JKYM8FJCQ37NMBYHE5X" @@ -3275,3 +3275,100 @@ def test_configuration_authorization_model_id_invalid(self): authorization_model_id="abcd", ) self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_set_heading_if_not_set_when_none_provided(self): + """Should set header when no options provided""" + result = set_heading_if_not_set(None, "X-Test-Header", "default-value") + + self.assertIsNotNone(result) + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_empty_options_provided(self): + """Should set header when empty options dict provided""" + result = set_heading_if_not_set({}, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_no_headers_in_options(self): + """Should set header when options dict has no headers key""" + options = {"page_size": 10} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + self.assertEqual(result["page_size"], 10) + + def test_set_heading_if_not_set_when_headers_empty(self): + """Should set header when headers dict is empty""" + options = {"headers": {}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_override_existing_custom_header(self): + """Should NOT override when custom header already exists - this is the critical test for the bug fix""" + options = {"headers": {"X-Test-Header": "custom-value"}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Custom header should be preserved, NOT overridden by default + self.assertEqual(result["headers"]["X-Test-Header"], "custom-value") + + def test_set_heading_if_not_set_preserves_other_headers_when_setting_new_header( + self, + ): + """Should preserve existing headers when setting a new one""" + options = {"headers": {"X-Existing-Header": "existing-value"}} + result = set_heading_if_not_set(options, "X-New-Header", "new-value") + + self.assertEqual(result["headers"]["X-Existing-Header"], "existing-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + def test_set_heading_if_not_set_handles_integer_header_values(self): + """Should not override existing integer header values""" + options = {"headers": {"X-Retry-Count": 5}} + result = set_heading_if_not_set(options, "X-Retry-Count", 1) + + # Existing integer value should be preserved + self.assertEqual(result["headers"]["X-Retry-Count"], 5) + + def test_set_heading_if_not_set_handles_non_dict_headers_value(self): + """Should convert non-dict headers value to dict""" + options = {"headers": "invalid"} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIsInstance(result["headers"], dict) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_mutate_when_header_exists(self): + """Should return same dict when header already exists""" + options = {"headers": {"X-Test-Header": "custom-value"}} + original_value = options["headers"]["X-Test-Header"] + + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Should return the same modified dict + self.assertIs(result, options) + # Value should not have changed + self.assertEqual(result["headers"]["X-Test-Header"], original_value) + + def test_set_heading_if_not_set_multiple_headers_with_mixed_states(self): + """Should handle multiple headers, some existing and some new""" + options = { + "headers": { + "X-Custom-Header": "custom-value", + "X-Another-Header": "another-value", + } + } + + # Try to set a custom header (should not override) + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "custom-value") + + # Try to set a new header (should be added) + result = set_heading_if_not_set(result, "X-New-Header", "new-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + # Original headers should still exist + self.assertEqual(result["headers"]["X-Another-Header"], "another-value") diff --git a/test/sync/open_fga_api_test.py b/test/sync/open_fga_api_test.py index 22f1261..3fcb73a 100644 --- a/test/sync/open_fga_api_test.py +++ b/test/sync/open_fga_api_test.py @@ -1916,6 +1916,121 @@ def test_check_custom_header(self, mock_request): _request_timeout=None, ) + @patch.object(rest.RESTClientObject, "request") + def test_check_custom_header_override_default_header(self, mock_request): + """Test case for per-request custom header overriding default header + + Per-request custom headers should override default headers with the same name + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Custom-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header that should override the default + api_response = api_instance.check( + body=body, + _headers={"X-Custom-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the per-request header value, not the default + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Custom-Header": "per-request-value", # Should be the per-request value + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + + @patch.object(rest.RESTClientObject, "request") + def test_check_per_request_header_and_default_header_coexist(self, mock_request): + """Test case for per-request custom header and default header coexisting + + Per-request custom headers should be merged with default headers + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Default-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header (different from default) + api_response = api_instance.check( + body=body, + _headers={"X-Per-Request-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure both headers are present in the request + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Default-Header": "default-value", # Default header preserved + "X-Per-Request-Header": "per-request-value", # Per-request header added + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + if __name__ == "__main__": unittest.main() diff --git a/uv.lock b/uv.lock index 4fa7063..19c5cf8 100644 --- a/uv.lock +++ b/uv.lock @@ -148,87 +148,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025, upload-time = "2025-08-29T15:32:57.169Z" }, - { url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419, upload-time = "2025-08-29T15:32:59.908Z" }, - { url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180, upload-time = "2025-08-29T15:33:01.608Z" }, - { url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992, upload-time = "2025-08-29T15:33:03.239Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851, upload-time = "2025-08-29T15:33:04.603Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891, upload-time = "2025-08-29T15:33:06.176Z" }, - { url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909, upload-time = "2025-08-29T15:33:07.74Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786, upload-time = "2025-08-29T15:33:08.965Z" }, - { url = "https://files.pythonhosted.org/packages/d0/4c/37ed872374a21813e0d3215256180c9a382c3f5ced6f2e5da0102fc2fd3e/coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", size = 219521, upload-time = "2025-08-29T15:33:10.599Z" }, - { url = "https://files.pythonhosted.org/packages/8e/36/9311352fdc551dec5b973b61f4e453227ce482985a9368305880af4f85dd/coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", size = 220417, upload-time = "2025-08-29T15:33:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, - { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, - { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, - { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, - { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, - { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, - { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, - { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, - { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, - { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, - { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, - { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, - { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, - { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, - { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" }, - { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" }, - { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" }, - { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" }, - { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" }, - { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" }, - { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" }, - { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" }, - { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" }, - { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" }, - { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, ] [package.optional-dependencies] @@ -497,7 +511,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.18.1" +version = "1.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, @@ -505,39 +519,39 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/a3/931e09fc02d7ba96da65266884da4e4a8806adcdb8a57faaacc6edf1d538/mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9", size = 3448447, upload-time = "2025-09-11T23:00:47.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/06/29ea5a34c23938ae93bc0040eb2900eb3f0f2ef4448cc59af37ab3ddae73/mypy-1.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2761b6ae22a2b7d8e8607fb9b81ae90bc2e95ec033fd18fa35e807af6c657763", size = 12811535, upload-time = "2025-09-11T22:58:55.399Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/04c38cb04fa9f1dc224b3e9634021a92c47b1569f1c87dfe6e63168883bb/mypy-1.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b10e3ea7f2eec23b4929a3fabf84505da21034a4f4b9613cda81217e92b74f3", size = 11897559, upload-time = "2025-09-11T22:59:48.041Z" }, - { url = "https://files.pythonhosted.org/packages/46/bf/4c535bd45ea86cebbc1a3b6a781d442f53a4883f322ebd2d442db6444d0b/mypy-1.18.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261fbfced030228bc0f724d5d92f9ae69f46373bdfd0e04a533852677a11dbea", size = 12507430, upload-time = "2025-09-11T22:59:30.415Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e1/cbefb16f2be078d09e28e0b9844e981afb41f6ffc85beb68b86c6976e641/mypy-1.18.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dc6b34a1c6875e6286e27d836a35c0d04e8316beac4482d42cfea7ed2527df8", size = 13243717, upload-time = "2025-09-11T22:59:11.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/e8/3e963da63176f16ca9caea7fa48f1bc8766de317cd961528c0391565fd47/mypy-1.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1cabb353194d2942522546501c0ff75c4043bf3b63069cb43274491b44b773c9", size = 13492052, upload-time = "2025-09-11T23:00:09.29Z" }, - { url = "https://files.pythonhosted.org/packages/4b/09/d5d70c252a3b5b7530662d145437bd1de15f39fa0b48a27ee4e57d254aa1/mypy-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:738b171690c8e47c93569635ee8ec633d2cdb06062f510b853b5f233020569a9", size = 9765846, upload-time = "2025-09-11T22:58:26.198Z" }, - { url = "https://files.pythonhosted.org/packages/32/28/47709d5d9e7068b26c0d5189c8137c8783e81065ad1102b505214a08b548/mypy-1.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c903857b3e28fc5489e54042684a9509039ea0aedb2a619469438b544ae1961", size = 12734635, upload-time = "2025-09-11T23:00:24.983Z" }, - { url = "https://files.pythonhosted.org/packages/7c/12/ee5c243e52497d0e59316854041cf3b3130131b92266d0764aca4dec3c00/mypy-1.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a0c8392c19934c2b6c65566d3a6abdc6b51d5da7f5d04e43f0eb627d6eeee65", size = 11817287, upload-time = "2025-09-11T22:59:07.38Z" }, - { url = "https://files.pythonhosted.org/packages/48/bd/2aeb950151005fe708ab59725afed7c4aeeb96daf844f86a05d4b8ac34f8/mypy-1.18.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f85eb7efa2ec73ef63fc23b8af89c2fe5bf2a4ad985ed2d3ff28c1bb3c317c92", size = 12430464, upload-time = "2025-09-11T22:58:48.084Z" }, - { url = "https://files.pythonhosted.org/packages/71/e8/7a20407aafb488acb5734ad7fb5e8c2ef78d292ca2674335350fa8ebef67/mypy-1.18.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82ace21edf7ba8af31c3308a61dc72df30500f4dbb26f99ac36b4b80809d7e94", size = 13164555, upload-time = "2025-09-11T23:00:13.803Z" }, - { url = "https://files.pythonhosted.org/packages/e8/c9/5f39065252e033b60f397096f538fb57c1d9fd70a7a490f314df20dd9d64/mypy-1.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a2dfd53dfe632f1ef5d161150a4b1f2d0786746ae02950eb3ac108964ee2975a", size = 13359222, upload-time = "2025-09-11T23:00:33.469Z" }, - { url = "https://files.pythonhosted.org/packages/85/b6/d54111ef3c1e55992cd2ec9b8b6ce9c72a407423e93132cae209f7e7ba60/mypy-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:320f0ad4205eefcb0e1a72428dde0ad10be73da9f92e793c36228e8ebf7298c0", size = 9760441, upload-time = "2025-09-11T23:00:44.826Z" }, - { url = "https://files.pythonhosted.org/packages/e7/14/1c3f54d606cb88a55d1567153ef3a8bc7b74702f2ff5eb64d0994f9e49cb/mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9", size = 12911082, upload-time = "2025-09-11T23:00:41.465Z" }, - { url = "https://files.pythonhosted.org/packages/90/83/235606c8b6d50a8eba99773add907ce1d41c068edb523f81eb0d01603a83/mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e", size = 11919107, upload-time = "2025-09-11T22:58:40.903Z" }, - { url = "https://files.pythonhosted.org/packages/ca/25/4e2ce00f8d15b99d0c68a2536ad63e9eac033f723439ef80290ec32c1ff5/mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2", size = 12472551, upload-time = "2025-09-11T22:58:37.272Z" }, - { url = "https://files.pythonhosted.org/packages/32/bb/92642a9350fc339dd9dcefcf6862d171b52294af107d521dce075f32f298/mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d", size = 13340554, upload-time = "2025-09-11T22:59:38.756Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ee/38d01db91c198fb6350025d28f9719ecf3c8f2c55a0094bfbf3ef478cc9a/mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5", size = 13530933, upload-time = "2025-09-11T22:59:20.228Z" }, - { url = "https://files.pythonhosted.org/packages/da/8d/6d991ae631f80d58edbf9d7066e3f2a96e479dca955d9a968cd6e90850a3/mypy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:2657654d82fcd2a87e02a33e0d23001789a554059bbf34702d623dafe353eabf", size = 9828426, upload-time = "2025-09-11T23:00:21.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ec/ef4a7260e1460a3071628a9277a7579e7da1b071bc134ebe909323f2fbc7/mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f", size = 12918671, upload-time = "2025-09-11T22:58:29.814Z" }, - { url = "https://files.pythonhosted.org/packages/a1/82/0ea6c3953f16223f0b8eda40c1aeac6bd266d15f4902556ae6e91f6fca4c/mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce", size = 11913023, upload-time = "2025-09-11T23:00:29.049Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ef/5e2057e692c2690fc27b3ed0a4dbde4388330c32e2576a23f0302bc8358d/mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e", size = 12473355, upload-time = "2025-09-11T23:00:04.544Z" }, - { url = "https://files.pythonhosted.org/packages/98/43/b7e429fc4be10e390a167b0cd1810d41cb4e4add4ae50bab96faff695a3b/mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71", size = 13346944, upload-time = "2025-09-11T22:58:23.024Z" }, - { url = "https://files.pythonhosted.org/packages/89/4e/899dba0bfe36bbd5b7c52e597de4cf47b5053d337b6d201a30e3798e77a6/mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746", size = 13512574, upload-time = "2025-09-11T22:59:52.152Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f8/7661021a5b0e501b76440454d786b0f01bb05d5c4b125fcbda02023d0250/mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d", size = 9837684, upload-time = "2025-09-11T22:58:44.454Z" }, - { url = "https://files.pythonhosted.org/packages/bf/87/7b173981466219eccc64c107cf8e5ab9eb39cc304b4c07df8e7881533e4f/mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61", size = 12900265, upload-time = "2025-09-11T22:59:03.4Z" }, - { url = "https://files.pythonhosted.org/packages/ae/cc/b10e65bae75b18a5ac8f81b1e8e5867677e418f0dd2c83b8e2de9ba96ebd/mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5", size = 11942890, upload-time = "2025-09-11T23:00:00.607Z" }, - { url = "https://files.pythonhosted.org/packages/39/d4/aeefa07c44d09f4c2102e525e2031bc066d12e5351f66b8a83719671004d/mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8", size = 12472291, upload-time = "2025-09-11T22:59:43.425Z" }, - { url = "https://files.pythonhosted.org/packages/c6/07/711e78668ff8e365f8c19735594ea95938bff3639a4c46a905e3ed8ff2d6/mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d", size = 13318610, upload-time = "2025-09-11T23:00:17.604Z" }, - { url = "https://files.pythonhosted.org/packages/ca/85/df3b2d39339c31d360ce299b418c55e8194ef3205284739b64962f6074e7/mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d", size = 13513697, upload-time = "2025-09-11T22:58:59.534Z" }, - { url = "https://files.pythonhosted.org/packages/b1/df/462866163c99ea73bb28f0eb4d415c087e30de5d36ee0f5429d42e28689b/mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce", size = 9985739, upload-time = "2025-09-11T22:58:51.644Z" }, - { url = "https://files.pythonhosted.org/packages/e0/1d/4b97d3089b48ef3d904c9ca69fab044475bd03245d878f5f0b3ea1daf7ce/mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e", size = 2352212, upload-time = "2025-09-11T22:59:26.576Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, ] [[package]] @@ -786,28 +800,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" }, - { url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" }, - { url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" }, - { url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" }, - { url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" }, - { url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" }, - { url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" }, - { url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" }, - { url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" }, - { url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" }, +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/8e/f9f9ca747fea8e3ac954e3690d4698c9737c23b51731d02df999c150b1c9/ruff-0.13.3.tar.gz", hash = "sha256:5b0ba0db740eefdfbcce4299f49e9eaefc643d4d007749d77d047c2bab19908e", size = 5438533, upload-time = "2025-10-02T19:29:31.582Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/33/8f7163553481466a92656d35dea9331095122bb84cf98210bef597dd2ecd/ruff-0.13.3-py3-none-linux_armv6l.whl", hash = "sha256:311860a4c5e19189c89d035638f500c1e191d283d0cc2f1600c8c80d6dcd430c", size = 12484040, upload-time = "2025-10-02T19:28:49.199Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b5/4a21a4922e5dd6845e91896b0d9ef493574cbe061ef7d00a73c61db531af/ruff-0.13.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2bdad6512fb666b40fcadb65e33add2b040fc18a24997d2e47fee7d66f7fcae2", size = 13122975, upload-time = "2025-10-02T19:28:52.446Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/15649af836d88c9f154e5be87e64ae7d2b1baa5a3ef317cb0c8fafcd882d/ruff-0.13.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fc6fa4637284708d6ed4e5e970d52fc3b76a557d7b4e85a53013d9d201d93286", size = 12346621, upload-time = "2025-10-02T19:28:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/bcbccb8141305f9a6d3f72549dd82d1134299177cc7eaf832599700f95a7/ruff-0.13.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c9e6469864f94a98f412f20ea143d547e4c652f45e44f369d7b74ee78185838", size = 12574408, upload-time = "2025-10-02T19:28:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/ce/19/0f3681c941cdcfa2d110ce4515624c07a964dc315d3100d889fcad3bfc9e/ruff-0.13.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5bf62b705f319476c78891e0e97e965b21db468b3c999086de8ffb0d40fd2822", size = 12285330, upload-time = "2025-10-02T19:28:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/f8/387976bf00d126b907bbd7725219257feea58650e6b055b29b224d8cb731/ruff-0.13.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cc1abed87ce40cb07ee0667ce99dbc766c9f519eabfd948ed87295d8737c60", size = 13980815, upload-time = "2025-10-02T19:29:01.577Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a6/7c8ec09d62d5a406e2b17d159e4817b63c945a8b9188a771193b7e1cc0b5/ruff-0.13.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4fb75e7c402d504f7a9a259e0442b96403fa4a7310ffe3588d11d7e170d2b1e3", size = 14987733, upload-time = "2025-10-02T19:29:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/97/e5/f403a60a12258e0fd0c2195341cfa170726f254c788673495d86ab5a9a9d/ruff-0.13.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b951f9d9afb39330b2bdd2dd144ce1c1335881c277837ac1b50bfd99985ed3", size = 14439848, upload-time = "2025-10-02T19:29:06.684Z" }, + { url = "https://files.pythonhosted.org/packages/39/49/3de381343e89364c2334c9f3268b0349dc734fc18b2d99a302d0935c8345/ruff-0.13.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6052f8088728898e0a449f0dde8fafc7ed47e4d878168b211977e3e7e854f662", size = 13421890, upload-time = "2025-10-02T19:29:08.767Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b5/c0feca27d45ae74185a6bacc399f5d8920ab82df2d732a17213fb86a2c4c/ruff-0.13.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc742c50f4ba72ce2a3be362bd359aef7d0d302bf7637a6f942eaa763bd292af", size = 13444870, upload-time = "2025-10-02T19:29:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/50/a1/b655298a1f3fda4fdc7340c3f671a4b260b009068fbeb3e4e151e9e3e1bf/ruff-0.13.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8e5640349493b378431637019366bbd73c927e515c9c1babfea3e932f5e68e1d", size = 13691599, upload-time = "2025-10-02T19:29:13.353Z" }, + { url = "https://files.pythonhosted.org/packages/32/b0/a8705065b2dafae007bcae21354e6e2e832e03eb077bb6c8e523c2becb92/ruff-0.13.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b139f638a80eae7073c691a5dd8d581e0ba319540be97c343d60fb12949c8d0", size = 12421893, upload-time = "2025-10-02T19:29:15.668Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/cbe7082588d025cddbb2f23e6dfef08b1a2ef6d6f8328584ad3015b5cebd/ruff-0.13.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6b547def0a40054825de7cfa341039ebdfa51f3d4bfa6a0772940ed351d2746c", size = 12267220, upload-time = "2025-10-02T19:29:17.583Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/4086f9c43f85e0755996d09bdcb334b6fee9b1eabdf34e7d8b877fadf964/ruff-0.13.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9cc48a3564423915c93573f1981d57d101e617839bef38504f85f3677b3a0a3e", size = 13177818, upload-time = "2025-10-02T19:29:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/7b5db7e39947d9dc1c5f9f17b838ad6e680527d45288eeb568e860467010/ruff-0.13.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1a993b17ec03719c502881cb2d5f91771e8742f2ca6de740034433a97c561989", size = 13618715, upload-time = "2025-10-02T19:29:22.527Z" }, + { url = "https://files.pythonhosted.org/packages/28/d3/bb25ee567ce2f61ac52430cf99f446b0e6d49bdfa4188699ad005fdd16aa/ruff-0.13.3-py3-none-win32.whl", hash = "sha256:f14e0d1fe6460f07814d03c6e32e815bff411505178a1f539a38f6097d3e8ee3", size = 12334488, upload-time = "2025-10-02T19:29:24.782Z" }, + { url = "https://files.pythonhosted.org/packages/cf/49/12f5955818a1139eed288753479ba9d996f6ea0b101784bb1fe6977ec128/ruff-0.13.3-py3-none-win_amd64.whl", hash = "sha256:621e2e5812b691d4f244638d693e640f188bacbb9bc793ddd46837cea0503dd2", size = 13455262, upload-time = "2025-10-02T19:29:26.882Z" }, + { url = "https://files.pythonhosted.org/packages/fe/72/7b83242b26627a00e3af70d0394d68f8f02750d642567af12983031777fc/ruff-0.13.3-py3-none-win_arm64.whl", hash = "sha256:9e9e9d699841eaf4c2c798fa783df2fabc680b72059a02ca0ed81c460bc58330", size = 12538484, upload-time = "2025-10-02T19:29:28.951Z" }, ] [[package]] From 27838161dbc96c0c57c323ec2a289d34893e72d7 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Fri, 3 Oct 2025 16:02:17 -0500 Subject: [PATCH 2/3] fix: custom header precedence --- openfga_sdk/api_client.py | 3 +- openfga_sdk/client/client.py | 2 +- openfga_sdk/sync/api_client.py | 3 +- openfga_sdk/sync/client/client.py | 2 +- test/api/open_fga_api_test.py | 117 +++++++++ test/client/client_test.py | 370 ++++++++++++++++++++++++++++- test/sync/client/client_test.py | 379 +++++++++++++++++++++++++++++- test/sync/open_fga_api_test.py | 115 +++++++++ 8 files changed, 983 insertions(+), 8 deletions(-) diff --git a/openfga_sdk/api_client.py b/openfga_sdk/api_client.py index 5a54500..d428caa 100644 --- a/openfga_sdk/api_client.py +++ b/openfga_sdk/api_client.py @@ -178,8 +178,7 @@ async def __call_api( start = float(time.time()) # header parameters - header_params = header_params or {} - header_params.update(self.default_headers) + header_params = {**self.default_headers, **(header_params or {})} if self.cookie: header_params["Cookie"] = self.cookie if header_params: diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 2e0459d..749a04a 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -106,7 +106,7 @@ def set_heading_if_not_set( _options["headers"] = {} if type(_options["headers"]) is dict: - if type(_options["headers"].get(name)) not in [int, str]: + if _options["headers"].get(name) is None: _options["headers"][name] = value return _options diff --git a/openfga_sdk/sync/api_client.py b/openfga_sdk/sync/api_client.py index a588092..196e83b 100644 --- a/openfga_sdk/sync/api_client.py +++ b/openfga_sdk/sync/api_client.py @@ -177,8 +177,7 @@ def __call_api( start = float(time.time()) # header parameters - header_params = header_params or {} - header_params.update(self.default_headers) + header_params = {**self.default_headers, **(header_params or {})} if self.cookie: header_params["Cookie"] = self.cookie if header_params: diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 732196c..e900b78 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -107,7 +107,7 @@ def set_heading_if_not_set( _options["headers"] = {} if type(_options["headers"]) is dict: - if type(_options["headers"].get(name)) not in [int, str]: + if _options["headers"].get(name) is None: _options["headers"][name] = value return _options diff --git a/test/api/open_fga_api_test.py b/test/api/open_fga_api_test.py index 42a43cf..62f1556 100644 --- a/test/api/open_fga_api_test.py +++ b/test/api/open_fga_api_test.py @@ -2048,6 +2048,123 @@ async def test_read_with_type_only_object(self, mock_request): ) await api_client.close() + @patch.object(rest.RESTClientObject, "request") + async def test_check_custom_header_override_default_header(self, mock_request): + """Test case for per-request custom header overriding default header + + Per-request custom headers should override default headers with the same name + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with openfga_sdk.ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Custom-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header that should override the default + api_response = await api_instance.check( + body=body, + _headers={"X-Custom-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the per-request header value, not the default + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Custom-Header": "per-request-value", # Should be the per-request value + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + + @patch.object(rest.RESTClientObject, "request") + async def test_check_per_request_header_and_default_header_coexist( + self, mock_request + ): + """Test case for per-request custom header and default header coexisting + + Per-request custom headers should be merged with default headers + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with openfga_sdk.ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Default-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header (different from default) + api_response = await api_instance.check( + body=body, + _headers={"X-Per-Request-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure both headers are present in the request + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Default-Header": "default-value", # Default header preserved + "X-Per-Request-Header": "per-request-value", # Per-request header added + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + if __name__ == "__main__": unittest.main() diff --git a/test/client/client_test.py b/test/client/client_test.py index a7117c9..3e0f4e5 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -21,7 +21,7 @@ from openfga_sdk import rest from openfga_sdk.client import ClientConfiguration -from openfga_sdk.client.client import OpenFgaClient +from openfga_sdk.client.client import OpenFgaClient, set_heading_if_not_set from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ClientBatchCheckItem from openfga_sdk.client.models.batch_check_request import ClientBatchCheckRequest @@ -3273,3 +3273,371 @@ def test_configuration_authorization_model_id_invalid(self): authorization_model_id="abcd", ) self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_set_heading_if_not_set_when_none_provided(self): + """Should set header when no options provided""" + result = set_heading_if_not_set(None, "X-Test-Header", "default-value") + + self.assertIsNotNone(result) + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_empty_options_provided(self): + """Should set header when empty options dict provided""" + result = set_heading_if_not_set({}, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_no_headers_in_options(self): + """Should set header when options dict has no headers key""" + options = {"page_size": 10} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + self.assertEqual(result["page_size"], 10) + + def test_set_heading_if_not_set_when_headers_empty(self): + """Should set header when headers dict is empty""" + options = {"headers": {}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_override_existing_custom_header(self): + """Should NOT override when custom header already exists - this is the critical test for the bug fix""" + options = {"headers": {"X-Test-Header": "custom-value"}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Custom header should be preserved, NOT overridden by default + self.assertEqual(result["headers"]["X-Test-Header"], "custom-value") + + def test_set_heading_if_not_set_preserves_other_headers_when_setting_new_header( + self, + ): + """Should preserve existing headers when setting a new one""" + options = {"headers": {"X-Existing-Header": "existing-value"}} + result = set_heading_if_not_set(options, "X-New-Header", "new-value") + + self.assertEqual(result["headers"]["X-Existing-Header"], "existing-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + def test_set_heading_if_not_set_handles_integer_header_values(self): + """Should not override existing integer header values""" + options = {"headers": {"X-Retry-Count": 5}} + result = set_heading_if_not_set(options, "X-Retry-Count", 1) + + # Existing integer value should be preserved + self.assertEqual(result["headers"]["X-Retry-Count"], 5) + + def test_set_heading_if_not_set_handles_non_dict_headers_value(self): + """Should convert non-dict headers value to dict""" + options = {"headers": "invalid"} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIsInstance(result["headers"], dict) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_mutate_when_header_exists(self): + """Should return same dict when header already exists""" + options = {"headers": {"X-Test-Header": "custom-value"}} + original_value = options["headers"]["X-Test-Header"] + + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Should return the same modified dict + self.assertIs(result, options) + # Value should not have changed + self.assertEqual(result["headers"]["X-Test-Header"], original_value) + + def test_set_heading_if_not_set_multiple_headers_with_mixed_states(self): + """Should handle multiple headers, some existing and some new""" + options = { + "headers": { + "X-Custom-Header": "custom-value", + "X-Another-Header": "another-value", + } + } + + # Try to set a custom header (should not override) + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "custom-value") + + # Try to set a new header (should be added) + result = set_heading_if_not_set(result, "X-New-Header", "new-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + # Original headers should still exist + self.assertEqual(result["headers"]["X-Another-Header"], "another-value") + + def test_set_heading_if_not_set_with_empty_string_value(self): + """Test that empty string values in custom headers are preserved and not overridden.""" + options = {"headers": {"X-Custom-Header": ""}} + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "") + + def test_set_heading_if_not_set_with_unicode_characters(self): + """Test that headers with Unicode characters are handled correctly.""" + options = {"headers": {"X-Custom-Header": "日本語"}} + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "日本語") + + # Test setting a new header with Unicode + result = set_heading_if_not_set({}, "X-Unicode-Header", "🔒") + self.assertEqual(result["headers"]["X-Unicode-Header"], "🔒") + + def test_set_heading_if_not_set_with_special_characters(self): + """Test that headers with special characters are handled correctly.""" + options = {"headers": {"X-Custom-Header": "value-with-special!@#$%^&*()_+"}} + result = set_heading_if_not_set(options, "X-Custom-Header", "default") + self.assertEqual( + result["headers"]["X-Custom-Header"], "value-with-special!@#$%^&*()_+" + ) + + def test_set_heading_if_not_set_case_sensitivity(self): + """Test that header names are treated as case-sensitive by the helper function.""" + options = {"headers": {"x-custom-header": "lowercase"}} + result = set_heading_if_not_set(options, "X-Custom-Header", "uppercase") + self.assertEqual(result["headers"]["X-Custom-Header"], "uppercase") + self.assertEqual(result["headers"]["x-custom-header"], "lowercase") + + @patch.object(rest.RESTClientObject, "request") + async def test_check_with_custom_headers_override_defaults(self, mock_request): + """Test that custom headers in options override default headers for check API.""" + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + async with OpenFgaClient(configuration) as api_client: + custom_options = {"headers": {"X-Custom-Request-Id": "custom-request-123"}} + api_response = await api_client.check(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-Custom-Request-Id"), "custom-request-123") + self.assertIsInstance(api_response, CheckResponse) + + @patch.object(rest.RESTClientObject, "request") + async def test_write_with_custom_headers(self, mock_request): + """Test that custom headers work correctly with write API.""" + response_body = '{"writes": [], "deletes": []}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:budget", + relation="reader", + user="user:anne", + ) + ] + ) + + configuration = self.configuration + configuration.store_id = store_id + + async with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Trace-Id": "trace-xyz-789"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + await api_client.write(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-Trace-Id"), "trace-xyz-789") + + @patch.object(rest.RESTClientObject, "request") + async def test_expand_with_custom_headers(self, mock_request): + """Test that custom headers are passed correctly in expand API calls.""" + response_body = '{"tree": {"root": {"name": "document:1#viewer", "leaf": {"users": {"users": ["user:anne"]}}}}}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientExpandRequest( + object="document:1", + relation="viewer", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + async with OpenFgaClient(configuration) as api_client: + custom_options = {"headers": {"X-Expand-Id": "expand-456"}} + api_response = await api_client.expand(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-Expand-Id"), "expand-456") + self.assertIsInstance(api_response, ExpandResponse) + + @patch.object(rest.RESTClientObject, "request") + async def test_list_objects_with_custom_headers(self, mock_request): + """Test that custom headers are passed correctly in list_objects API calls.""" + response_body = '{"objects": ["document:1", "document:2"]}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientListObjectsRequest( + type="document", + relation="viewer", + user="user:anne", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + async with OpenFgaClient(configuration) as api_client: + custom_options = {"headers": {"X-List-Objects-Id": "list-obj-999"}} + api_response = await api_client.list_objects( + body=body, options=custom_options + ) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-List-Objects-Id"), "list-obj-999") + self.assertIsInstance(api_response, ListObjectsResponse) + + @patch.object(rest.RESTClientObject, "request") + async def test_list_users_with_custom_headers(self, mock_request): + """Test that custom headers are passed correctly in list_users API calls.""" + response_body = '{"users": [{"object": {"type": "user", "id": "anne"}}, {"object": {"type": "user", "id": "bob"}}]}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientListUsersRequest( + object=FgaObject(type="document", id="1"), + relation="viewer", + user_filters=[{"type": "user"}], + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + async with OpenFgaClient(configuration) as api_client: + custom_options = {"headers": {"X-List-Users-Id": "list-users-777"}} + api_response = await api_client.list_users( + body=body, options=custom_options + ) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-List-Users-Id"), "list-users-777") + self.assertIsInstance(api_response, ListUsersResponse) + + @patch.object(rest.RESTClientObject, "request") + async def test_multiple_api_calls_with_different_custom_headers(self, mock_request): + """Test that different custom headers can be used for different API calls.""" + + def mock_side_effect(*args, **kwargs): + path = args[1] + if "check" in path: + return mock_response('{"allowed": true, "resolution": "1234"}', 200) + elif "expand" in path: + return mock_response( + '{"tree": {"root": {"name": "document:1#viewer", "leaf": {"users": {"users": ["user:anne"]}}}}}', + 200, + ) + return mock_response("{}", 200) + + mock_request.side_effect = mock_side_effect + + check_body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + + expand_body = ClientExpandRequest( + object="document:1", + relation="viewer", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + async with OpenFgaClient(configuration) as api_client: + # First call with custom header 1 + check_options = {"headers": {"X-Request-Id": "check-request-111"}} + check_response = await api_client.check( + body=check_body, options=check_options + ) + + # Second call with custom header 2 + expand_options = {"headers": {"X-Request-Id": "expand-request-222"}} + expand_response = await api_client.expand( + body=expand_body, options=expand_options + ) + + # Verify first call had correct header + first_call_args = mock_request.call_args_list[0] + first_headers = first_call_args[1]["headers"] + self.assertEqual(first_headers.get("X-Request-Id"), "check-request-111") + + # Verify second call had correct header + second_call_args = mock_request.call_args_list[1] + second_headers = second_call_args[1]["headers"] + self.assertEqual(second_headers.get("X-Request-Id"), "expand-request-222") + + self.assertIsInstance(check_response, CheckResponse) + self.assertIsInstance(expand_response, ExpandResponse) + + @patch.object(rest.RESTClientObject, "request") + async def test_client_batch_check_with_custom_headers(self, mock_request): + """Test that custom headers work correctly in batch check operations.""" + + def mock_side_effect(*args, **kwargs): + body = kwargs.get("body", {}) + user = body.get("tuple_key", {}).get("user", "") + if user == "user:anne": + return mock_response('{"allowed": true, "resolution": "1234"}', 200) + elif user == "user:bob": + return mock_response('{"allowed": false, "resolution": "5678"}', 200) + return mock_response('{"allowed": false, "resolution": "0000"}', 200) + + mock_request.side_effect = mock_side_effect + + body = [ + ClientCheckRequest( + object="document:budget", + relation="reader", + user="user:anne", + ), + ClientCheckRequest( + object="document:roadmap", + relation="writer", + user="user:bob", + ), + ] + + configuration = self.configuration + configuration.store_id = store_id + + async with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Batch-Id": "batch-xyz-123"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + api_response = await api_client.client_batch_check( + body=body, options=custom_options + ) + + # Verify all calls had the custom header + for call_args in mock_request.call_args_list: + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-Batch-Id"), "batch-xyz-123") + + self.assertEqual(len(api_response), 2) + self.assertTrue(api_response[0].allowed) + self.assertFalse(api_response[1].allowed) diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index 6e4b790..c3ee918 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -85,7 +85,7 @@ WriteAuthorizationModelResponse, ) from openfga_sdk.sync import rest -from openfga_sdk.sync.client.client import OpenFgaClient +from openfga_sdk.sync.client.client import OpenFgaClient, set_heading_if_not_set store_id = "01YCP46JKYM8FJCQ37NMBYHE5X" @@ -3275,3 +3275,380 @@ def test_configuration_authorization_model_id_invalid(self): authorization_model_id="abcd", ) self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_set_heading_if_not_set_when_none_provided(self): + """Should set header when no options provided""" + result = set_heading_if_not_set(None, "X-Test-Header", "default-value") + + self.assertIsNotNone(result) + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_empty_options_provided(self): + """Should set header when empty options dict provided""" + result = set_heading_if_not_set({}, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_when_no_headers_in_options(self): + """Should set header when options dict has no headers key""" + options = {"page_size": 10} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIn("headers", result) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + self.assertEqual(result["page_size"], 10) + + def test_set_heading_if_not_set_when_headers_empty(self): + """Should set header when headers dict is empty""" + options = {"headers": {}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_override_existing_custom_header(self): + """Should NOT override when custom header already exists - this is the critical test for the bug fix""" + options = {"headers": {"X-Test-Header": "custom-value"}} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Custom header should be preserved, NOT overridden by default + self.assertEqual(result["headers"]["X-Test-Header"], "custom-value") + + def test_set_heading_if_not_set_preserves_other_headers_when_setting_new_header( + self, + ): + """Should preserve existing headers when setting a new one""" + options = {"headers": {"X-Existing-Header": "existing-value"}} + result = set_heading_if_not_set(options, "X-New-Header", "new-value") + + self.assertEqual(result["headers"]["X-Existing-Header"], "existing-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + def test_set_heading_if_not_set_handles_integer_header_values(self): + """Should not override existing integer header values""" + options = {"headers": {"X-Retry-Count": 5}} + result = set_heading_if_not_set(options, "X-Retry-Count", 1) + + # Existing integer value should be preserved + self.assertEqual(result["headers"]["X-Retry-Count"], 5) + + def test_set_heading_if_not_set_handles_non_dict_headers_value(self): + """Should convert non-dict headers value to dict""" + options = {"headers": "invalid"} + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + self.assertIsInstance(result["headers"], dict) + self.assertEqual(result["headers"]["X-Test-Header"], "default-value") + + def test_set_heading_if_not_set_does_not_mutate_when_header_exists(self): + """Should return same dict when header already exists""" + options = {"headers": {"X-Test-Header": "custom-value"}} + original_value = options["headers"]["X-Test-Header"] + + result = set_heading_if_not_set(options, "X-Test-Header", "default-value") + + # Should return the same modified dict + self.assertIs(result, options) + # Value should not have changed + self.assertEqual(result["headers"]["X-Test-Header"], original_value) + + def test_set_heading_if_not_set_multiple_headers_with_mixed_states(self): + """Should handle multiple headers, some existing and some new""" + options = { + "headers": { + "X-Custom-Header": "custom-value", + "X-Another-Header": "another-value", + } + } + + # Try to set a custom header (should not override) + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "custom-value") + + # Try to set a new header (should be added) + result = set_heading_if_not_set(result, "X-New-Header", "new-value") + self.assertEqual(result["headers"]["X-New-Header"], "new-value") + + # Original headers should still exist + self.assertEqual(result["headers"]["X-Another-Header"], "another-value") + + def test_set_heading_if_not_set_with_empty_string_value(self): + """Test that empty string values in custom headers are preserved and not overridden.""" + options = {"headers": {"X-Custom-Header": ""}} + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + # Empty string should be preserved, not overridden + self.assertEqual(result["headers"]["X-Custom-Header"], "") + + def test_set_heading_if_not_set_with_unicode_characters(self): + """Test that headers with Unicode characters are handled correctly.""" + options = {"headers": {"X-Custom-Header": "日本語"}} + result = set_heading_if_not_set(options, "X-Custom-Header", "default-value") + self.assertEqual(result["headers"]["X-Custom-Header"], "日本語") + + # Test setting a new header with Unicode + result = set_heading_if_not_set({}, "X-Unicode-Header", "🔒") + self.assertEqual(result["headers"]["X-Unicode-Header"], "🔒") + + def test_set_heading_if_not_set_with_special_characters(self): + """Test that headers with special characters are handled correctly.""" + options = {"headers": {"X-Custom-Header": "value-with-special!@#$%^&*()_+"}} + result = set_heading_if_not_set(options, "X-Custom-Header", "default") + self.assertEqual( + result["headers"]["X-Custom-Header"], "value-with-special!@#$%^&*()_+" + ) + + def test_set_heading_if_not_set_case_sensitivity(self): + """Test that header names are treated as case-sensitive by the helper function.""" + options = {"headers": {"x-custom-header": "lowercase"}} + # Different case - should add new header + result = set_heading_if_not_set(options, "X-Custom-Header", "uppercase") + self.assertEqual(result["headers"]["X-Custom-Header"], "uppercase") + self.assertEqual(result["headers"]["x-custom-header"], "lowercase") + + @patch.object(rest.RESTClientObject, "request") + def test_check_with_custom_headers_override_defaults(self, mock_request): + """Test that custom headers in options override default headers for check API.""" + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + with OpenFgaClient(configuration) as api_client: + # Add custom header that should override any defaults + custom_options = {"headers": {"X-Custom-Request-Id": "custom-request-123"}} + api_response = api_client.check(body=body, options=custom_options) + + # Verify the API was called and extract the headers + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + # Verify custom header is present + self.assertEqual(headers.get("X-Custom-Request-Id"), "custom-request-123") + self.assertIsInstance(api_response, CheckResponse) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_write_with_custom_headers(self, mock_request): + """Test that custom headers work correctly with write API.""" + response_body = '{"writes": [], "deletes": []}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:budget", + relation="reader", + user="user:anne", + ) + ] + ) + + configuration = self.configuration + configuration.store_id = store_id + + with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Trace-Id": "trace-xyz-789"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + api_client.write(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + self.assertEqual(headers.get("X-Trace-Id"), "trace-xyz-789") + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_expand_with_custom_headers(self, mock_request): + """Test that custom headers work correctly with expand API.""" + response_body = '{"tree": {"root": {"name": "document:budget#viewer", "leaf": {"users": {"users": ["user:anne"]}}}}}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientExpandRequest( + object="document:budget", + relation="viewer", + ) + + configuration = self.configuration + configuration.store_id = store_id + + with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Correlation-Id": "corr-abc-123"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + api_response = api_client.expand(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + self.assertEqual(headers.get("X-Correlation-Id"), "corr-abc-123") + self.assertIsInstance(api_response, ExpandResponse) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_list_objects_with_custom_headers(self, mock_request): + """Test that custom headers work correctly with list_objects API.""" + response_body = '{"objects": ["document:budget", "document:roadmap"]}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientListObjectsRequest( + type="document", + relation="viewer", + user="user:anne", + ) + + configuration = self.configuration + configuration.store_id = store_id + + with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Session-Id": "session-456"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + api_response = api_client.list_objects(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + self.assertEqual(headers.get("X-Session-Id"), "session-456") + self.assertIsInstance(api_response, ListObjectsResponse) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_list_users_with_custom_headers(self, mock_request): + """Test that custom headers work correctly with list_users API.""" + response_body = '{"users": [{"object": {"type": "user", "id": "anne"}}]}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientListUsersRequest( + object=FgaObject(type="document", id="budget"), + relation="viewer", + user_filters=[UserTypeFilter(type="user")], + ) + + configuration = self.configuration + configuration.store_id = store_id + + with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Client-Version": "1.2.3"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + api_response = api_client.list_users(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + self.assertEqual(headers.get("X-Client-Version"), "1.2.3") + self.assertIsInstance(api_response, ListUsersResponse) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_multiple_api_calls_with_different_custom_headers(self, mock_request): + """Test that different custom headers can be used for different API calls.""" + check_response = '{"allowed": true, "resolution": "1234"}' + expand_response = '{"tree": {"root": {"name": "document:budget#viewer", "leaf": {"users": {"users": ["user:anne"]}}}}}' + + mock_request.side_effect = [ + mock_response(check_response, 200), + mock_response(expand_response, 200), + ] + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + with OpenFgaClient(configuration) as api_client: + # First API call with custom header 1 + check_body = ClientCheckRequest( + object="document:budget", + relation="reader", + user="user:anne", + ) + api_client.check( + body=check_body, + options={"headers": {"X-Request-Type": "check-call"}}, + ) + + # Second API call with custom header 2 + expand_body = ClientExpandRequest( + object="document:budget", + relation="viewer", + ) + api_client.expand( + body=expand_body, + options={"headers": {"X-Request-Type": "expand-call"}}, + ) + + # Verify first call had the check header + first_call_headers = mock_request.call_args_list[0][1]["headers"] + self.assertEqual(first_call_headers.get("X-Request-Type"), "check-call") + + # Verify second call had the expand header + second_call_headers = mock_request.call_args_list[1][1]["headers"] + self.assertEqual(second_call_headers.get("X-Request-Type"), "expand-call") + + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_client_batch_check_with_custom_headers(self, mock_request): + """Test that custom headers work correctly in batch check operations.""" + + def mock_side_effect(*args, **kwargs): + body = kwargs.get("body", {}) + user = body.get("tuple_key", {}).get("user", "") + if user == "user:anne": + return mock_response('{"allowed": true, "resolution": "1234"}', 200) + elif user == "user:bob": + return mock_response('{"allowed": false, "resolution": "5678"}', 200) + return mock_response('{"allowed": false, "resolution": "0000"}', 200) + + mock_request.side_effect = mock_side_effect + + body = [ + ClientCheckRequest( + object="document:budget", + relation="reader", + user="user:anne", + ), + ClientCheckRequest( + object="document:roadmap", + relation="writer", + user="user:bob", + ), + ] + + configuration = self.configuration + configuration.store_id = store_id + + with OpenFgaClient(configuration) as api_client: + custom_options = { + "headers": {"X-Batch-Id": "batch-xyz-123"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + } + api_response = api_client.client_batch_check( + body=body, options=custom_options + ) + + # Verify all calls had the custom header + for call_args in mock_request.call_args_list: + headers = call_args[1]["headers"] + self.assertEqual(headers.get("X-Batch-Id"), "batch-xyz-123") + + # Verify responses are correct + self.assertEqual(len(api_response), 2) + self.assertTrue(api_response[0].allowed) + self.assertFalse(api_response[1].allowed) + + api_client.close() diff --git a/test/sync/open_fga_api_test.py b/test/sync/open_fga_api_test.py index 22f1261..3fcb73a 100644 --- a/test/sync/open_fga_api_test.py +++ b/test/sync/open_fga_api_test.py @@ -1916,6 +1916,121 @@ def test_check_custom_header(self, mock_request): _request_timeout=None, ) + @patch.object(rest.RESTClientObject, "request") + def test_check_custom_header_override_default_header(self, mock_request): + """Test case for per-request custom header overriding default header + + Per-request custom headers should override default headers with the same name + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Custom-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header that should override the default + api_response = api_instance.check( + body=body, + _headers={"X-Custom-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the per-request header value, not the default + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Custom-Header": "per-request-value", # Should be the per-request value + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + + @patch.object(rest.RESTClientObject, "request") + def test_check_per_request_header_and_default_header_coexist(self, mock_request): + """Test case for per-request custom header and default header coexisting + + Per-request custom headers should be merged with default headers + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with ApiClient(configuration) as api_client: + # Set a default header + api_client.set_default_header("X-Default-Header", "default-value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + # Make request with per-request custom header (different from default) + api_response = api_instance.check( + body=body, + _headers={"X-Per-Request-Header": "per-request-value"}, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure both headers are present in the request + expected_headers = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.6", + "X-Default-Header": "default-value", # Default header preserved + "X-Per-Request-Header": "per-request-value", # Per-request header added + } + ) + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", + headers=expected_headers, + query_params=[], + post_params=[], + body={ + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + } + }, + _preload_content=ANY, + _request_timeout=None, + ) + if __name__ == "__main__": unittest.main() From 57b44242d499a3dc3dc7f8eeee58c3b2f5150fcb Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Fri, 3 Oct 2025 16:59:57 -0500 Subject: [PATCH 3/3] test: add tests per feedback --- test/client/client_test.py | 54 +++++++++++++++++++++++++++++++++ test/sync/client/client_test.py | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/test/client/client_test.py b/test/client/client_test.py index 3e0f4e5..f25d209 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -3371,6 +3371,30 @@ def test_set_heading_if_not_set_multiple_headers_with_mixed_states(self): # Original headers should still exist self.assertEqual(result["headers"]["X-Another-Header"], "another-value") + def test_set_heading_if_not_set_two_defaults_two_customs_one_override(self): + """Test setting two default headers when two custom headers exist, with one custom overriding one default""" + # Start with two custom headers + options = { + "headers": { + "X-Request-ID": "my-custom-request-id", # This should override the default + "X-Tenant-ID": "tenant-123", # This is custom-only + } + } + + # Try to set two default headers + result = set_heading_if_not_set(options, "X-SDK-Version", "1.0.0") + result = set_heading_if_not_set(result, "X-Request-ID", "default-uuid") + + # Verify all four headers exist with correct values + self.assertEqual(result["headers"]["X-SDK-Version"], "1.0.0") # Default was set + self.assertEqual( + result["headers"]["X-Request-ID"], "my-custom-request-id" + ) # Custom overrode default + self.assertEqual( + result["headers"]["X-Tenant-ID"], "tenant-123" + ) # Custom preserved + self.assertEqual(len(result["headers"]), 3) # Exactly 3 headers + def test_set_heading_if_not_set_with_empty_string_value(self): """Test that empty string values in custom headers are preserved and not overridden.""" options = {"headers": {"X-Custom-Header": ""}} @@ -3402,6 +3426,36 @@ def test_set_heading_if_not_set_case_sensitivity(self): self.assertEqual(result["headers"]["X-Custom-Header"], "uppercase") self.assertEqual(result["headers"]["x-custom-header"], "lowercase") + @patch.object(rest.RESTClientObject, "request") + async def test_content_type_cannot_be_overridden_by_custom_headers( + self, mock_request + ): + """Test that Content-Type header cannot be overridden by custom headers.""" + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + async with OpenFgaClient(configuration) as api_client: + # Try to override Content-Type with a custom value + custom_options = {"headers": {"Content-Type": "text/plain"}} + await api_client.check(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + # Content-Type should be application/json, NOT the custom text/plain + self.assertEqual(headers.get("Content-Type"), "application/json") + self.assertNotEqual(headers.get("Content-Type"), "text/plain") + @patch.object(rest.RESTClientObject, "request") async def test_check_with_custom_headers_override_defaults(self, mock_request): """Test that custom headers in options override default headers for check API.""" diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index c3ee918..33548c9 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -3373,6 +3373,30 @@ def test_set_heading_if_not_set_multiple_headers_with_mixed_states(self): # Original headers should still exist self.assertEqual(result["headers"]["X-Another-Header"], "another-value") + def test_set_heading_if_not_set_two_defaults_two_customs_one_override(self): + """Test setting two default headers when two custom headers exist, with one custom overriding one default""" + # Start with two custom headers + options = { + "headers": { + "X-Request-ID": "my-custom-request-id", # This should override the default + "X-Tenant-ID": "tenant-123", # This is custom-only + } + } + + # Try to set two default headers + result = set_heading_if_not_set(options, "X-SDK-Version", "1.0.0") + result = set_heading_if_not_set(result, "X-Request-ID", "default-uuid") + + # Verify all four headers exist with correct values + self.assertEqual(result["headers"]["X-SDK-Version"], "1.0.0") # Default was set + self.assertEqual( + result["headers"]["X-Request-ID"], "my-custom-request-id" + ) # Custom overrode default + self.assertEqual( + result["headers"]["X-Tenant-ID"], "tenant-123" + ) # Custom preserved + self.assertEqual(len(result["headers"]), 3) # Exactly 3 headers + def test_set_heading_if_not_set_with_empty_string_value(self): """Test that empty string values in custom headers are preserved and not overridden.""" options = {"headers": {"X-Custom-Header": ""}} @@ -3406,6 +3430,35 @@ def test_set_heading_if_not_set_case_sensitivity(self): self.assertEqual(result["headers"]["X-Custom-Header"], "uppercase") self.assertEqual(result["headers"]["x-custom-header"], "lowercase") + @patch.object(rest.RESTClientObject, "request") + def test_content_type_cannot_be_overridden_by_custom_headers(self, mock_request): + """Test that Content-Type header cannot be overridden by custom headers.""" + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + + body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + + with OpenFgaClient(configuration) as api_client: + # Try to override Content-Type with a custom value + custom_options = {"headers": {"Content-Type": "text/plain"}} + api_client.check(body=body, options=custom_options) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + + # Content-Type should be application/json, NOT the custom text/plain + self.assertEqual(headers.get("Content-Type"), "application/json") + self.assertNotEqual(headers.get("Content-Type"), "text/plain") + api_client.close() + @patch.object(rest.RESTClientObject, "request") def test_check_with_custom_headers_override_defaults(self, mock_request): """Test that custom headers in options override default headers for check API."""