diff --git a/aiogqlc/client.py b/aiogqlc/client.py index 6827105..a4ae6ea 100644 --- a/aiogqlc/client.py +++ b/aiogqlc/client.py @@ -169,6 +169,7 @@ async def execute( query: str, variables: Optional[Dict[str, Any]] = None, operation: Optional[str] = None, + **kwargs, ) -> aiohttp.ClientResponse: nulled_variables, files_to_paths_mapping = self.prepare(variables) data_param: Dict[str, Any] @@ -182,7 +183,7 @@ async def execute( json_data = serialize_payload(query, variables, operation) data_param = {"json": json_data} - async with self.session.post(self.endpoint, **data_param) as response: + async with self.session.post(self.endpoint, **kwargs, **data_param) as response: await response.read() return response diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md new file mode 100644 index 0000000..e30daac --- /dev/null +++ b/docs/advanced-usage.md @@ -0,0 +1,23 @@ +# Advanced usage + +## Passing request options to `aiohttp` + +While you can set various default options on your `aiohttp.ClientSession` instance, +there's sometimes the need to pass extra options to the underlying request made by `aiohttp`. + +For this purpose, any additional keyword argument passed to `GraphQLClient.execute` will be passed to `aiohttp.ClientSession.request`. + +```python +import aiohttp +from aiogqlc import GraphQLClient + +async def foo(): + async with aiohttp.ClientSession() as session: + client = GraphQLClient("https://example.com/graphql/", session=session) + + response = await client.execute( + document="query { someField }", + headers={"Authorization": "Bearer SomeToken"}, + timeout=aiohttp.ClientTimeout(total=10), + ) +``` diff --git a/docs/authentication.md b/docs/authentication.md index c6fc1ac..0f02d67 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -7,6 +7,8 @@ Take a look at the [aiohttp documentation][aiohttp-headers-url] to learn more. [aiohttp-headers-url]: https://docs.aiohttp.org/en/stable/client_advanced.html#custom-request-headers +The following example shows how to set a default `Authorization` header for the whole session. + ```python import aiohttp from aiogqlc import GraphQLClient @@ -20,6 +22,22 @@ async def foo(): client = GraphQLClient("https://example.com/graphql/", session=session) ``` +Instead of setting a default header for the whole session, you can also set a header for a single request. + +```python +import aiohttp +from aiogqlc import GraphQLClient + +headers = { + "Authorization": "Token " +} + +async def foo(): + async with aiohttp.ClientSession() as session: + client = GraphQLClient("https://example.com/graphql/", session=session) + response = await client.execute("query { someField }", headers=headers) +``` + ## Authenticate `graphql-ws` connections GraphQL servers _usualy_ don't support the authentication of WebSocket connections via @@ -39,7 +57,7 @@ from aiogqlc import GraphQLClient async def foo(): async with aiohttp.ClientSession() as session: client = GraphQLClient("https://example.com/graphql/", session=session) - + connection_params = { "username": "john", "password": "1234", diff --git a/mkdocs.yml b/mkdocs.yml index 2018ffd..f17d7c6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: AIOGQLC -site_description: LUL +site_description: "Python aynchronous/IO GraphQL client with file upload and subscription support." site_url: https://doctorjohn.github.io/aiogqlc/ repo_url: https://github.com/DoctorJohn/aiogqlc/ @@ -20,20 +20,20 @@ markdown_extensions: use_pygments: true anchor_linenums: true - pymdownx.tabbed: - alternate_style: true + alternate_style: true - pymdownx.superfences - pymdownx.details - nav: - Overview: index.md - Getting started: getting-started.md - Operations: - - Queries: queries.md - - Mutations: mutations.md - - File Uploads: file-uploads.md - - Subscriptions: subscriptions.md - - Authentication: authentication.md + - Queries: queries.md + - Mutations: mutations.md + - File Uploads: file-uploads.md + - Subscriptions: subscriptions.md + - Authentication: authentication.md + - Advanced Usage: advanced-usage.md - Guides: - - Contributing: contributing.md - - Migrating: migrating.md + - Contributing: contributing.md + - Migrating: migrating.md diff --git a/tests/app.py b/tests/app.py index 2f7fc8d..179ff73 100644 --- a/tests/app.py +++ b/tests/app.py @@ -54,6 +54,10 @@ def todos(self) -> typing.List[Todo]: def todo(self, id: strawberry.ID) -> Todo: return todos[int(id)] + @strawberry.field + def authorization_header(self, info: Info) -> str: + return info.context["request"].headers["Authorization"] + @strawberry.type class Mutation: diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..33d77b0 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,37 @@ +from aiogqlc import GraphQLClient +from tests.app import create_app + + +async def test_execute_extra_kwargs_are_passed_to_aiohttp(graphql_session): + query = """ + query { + authorizationHeader + } + """ + + client = GraphQLClient(endpoint="/graphql", session=graphql_session) + response = await client.execute(query, headers={"Authorization": "Bearer Token123"}) + + assert await response.json() == {"data": {"authorizationHeader": "Bearer Token123"}} + + +async def test_default_headers_can_be_overridden(aiohttp_client): + app = create_app() + graphql_session = await aiohttp_client( + app, headers={"Authorization": "Bearer DefaultToken"} + ) + + query = """ + query { + authorizationHeader + } + """ + + client = GraphQLClient(endpoint="/graphql", session=graphql_session) + response = await client.execute( + query, headers={"Authorization": "Bearer SpecialToken"} + ) + + result = await response.json() + assert result["data"]["authorizationHeader"] != "Bearer DefaultToken" + assert result["data"]["authorizationHeader"] == "Bearer SpecialToken"