genezio
Version:
Command line utility to interact with Genezio infrastructure.
438 lines (430 loc) • 17.4 kB
JavaScript
import Mustache from "mustache";
import { AstNodeType, } from "../../models/genezioModels.js";
import { TriggerType } from "../../projectConfiguration/yaml/models.js";
import { pythonSdk } from "../templates/pythonSdk.js";
import path from "path";
import { UserError } from "../../errors.js";
const PYTHON_RESERVED_WORDS = [
"False",
"class",
"from",
"or",
"None",
"continue",
"global",
"pass",
"True",
"def",
"if",
"raise",
"and",
"del",
"import",
"return",
"as",
"elif",
"in",
"try",
"assert",
"else",
"is",
"while",
"async",
"except",
"lambda",
"with",
"await",
"finally",
"nonlocal",
"yield",
"break",
"for",
"not",
];
const modelTemplate = `# This is an auto generated code. This code should not be modified since the file can be overwritten
# if new genezio commands are executed.
from typing import Any, List
from enum import IntEnum, Enum
from datetime import datetime
from collections.abc import Mapping
{{#imports}}
from .{{{path}}} import {{#models}}{{{name}}}{{^last}}, {{/last}}{{/models}}
{{/imports}}
{{#externalTypes}}
{{{type}}}
{{/externalTypes}}
`;
const template = `# This is an auto generated code. This code should not be modified since the file can be overwritten
# if new genezio commands are executed.
from .remote import Remote
from typing import Any, List
from enum import IntEnum, Enum
from datetime import datetime
from collections.abc import Mapping
{{#imports}}
from .{{{path}}} import {{#models}}{{{name}}}{{^last}}, {{/last}}{{/models}}
{{/imports}}
{{#externalTypes}}
{{{type}}}
{{/externalTypes}}
class {{{className}}}:
remote = Remote("{{{_url}}}")
{{#methods}}
def {{{name}}}({{#parameters}}{{{name}}}{{^last}}, {{/last}}{{/parameters}}) -> {{{returnType}}}:
return {{#mapToObject}}{{{returnType}}}(**({{/mapToObject}}{{{className}}}.remote.call({{{methodCaller}}}{{#sendParameters}}{{{name}}}{{^last}}, {{/last}}{{/sendParameters}}){{#mapToObject}})){{/mapToObject}}
{{/methods}}
`;
class SdkGenerator {
async generateSdk(sdkGeneratorInput) {
const generateSdkOutput = {
files: [],
};
const modelViews = [];
for (const classInfo of sdkGeneratorInput.classesInfo) {
const externalTypes = [];
const _url = "%%%link_to_be_replace%%%";
const classConfiguration = classInfo.classConfiguration;
let classDefinition = undefined;
if (classInfo.program.body === undefined) {
continue;
}
for (const elem of classInfo.program.body) {
if (elem.type === AstNodeType.ClassDefinition) {
classDefinition = elem;
}
else {
externalTypes.push(elem);
}
}
if (classDefinition === undefined) {
continue;
}
const view = {
className: classDefinition.name,
_url: _url,
methods: [],
externalTypes: [],
imports: [],
};
let exportClassChecker = false;
for (const methodDefinition of classDefinition.methods) {
const methodConfiguration = classConfiguration.methods.find((e) => e.name === methodDefinition.name);
const methodConfigurationType = methodConfiguration?.type || classConfiguration.type;
if (methodConfigurationType !== TriggerType.jsonrpc ||
classConfiguration.type !== TriggerType.jsonrpc) {
continue;
}
exportClassChecker = true;
const methodView = {
name: methodDefinition.name,
parameters: [],
returnType: this.getParamType(methodDefinition.returnType),
mapToObject: this.isMapToObject(methodDefinition.returnType),
methodCaller: methodDefinition.params.length === 0
? `"${classDefinition.name}.${methodDefinition.name}"`
: `"${classDefinition.name}.${methodDefinition.name}", `,
};
methodView.parameters = methodDefinition.params.map((e) => {
return {
name: (PYTHON_RESERVED_WORDS.includes(e.name) ? e.name + "_" : e.name) +
": " +
this.getParamType(e.paramType) +
(e.optional
? " = None"
: e.defaultValue
? " = " +
(e.defaultValue.type === AstNodeType.StringLiteral
? "'" + e.defaultValue.value + "'"
: e.defaultValue.value)
: ""),
last: false,
};
});
methodView.sendParameters = methodDefinition.params.map((e) => {
return {
name: PYTHON_RESERVED_WORDS.includes(e.name) ? e.name + "_" : e.name,
last: false,
};
});
if (methodView.parameters.length > 0) {
methodView.parameters[methodView.parameters.length - 1].last = true;
methodView.sendParameters[methodView.sendParameters.length - 1].last = true;
}
methodView.parameters.unshift({
name: "self",
last: false,
});
if (methodView.parameters.length == 1) {
methodView.parameters[methodView.parameters.length - 1].last = true;
}
view.methods.push(methodView);
}
for (const externalType of externalTypes) {
if (externalType.path) {
let currentView = undefined;
for (const parentType of externalTypes) {
const isUsed = this.isExternalTypeUsedByOtherType(externalType, parentType);
if (isUsed && parentType.path && parentType.path !== externalType.path) {
currentView = this.addViewIfNotExists(modelViews, parentType, view, classInfo);
}
if (currentView &&
!classInfo.classConfiguration.path.includes(externalType.path)) {
this.addImportToCurrentView(currentView, externalType);
}
}
if (this.isExternalTypeUsedInMethod(externalType, classDefinition.methods)) {
currentView = view;
if (currentView &&
!classInfo.classConfiguration.path.includes(externalType.path)) {
this.addImportToCurrentView(currentView, externalType);
}
}
currentView = this.addViewIfNotExists(modelViews, externalType, view, classInfo);
if (!currentView?.externalTypes.find((e) => e.name === externalType.name)) {
currentView?.externalTypes.push({
type: this.generateExternalType(externalType),
name: externalType.name,
});
}
}
}
for (const modelView of modelViews) {
for (const importType of modelView.imports) {
if (importType.models.length > 0) {
importType.models[importType.models.length - 1].last = true;
}
}
}
for (const importType of view.imports) {
if (importType.models.length > 0) {
importType.models[importType.models.length - 1].last = true;
}
}
if (!exportClassChecker) {
continue;
}
const rawSdkClassName = `${classDefinition.name}.py`;
const sdkClassName = rawSdkClassName.charAt(0).toLowerCase() + rawSdkClassName.slice(1);
generateSdkOutput.files.push({
path: sdkClassName,
data: Mustache.render(template, view),
className: classDefinition.name,
});
for (const modelView of modelViews) {
generateSdkOutput.files.push({
path: modelView.path + ".py",
data: Mustache.render(modelTemplate, modelView),
className: "",
});
}
}
// generate remote.js
generateSdkOutput.files.push({
className: "Remote",
path: "remote.py",
data: pythonSdk,
});
return generateSdkOutput;
}
getParamType(elem) {
if (elem.type === AstNodeType.CustomNodeLiteral) {
const customAstNodeType = elem;
return customAstNodeType.rawValue;
}
else if (elem.type === AstNodeType.StringLiteral) {
return "str";
}
else if (elem.type === AstNodeType.IntegerLiteral) {
return "int";
}
else if (elem.type === AstNodeType.FloatLiteral ||
elem.type === AstNodeType.DoubleLiteral) {
return "float";
}
else if (elem.type === AstNodeType.BooleanLiteral) {
return "bool";
}
else if (elem.type === AstNodeType.Enum) {
return elem.name;
}
else if (elem.type === AstNodeType.AnyLiteral) {
return "Any";
}
else if (elem.type === AstNodeType.ArrayType) {
return `List[${this.getParamType(elem.generic)}]`;
}
else if (elem.type === AstNodeType.PromiseType) {
return `${this.getParamType(elem.generic)}`;
}
else if (elem.type === AstNodeType.TypeAlias) {
return elem.name;
}
else if (elem.type === AstNodeType.UnionType) {
return elem.params.map((e) => this.getParamType(e)).join(" | ");
}
else if (elem.type === AstNodeType.DateType) {
return "datetime";
}
return "Any";
}
generateEnum(e) {
const enumType = e.cases[0].type;
const allTypesAreTheSame = e.cases.map((v) => v.type).every((type) => type === enumType);
if (!allTypesAreTheSame) {
throw new UserError("All enum cases must be the same type. Fix enum " + e.name + " and try again.");
}
switch (enumType) {
case AstNodeType.StringLiteral:
return `class ${e.name}(str, Enum):\n\t${e.cases
.map((e) => `${e.name} = "${e.value}"`)
.join("\n\t")}`;
case AstNodeType.DoubleLiteral:
return `class ${e.name}(IntEnum):\n\t${e.cases
.map((e) => `${e.name} = ${e.value}`)
.join("\n\t")}`;
default:
throw new UserError("Unsupported enum type");
}
}
generateExternalType(type) {
if (type.type === AstNodeType.TypeAlias) {
const typeAlias = type;
return `${typeAlias.name} = ${this.getParamType(typeAlias.aliasType)};`;
}
else if (type.type === AstNodeType.Enum) {
const enumType = type;
return this.generateEnum(enumType);
}
else if (type.type === AstNodeType.StructLiteral) {
const typeAlias = type;
typeAlias.typeLiteral.properties.sort((a, b) => a.optional === b.optional ? 0 : a.optional ? 1 : -1);
return (`class ${typeAlias.name}:\n\tdef __init__(self, ${typeAlias.typeLiteral.properties
.map((e) => `${e.name}: ${this.getParamType(e.type)}${e.optional ? " = None" : ""}`)
.join(", ")}):\n\t\t` + // header
`${typeAlias.typeLiteral.properties
.map((e) => `${e.optional ? `if ${e.name} is not None:\n\t\t\t` : ""}${e.type.type === AstNodeType.CustomNodeLiteral
? `if isinstance(${e.name}, Mapping):\n\t\t\t${e.optional ? "\t" : ""}self.${e.name} = ${this.getParamType(e.type)}(**${e.name})\n\t\t${e.optional ? "\t" : ""}else:\n\t\t\t${e.optional ? "\t" : ""}self.${e.name} = ${e.name}`
: `self.${e.name} = ${e.name}`}`)
.join("\n\t\t")}`);
}
return "";
}
isMapToObject(type) {
if (type.type === AstNodeType.CustomNodeLiteral) {
const customAstNodeType = type;
if (customAstNodeType.rawValue === "Date") {
return false;
}
return true;
}
if (type.type === AstNodeType.PromiseType) {
return this.isMapToObject(type.generic);
}
return false;
}
isExternalTypeUsedByOtherType(externalType, type) {
if (type.type === AstNodeType.TypeAlias) {
const typeAlias = type;
return this.isExternalTypeUsedByOtherType(externalType, typeAlias.aliasType);
}
else if (type.type === AstNodeType.Enum) {
return false;
}
else if (type.type === AstNodeType.StructLiteral) {
const typeAlias = type;
return this.isExternalTypeUsedByOtherType(externalType, typeAlias.typeLiteral);
}
else if (type.type === AstNodeType.ArrayType) {
return this.isExternalTypeUsedByOtherType(externalType, type.generic);
}
else if (type.type === AstNodeType.PromiseType) {
return this.isExternalTypeUsedByOtherType(externalType, type.generic);
}
else if (type.type === AstNodeType.UnionType) {
return type.params.some((e) => this.isExternalTypeUsedByOtherType(externalType, e));
}
else if (type.type === AstNodeType.TypeLiteral) {
return type.properties.some((e) => this.isExternalTypeUsedByOtherType(externalType, e.type));
}
else if (type.type === AstNodeType.DateType) {
return false;
}
else if (type.type === AstNodeType.CustomNodeLiteral) {
if (type.rawValue === externalType.name) {
return true;
}
return false;
}
else if (type.type === AstNodeType.StringLiteral) {
return false;
}
else if (type.type === AstNodeType.IntegerLiteral ||
type.type === AstNodeType.FloatLiteral ||
type.type === AstNodeType.DoubleLiteral) {
return false;
}
else if (type.type === AstNodeType.BooleanLiteral) {
return false;
}
else if (type.type === AstNodeType.AnyLiteral) {
return false;
}
return false;
}
isExternalTypeUsedInMethod(externalType, methods) {
return methods.some((m) => this.isExternalTypeUsedByOtherType(externalType, m.returnType) ||
m.params.some((p) => this.isExternalTypeUsedByOtherType(externalType, p.paramType)));
}
addImportToCurrentView(currentView, externalType) {
let found = false;
for (const importType of currentView.imports) {
if (importType.path === externalType.path?.replace("/", ".") &&
!importType.models.find((e) => e.name === externalType.name)) {
importType.models.push({ name: externalType.name });
found = true;
break;
}
}
if (!found) {
let relativePath = path
.relative(currentView.path || ".", externalType.path || ".")
.replace("/", ".");
if (relativePath.substring(0, 3) == "...") {
relativePath = relativePath.substring(3);
}
if (!currentView.imports.find((e) => e.path === relativePath)) {
currentView.imports.push({
path: relativePath,
models: [{ name: externalType.name }],
});
}
}
}
addViewIfNotExists(modelViews, type, classView, classInfo) {
let found = false;
let currentView = undefined;
for (const modelView of modelViews) {
if (modelView.path === type.path) {
currentView = modelView;
found = true;
break;
}
}
if (!found) {
if (!classInfo.classConfiguration.path.includes(type.path)) {
currentView = {
path: type.path || "",
externalTypes: [],
imports: [],
};
modelViews.push(currentView);
}
else {
currentView = classView;
}
}
return currentView;
}
}
const supportedLanguages = ["py", "python"];
export default { SdkGenerator, supportedLanguages };