UNPKG

genezio

Version:

Command line utility to interact with Genezio infrastructure.

438 lines (430 loc) 17.4 kB
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 };