Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion xdk-gen/templates/python/client_macros.j2
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ stream_config: Optional[StreamConfig] = None

{# Macro for method return type #}
{% macro return_type(operation) -%}
{% if operation.is_streaming %}Generator[{% if operation.responses and "200" in operation.responses %}{{ operation.class_name }}Response{% else %}Dict[str, Any]{% endif %}, None, None]{% elif operation.parameters and (operation.parameters | selectattr('original_name', 'equalto', 'pagination_token') | list | length > 0 or operation.parameters | selectattr('original_name', 'equalto', 'next_token') | list | length > 0) %}Iterator[{% if operation.responses and "200" in operation.responses %}{{ operation.class_name }}Response{% else %}Dict[str, Any]{% endif %}]{% else %}{% if operation.responses and "200" in operation.responses %}{{ operation.class_name }}Response{% else %}Dict[str, Any]{% endif %}{% endif %}
{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}bytes{% elif operation.is_streaming %}Generator[{% if operation.responses and "200" in operation.responses %}{{ operation.class_name }}Response{% else %}Dict[str, Any]{% endif %}, None, None]{% elif operation.parameters and (operation.parameters | selectattr('original_name', 'equalto', 'pagination_token') | list | length > 0 or operation.parameters | selectattr('original_name', 'equalto', 'next_token') | list | length > 0) %}Iterator[{% if operation.responses and "200" in operation.responses %}{{ operation.class_name }}Response{% else %}Dict[str, Any]{% endif %}]{% else %}{% if operation.responses and "200" in operation.responses %}{{ operation.class_name }}Response{% else %}Dict[str, Any]{% endif %}{% endif %}
{%- endmacro %}

{# Macro for method docstring #}
Expand Down Expand Up @@ -388,7 +391,15 @@ headers = {% if operation.is_streaming %}{
response.raise_for_status()

# Parse the response data
{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}
# Binary endpoint - return raw bytes
return response.content
{% else %}
response_data = response.json()
{% endif %}

# Convert to Pydantic model if applicable
{% if operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}
Expand Down Expand Up @@ -594,7 +605,16 @@ headers = {% if operation.is_streaming %}{
response.raise_for_status()

# Parse the response data
{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}
# Binary endpoint - return raw bytes (pagination not applicable for binary)
yield response.content
return
{% else %}
response_data = response.json()
{% endif %}

# Convert to Pydantic model if applicable
{% if operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}
Expand Down
3 changes: 3 additions & 0 deletions xdk-gen/templates/python/test_contracts.j2
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Test{{ tag.class_name }}Contracts:
{% endfor %}
}
mock_response.raise_for_status.return_value = None
mock_response.headers = {'content-type': 'application/json'}
mock_session.{{ contract_test.method|lower }}.return_value = mock_response

# Prepare test parameters
Expand Down Expand Up @@ -236,6 +237,7 @@ class Test{{ tag.class_name }}Contracts:
mock_response.status_code = 200
mock_response.json.return_value = {}
mock_response.raise_for_status.return_value = None
mock_response.headers = {'content-type': 'application/json'}
mock_session.{{ contract_test.method|lower }}.return_value = mock_response

try:
Expand Down Expand Up @@ -277,6 +279,7 @@ class Test{{ tag.class_name }}Contracts:
mock_response.status_code = {{ contract_test.response_schema.status_code }}
mock_response.json.return_value = mock_response_data
mock_response.raise_for_status.return_value = None
mock_response.headers = {'content-type': 'application/json'}
mock_session.{{ contract_test.method|lower }}.return_value = mock_response

# Prepare minimal valid parameters
Expand Down
7 changes: 7 additions & 0 deletions xdk-gen/templates/python/test_pagination.j2
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Test{{ tag.class_name }}Pagination:
}
}
first_page_response.raise_for_status.return_value = None
first_page_response.headers = {'content-type': 'application/json'}

# Mock second page response (no next token = end of pagination)
second_page_response = Mock()
Expand All @@ -84,6 +85,7 @@ class Test{{ tag.class_name }}Pagination:
}
}
second_page_response.raise_for_status.return_value = None
second_page_response.headers = {'content-type': 'application/json'}

# Return different responses for consecutive calls
mock_session.get.side_effect = [first_page_response, second_page_response]
Expand Down Expand Up @@ -127,6 +129,7 @@ class Test{{ tag.class_name }}Pagination:
}
}
mock_response.raise_for_status.return_value = None
mock_response.headers = {'content-type': 'application/json'}
mock_session.get.return_value = mock_response

# Test item iteration
Expand All @@ -152,6 +155,7 @@ class Test{{ tag.class_name }}Pagination:
"meta": {"result_count": 0}
}
mock_response.raise_for_status.return_value = None
mock_response.headers = {'content-type': 'application/json'}
mock_session.get.return_value = mock_response

method = getattr(self.{{ tag.property_name }}_client, "{{ pagination_test.method_name }}")
Expand Down Expand Up @@ -191,6 +195,7 @@ class Test{{ tag.class_name }}Pagination:
}
}
mock_response_with_token.raise_for_status.return_value = None
mock_response_with_token.headers = {'content-type': 'application/json'}

