From 916cf9ada899bec781b272bb1b34a5620ddcfc79 Mon Sep 17 00:00:00 2001 From: eden wang <64514273+eyw520@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:35:36 -0500 Subject: [PATCH] fix(python): Include content-type header in endpoint generation (#5236) * Include content-type header in endpoint generation * Increment versions.yml. * chore: update changelog * resolve nit * Update snapshots --------- Co-authored-by: fern-bot --- .../changelogs/python-sdk/2024-11-20.mdx | 4 + generators/python/sdk/versions.yml | 7 ++ .../endpoint_function_generator.py | 94 ++++++++------ .../src/seed/dataservice/client.py | 30 +++++ .../grpc-proto/src/seed/userservice/client.py | 6 + .../pagination/.mock/definition/users.yml | 116 +++++++++--------- 6 files changed, 159 insertions(+), 98 deletions(-) create mode 100644 fern/pages/changelogs/python-sdk/2024-11-20.mdx diff --git a/fern/pages/changelogs/python-sdk/2024-11-20.mdx b/fern/pages/changelogs/python-sdk/2024-11-20.mdx new file mode 100644 index 00000000000..864c53203ef --- /dev/null +++ b/fern/pages/changelogs/python-sdk/2024-11-20.mdx @@ -0,0 +1,4 @@ +## 4.3.8 +**`(fix):`** Include content-type headers when available as part of endpoint request generation. + + diff --git a/generators/python/sdk/versions.yml b/generators/python/sdk/versions.yml index 918784ae672..c2417507cb1 100644 --- a/generators/python/sdk/versions.yml +++ b/generators/python/sdk/versions.yml @@ -1,4 +1,11 @@ # For unreleased changes, use unreleased.yml +- version: 4.3.8 + irVersion: 53 + changelogEntry: + - type: fix + summary: | + Include content-type headers when available as part of endpoint request generation. + - version: 4.3.7 irVersion: 53 changelogEntry: diff --git a/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py b/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py index c04b8917fe2..2fc00ac25de 100644 --- a/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py +++ b/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py @@ -262,36 +262,42 @@ def generate_single_function( endpoint=self._endpoint, named_parameters=named_parameters, path_parameters=self._endpoint.all_path_parameters, - snippet=endpoint_snippets[0].snippet - if endpoint_snippets is not None and len(endpoint_snippets) > 0 and include_snippet - else None, + snippet=( + endpoint_snippets[0].snippet + if endpoint_snippets is not None and len(endpoint_snippets) > 0 and include_snippet + else None + ), ), signature=AST.FunctionSignature( parameters=unnamed_parameters, named_parameters=named_parameters, return_type=self._get_endpoint_return_type(streaming_parameter=streaming_parameter), ), - body=self._create_endpoint_body_writer( - service=self._service, - endpoint=self._endpoint, - idempotency_headers=self._idempotency_headers, - request_body_parameters=self.request_body_parameters, - is_async=self._is_async, - parameters=unnamed_parameters, - named_parameters=named_parameters, - ) - if not is_overloaded - else self._create_empty_body_writer(), - decorators=[ - AST.Expression( - AST.Reference( - qualified_name_excluding_import=("overload",), - import_=AST.ReferenceImport(module=AST.Module.built_in(("typing",))), - ) + body=( + self._create_endpoint_body_writer( + service=self._service, + endpoint=self._endpoint, + idempotency_headers=self._idempotency_headers, + request_body_parameters=self.request_body_parameters, + is_async=self._is_async, + parameters=unnamed_parameters, + named_parameters=named_parameters, ) - ] - if is_overloaded - else [], + if not is_overloaded + else self._create_empty_body_writer() + ), + decorators=( + [ + AST.Expression( + AST.Reference( + qualified_name_excluding_import=("overload",), + import_=AST.ReferenceImport(module=AST.Module.built_in(("typing",))), + ) + ) + ] + if is_overloaded + else [] + ), ) return GeneratedEndpointFunction( function=function_declaration, @@ -372,17 +378,19 @@ def _get_endpoint_named_parameters( name=get_parameter_name(header.name.name), docs=header.docs, type_hint=header_type_hint, - initializer=AST.Expression( - AST.FunctionInvocation( - function_definition=AST.Reference( - import_=AST.ReferenceImport(module=AST.Module.built_in(("os",))), - qualified_name_excluding_import=("getenv",), - ), - args=[AST.Expression(f'"{header.env}"')], + initializer=( + AST.Expression( + AST.FunctionInvocation( + function_definition=AST.Reference( + import_=AST.ReferenceImport(module=AST.Module.built_in(("os",))), + qualified_name_excluding_import=("getenv",), + ), + args=[AST.Expression(f'"{header.env}"')], + ) ) - ) - if header.env is not None - else None, + if header.env is not None + else None + ), ), ) @@ -510,9 +518,9 @@ def get_httpx_request( return HttpX.make_request( is_streaming=is_streaming, is_async=is_async, - path=self._get_path_for_endpoint(endpoint=endpoint) - if not is_endpoint_path_empty(endpoint) - else None, + path=( + self._get_path_for_endpoint(endpoint=endpoint) if not is_endpoint_path_empty(endpoint) else None + ), url=self._get_environment_as_str(endpoint=endpoint), method=method, query_parameters=self._get_query_parameters_for_endpoint(endpoint=endpoint, parent_writer=writer), @@ -1165,6 +1173,12 @@ def _get_headers_for_endpoint( if endpoint.idempotent: ir_headers += idempotency_headers + if endpoint.request_body is not None: + unioned_value = endpoint.request_body.get_as_union() + if unioned_value.type == "inlinedRequestBody": + if unioned_value.content_type is not None: + headers.append(("content-type", AST.Expression(f'"{unioned_value.content_type}"'))) + for header in ir_headers: literal_header_value = self._context.get_literal_header_value(header) if literal_header_value is not None and type(literal_header_value) is str: @@ -1443,9 +1457,11 @@ def generate_snippet(self) -> AST.Expression: self.snippet_writer.get_snippet_for_named_parameter( parameter_name=self.path_parameter_names[path_parameter.name], # If there's no value put a None in place as path parameters are unnamed and cannot be skipped - value=path_parameter_value - if path_parameter_value is not None - else AST.Expression(AST.TypeHint.none()), + value=( + path_parameter_value + if path_parameter_value is not None + else AST.Expression(AST.TypeHint.none()) + ), ), ) diff --git a/seed/python-sdk/grpc-proto-exhaustive/src/seed/dataservice/client.py b/seed/python-sdk/grpc-proto-exhaustive/src/seed/dataservice/client.py index 36efa4075e9..a6f245ec9fb 100644 --- a/seed/python-sdk/grpc-proto-exhaustive/src/seed/dataservice/client.py +++ b/seed/python-sdk/grpc-proto-exhaustive/src/seed/dataservice/client.py @@ -75,6 +75,9 @@ def upload( ), "namespace": namespace, }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -140,6 +143,9 @@ def delete( object_=filter, annotation=Metadata, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -190,6 +196,9 @@ def describe( object_=filter, annotation=Metadata, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -397,6 +406,9 @@ def query( object_=indexed_data, annotation=IndexedData, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -470,6 +482,9 @@ def update( object_=indexed_data, annotation=IndexedData, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -547,6 +562,9 @@ async def main() -> None: ), "namespace": namespace, }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -620,6 +638,9 @@ async def main() -> None: object_=filter, annotation=Metadata, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -678,6 +699,9 @@ async def main() -> None: object_=filter, annotation=Metadata, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -909,6 +933,9 @@ async def main() -> None: object_=indexed_data, annotation=IndexedData, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -990,6 +1017,9 @@ async def main() -> None: object_=indexed_data, annotation=IndexedData, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) diff --git a/seed/python-sdk/grpc-proto/src/seed/userservice/client.py b/seed/python-sdk/grpc-proto/src/seed/userservice/client.py index a5be6912d91..fec99e148bb 100644 --- a/seed/python-sdk/grpc-proto/src/seed/userservice/client.py +++ b/seed/python-sdk/grpc-proto/src/seed/userservice/client.py @@ -71,6 +71,9 @@ def create( object_=metadata, annotation=Metadata, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) @@ -153,6 +156,9 @@ async def main() -> None: object_=metadata, annotation=Metadata, direction="write" ), }, + headers={ + "content-type": "application/json", + }, request_options=request_options, omit=OMIT, ) diff --git a/seed/python-sdk/pagination/.mock/definition/users.yml b/seed/python-sdk/pagination/.mock/definition/users.yml index ecde10ef707..ddc0bc1848b 100644 --- a/seed/python-sdk/pagination/.mock/definition/users.yml +++ b/seed/python-sdk/pagination/.mock/definition/users.yml @@ -1,9 +1,9 @@ imports: root: __package__.yml -types: - Order: - enum: +types: + Order: + enum: - asc - desc @@ -15,8 +15,8 @@ types: properties: cursor: optional - UserListContainer: - properties: + UserListContainer: + properties: users: list UserPage: @@ -24,60 +24,59 @@ types: data: UserListContainer next: optional - UserOptionalListContainer: - properties: + UserOptionalListContainer: + properties: users: optional> UserOptionalListPage: properties: data: UserOptionalListContainer next: optional - UsernameContainer: properties: results: list - ListUsersExtendedResponse: + ListUsersExtendedResponse: extends: - UserPage properties: - total_count: + total_count: type: integer docs: The totall number of /users - ListUsersExtendedOptionalListResponse: + ListUsersExtendedOptionalListResponse: extends: - UserOptionalListPage properties: - total_count: + total_count: type: integer docs: The totall number of /users - - ListUsersPaginationResponse: - properties: + + ListUsersPaginationResponse: + properties: hasNextPage: optional page: optional - total_count: + total_count: type: integer docs: The totall number of /users data: list - Page: - properties: - page: + Page: + properties: + page: type: integer docs: The current page next: optional per_page: integer total_page: integer - NextPage: - properties: + NextPage: + properties: page: integer starting_after: string - User: - properties: + User: + properties: name: string id: integer @@ -86,7 +85,7 @@ service: base-path: /users endpoints: listWithCursorPagination: - pagination: + pagination: cursor: $request.starting_after next_cursor: $response.page.next.starting_after results: $response.data @@ -95,41 +94,41 @@ service: request: name: ListUsersCursorPaginationRequest query-parameters: - page: + page: type: optional docs: Defaults to first page - per_page: + per_page: type: optional docs: Defaults to per page - order: - type: optional - starting_after: + order: + type: optional + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithBodyCursorPagination: - pagination: + pagination: cursor: $request.pagination.cursor next_cursor: $response.page.next.starting_after results: $response.data method: POST path: "" - request: + request: name: ListUsersBodyCursorPaginationRequest - body: + body: properties: - pagination: + pagination: type: optional - docs: | + docs: | The object that contains the cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithOffsetPagination: - pagination: + pagination: offset: $request.page results: $response.data method: GET @@ -137,38 +136,37 @@ service: request: name: ListUsersOffsetPaginationRequest query-parameters: - page: + page: type: optional docs: Defaults to first page - per_page: + per_page: type: optional docs: Defaults to per page - order: - type: optional - starting_after: + order: + type: optional + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse listWithBodyOffsetPagination: - pagination: + pagination: offset: $request.pagination.page results: $response.data method: POST path: "" - request: + request: name: ListUsersBodyOffsetPaginationRequest - body: + body: properties: - pagination: + pagination: type: optional - docs: | + docs: | The object that contains the offset used for pagination in order to fetch the next page of results. response: ListUsersPaginationResponse - listWithOffsetStepPagination: pagination: offset: $request.page @@ -191,8 +189,8 @@ service: order: type: optional response: ListUsersPaginationResponse - - listWithOffsetPaginationHasNextPage: + + listWithOffsetPaginationHasNextPage: pagination: offset: $request.page results: $response.data @@ -217,7 +215,7 @@ service: response: ListUsersPaginationResponse listWithExtendedResults: - pagination: + pagination: cursor: $request.cursor next_cursor: $response.next results: $response.data.users @@ -230,7 +228,7 @@ service: response: ListUsersExtendedResponse listWithExtendedResultsAndOptionalData: - pagination: + pagination: cursor: $request.cursor next_cursor: $response.next results: $response.data.users @@ -243,7 +241,7 @@ service: response: ListUsersExtendedOptionalListResponse listUsernames: - pagination: + pagination: cursor: $request.starting_after next_cursor: $response.cursor.after results: $response.cursor.data @@ -252,10 +250,10 @@ service: request: name: ListUsernamesRequest query-parameters: - starting_after: + starting_after: type: optional - docs: | - The cursor used for pagination in order to fetch + docs: | + The cursor used for pagination in order to fetch the next page of results. response: root.UsernameCursor @@ -266,6 +264,6 @@ service: request: name: ListWithGlobalConfigRequest query-parameters: - offset: + offset: type: optional response: UsernameContainer \ No newline at end of file