Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(type-safe-api): faster code generation for python infrastructure #849

Merged
merged 1 commit into from
Oct 9, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,59 @@ const toJavaType = (property: parseOpenapi.Model): string => {
}
};

const toPythonPrimitive = (property: parseOpenapi.Model): string => {
if (property.type === "string" && property.format === "date") {
return "date";
} else if (property.type === "string" && property.format === "date-time") {
return "datetime"
} else if (property.type === "any") {
return "object";
} else if (property.type === "binary") {
return "bytearray";
} else if (property.type === "number") {
if ((property as any).openapiType === "integer") {
return "int";
}

switch(property.format) {
case "int32":
case "int64":
return "int";
case "float":
case "double":
default:
return "float";
}
} else if (property.type === "boolean") {
return "bool";
} else if (property.type === "string") {
return "str";
}
return property.type;
};

const toPythonType = (property: parseOpenapi.Model): string => {
switch (property.export) {
case "generic":
case "reference":
return toPythonPrimitive(property);
case "array":
return `List[${property.link ? toPythonType(property.link) : property.type}]`;
case "dictionary":
return `Dict[str, ${property.link ? toPythonType(property.link) : property.type}]`;
default:
return property.type;
}
};

/**
* Mutates the given model to add language specific types and names
*/
const mutateModelWithAdditionalTypes = (model: parseOpenapi.Model) => {
(model as any).typescriptName = model.name;
(model as any).typescriptType = toTypeScriptType(model);
(model as any).javaType = toJavaType(model);
(model as any).pythonType = toPythonType(model);
(model as any).isPrimitive = PRIMITIVE_TYPES.has(model.type);

// Trim any surrounding quotes from name
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
###TSAPI_WRITE_FILE###
{
"id": "init",
"dir": "<%= metadata.srcDir || 'src' %>",
"name": "__init__",
"ext": ".py",
"overwrite": true
}
###/TSAPI_WRITE_FILE####
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from dataclasses import fields
###TSAPI_WRITE_FILE###
{
"id": "api",
"dir": "<%= metadata.srcDir || 'src' %>",
"name": "api",
"ext": ".py",
"overwrite": true
}
###/TSAPI_WRITE_FILE###from dataclasses import fields
from aws_pdk.type_safe_api import TypeSafeRestApi, TypeSafeApiIntegration
from {{#apiInfo}}{{#apis.0}}{{vendorExtensions.x-runtime-module-name}}{{/apis.0}}{{/apiInfo}}.api.operation_config import OperationLookup, OperationConfig
from <%- metadata.runtimeModuleName %>.api.operation_config import OperationLookup, OperationConfig
from os import path
from pathlib import Path

SPEC_PATH = path.join(str(Path(__file__).absolute().parent), "{{#apiInfo}}{{#apis.0}}{{vendorExtensions.x-relative-spec-path}}{{/apis.0}}{{/apiInfo}}")
SPEC_PATH = path.join(str(Path(__file__).absolute().parent), "<%- metadata.relativeSpecPath %>")

class Api(TypeSafeRestApi):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
###TSAPI_WRITE_FILE###
{
"dir": "<%- metadata.srcDir || 'src' %>",
"name": "functions",
"ext": ".py",
"overwrite": true
}
###/TSAPI_WRITE_FILE###from aws_cdk import Duration
from aws_cdk.aws_lambda import (
Function, Runtime, Tracing, Code
)
from aws_pdk.type_safe_api import SnapStartFunction
from os import path
from pathlib import Path

<%_ allOperations.forEach((operation) => { _%>
<%_ if (operation.vendorExtensions && operation.vendorExtensions['x-handler']) { _%>
<%_ const language = operation.vendorExtensions['x-handler'].language; _%>
<%_ const isTypeScript = language === 'typescript'; _%>
<%_ const isJava = language === 'java'; _%>
<%_ const isPython = language === 'python'; _%>

class <%- operation.operationIdPascalCase %>Function(<% if (isJava) { %>SnapStart<% } %>Function):
"""
Lambda function construct which points to the <%- language %> implementation of <%- operation.operationIdPascalCase %>
"""
def __init__(self, scope, id, **kwargs):
super().__init__(scope, id,
<%_ if (isTypeScript) { _%>
runtime=Runtime.<%- metadata['x-handlers-node-lambda-runtime-version'] %>,
<%_ } else if (isPython) { _%>
runtime=Runtime.<%- metadata['x-handlers-python-lambda-runtime-version'] %>,
<%_ } else if (isJava) { _%>
runtime=Runtime.<%- metadata['x-handlers-java-lambda-runtime-version'] %>,
<%_ } _%>
<%_ if (isTypeScript) { _%>
handler="index.handler",
<%_ } else if (isPython) { _%>
handler="<%- metadata['x-handlers-python-module'] %>.<%- operation.operationIdSnakeCase %>.handler",
<%_ } else if (isJava) { _%>
handler="<%- metadata['x-handlers-java-package'] %>.<%- operation.operationIdPascalCase %>Handler",
<%_ } _%>
code=Code.from_asset(path.join(str(Path(__file__).absolute().parent), "..",
<%_ if (isTypeScript) { _%>
"<%- metadata['x-handlers-typescript-asset-path'] %>",
"<%- operation.operationIdKebabCase %>",
<%_ } else if (isPython) { _%>
"<%- metadata['x-handlers-python-asset-path'] %>",
<%_ } else if (isJava) { _%>
"<%- metadata['x-handlers-java-asset-path'] %>",
<%_ } _%>
)),
tracing=Tracing.ACTIVE,
timeout=Duration.seconds(30),
**kwargs,
)

<%_ } _%>
<%_ }); _%>

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
###TSAPI_WRITE_FILE###
{
"id": "mock-integrations",
"dir": "<%= metadata.srcDir || 'src' %>",
"name": "mock_integrations",
"ext": ".py",
"overwrite": true
}
###/TSAPI_WRITE_FILE###import json
from aws_pdk.type_safe_api import Integrations, MockIntegration, TypeSafeApiIntegration
from <%- metadata.runtimeModuleName %>.models import *
from <%- metadata.runtimeModuleName %>.api.operation_config import OperationConfig
from os import path
from pathlib import Path

MOCK_DATA_PATH = path.join(str(Path(__file__).absolute().parent), "..", "mocks")

class MockIntegrations:
"""
Type-safe mock integrations for API operations
"""
<%_ if (metadata.enableMockIntegrations) { _%>
<%_ allOperations.forEach((operation) => { _%>
<%_ operation.responses.forEach((response) => { _%>
@staticmethod
def <%- operation.operationIdSnakeCase %>_<%- response.code %>(<% if (response.type !== 'void') { %>body: <% if (!response.isPrimitive) { %><%- response.pythonType %> = None<% } else { %>str<% } %><% } %>) -> MockIntegration:
"""
Mock integration to return a <%- response.code %> response from the <%- operation.name %> operation
<%_ if (!response.isPrimitive) { _%>
Call this with no arguments to use a generated mock response
<%_ } _%>
"""
<%_ if (response.type !== 'void') { _%>
<%_ if (!response.isPrimitive) { _%>
response_body = None
if body is None:
with open(path.join(MOCK_DATA_PATH, "{{httpMethod}}{}-{{code}}.json".format("{{path}}".replace("/", "-").lower())), "r") as f:
response_body = f.read()
else:
response_body = body.to_json()
<%_ } _%>
<%_ } _%>
return Integrations.mock(
status_code=<%- response.code %>,
<%_ if (response.type !== 'void') { _%>
<%_ if (!response.isPrimitive) { _%>
body=response_body,
<%_ } else { _%>
body=body,
<%_ } _%>
<%_ } _%>
)

<%_ }); _%>
<%_ }); _%>
@staticmethod
def mock_all(**kwargs) -> OperationConfig[TypeSafeApiIntegration]:
"""
Mock all operations for which generated JSON data can be returned.
The first available response is used for each operation. In most cases this is the successful 200 response.
Pass any additional or overridden integrations as kwargs, for example:

MockIntegrations.mock_all(
say_hello=TypeSafeApiIntegration(
integration=Integrations.lambda_(...)
)
)
"""
return OperationConfig(**{
**{
<%_ allOperations.forEach((operation) => { _%>
<%_ const firstResponse = operation.results[0] || operation.responses[0]; _%>
<%_ if (firstResponse && !firstResponse.isPrimitive) { _%>
"<%- operation.operationIdSnakeCase %>": TypeSafeApiIntegration(
integration=MockIntegrations.<%- operation.operationIdSnakeCase %>_<%- firstResponse.code %>(),
),
<%_ } _%>
<%_ }); _%>
},
**kwargs
})
<%_ } else { _%>
# No mock integrations have been generated, since mock data generation is disabled.
pass
<%_ } _%>
Loading
Loading