second_page_response = Mock()
second_page_response.status_code = 200
Expand All @@ -199,6 +204,7 @@ class Test{{ tag.class_name }}Pagination:
"meta": {"result_count": 0}
}
second_page_response.raise_for_status.return_value = None
second_page_response.headers = {'content-type': 'application/json'}

mock_session.get.side_effect = [mock_response_with_token, second_page_response]

Expand Down Expand Up @@ -229,6 +235,7 @@ class Test{{ tag.class_name }}Pagination:
empty_response.status_code = 200
empty_response.json.return_value = {"data": [], "meta": {"result_count": 0}}
empty_response.raise_for_status.return_value = None
empty_response.headers = {'content-type': 'application/json'}
mock_session.get.return_value = empty_response

# Pick first paginatable method for testing
Expand Down
28 changes: 23 additions & 5 deletions xdk-gen/templates/typescript/client_class.j2
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@ export class {{ tag.class_name }}Client {
{% if operation.request_body and operation.request_body.required %}
* @param body {% if operation.request_body.content and operation.request_body.content["application/json"] and operation.request_body.content["application/json"].schema and operation.request_body.content["application/json"].schema.description %}{{ operation.request_body.content["application/json"].schema.description }}{% else %}Request body{% endif %}
{% endif %}
* @returns {Promise<{% if operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %}>} Promise resolving to the API response, or raw Response if requestOptions.raw is true
* @returns {Promise<{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}ArrayBuffer{% elif operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %}>} Promise resolving to the API response{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %} as binary ArrayBuffer{% endif %}, or raw Response if requestOptions.raw is true
*/
// Overload 1: raw: true returns Response
{{ operation.method_name }}(
Expand Down Expand Up @@ -158,7 +164,10 @@ export class {{ tag.class_name }}Client {
{% if operation.parameters | rejectattr('required') | rejectattr('location', 'equalto', 'path') | list | length > 0 or (operation.request_body and not operation.request_body.required) %}
options?: {{ operation.class_name }}Options
{% endif %}
): Promise<{% if operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %}>;
): Promise<{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}ArrayBuffer{% elif operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %}>;
// Implementation
async {{ operation.method_name }}(
{# Path parameters are always required - use location field #}
Expand All @@ -181,7 +190,10 @@ export class {{ tag.class_name }}Client {
{% if operation.parameters | rejectattr('required') | rejectattr('location', 'equalto', 'path') | list | length > 0 or (operation.request_body and not operation.request_body.required) %}
options: {{ operation.class_name }}Options = {}
{% endif %}
): Promise<{% if operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %} | Response> {
): Promise<{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}ArrayBuffer{% elif operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %} | Response> {
// Normalize options to handle both camelCase and original API parameter names
{% if operation.parameters | rejectattr('required') | rejectattr('location', 'equalto', 'path') | list | length > 0 or (operation.request_body and not operation.request_body.required) %}
{% if operation.parameters | rejectattr('required') | rejectattr('location', 'equalto', 'path') | list | length > 0 %}
Expand Down Expand Up @@ -280,10 +292,16 @@ export class {{ tag.class_name }}Client {
{% endif %}
};

return this.client.request<{% if operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %}>(
return this.client.request<{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}ArrayBuffer{% elif operation.responses and "200" in operation.responses or operation.responses and "201" in operation.responses %}{{ operation.class_name }}Response{% else %}any{% endif %}>(
'{{ operation.method | upper }}',
path + (params.toString() ? `?${params.toString()}` : ''),
finalRequestOptions
{% if operation.responses and (
("200" in operation.responses and operation.responses["200"].content and "application/octet-stream" in operation.responses["200"].content) or
("201" in operation.responses and operation.responses["201"].content and "application/octet-stream" in operation.responses["201"].content)
) %}{ ...finalRequestOptions, binary: true }{% else %}finalRequestOptions{% endif %}
);
}

Expand Down
13 changes: 8 additions & 5 deletions xdk-gen/templates/typescript/main_client.j2
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface RequestOptions {
raw?: boolean;
/** Security requirements for the endpoint (from OpenAPI spec) - used for smart auth selection */
security?: Array<Record<string, string[]>>;
/** Whether this endpoint returns binary data (ArrayBuffer) - determined from OpenAPI spec */
binary?: boolean;
}

/**
Expand Down Expand Up @@ -416,13 +418,14 @@ export class Client {
}

let data: T;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
// Check if binary response is expected (from OpenAPI spec)
if (options.binary) {
// Return ArrayBuffer for binary endpoints
data = await response.arrayBuffer() as T;
} else {
// Default: parse as JSON and transform keys
const rawData = await response.json();
// Transform snake_case keys to camelCase to match TypeScript conventions
data = transformKeys<T>(rawData);
} else {
data = await response.text() as T;
}

// Return parsed body for non-streaming requests
Expand Down
Loading