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

Python/add pyodide #5289

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a7aecc6
adopt yalin's code with comments addressed
Dec 6, 2024
7e4e3a2
run with pyodide if user doesn't have venv
Dec 6, 2024
208dd37
update changelog and package.json
Dec 6, 2024
ea6dc89
Merge branch 'main' of https://github.com/microsoft/typespec into pyt…
Dec 6, 2024
a3bb2b5
Merge branch 'main' of https://github.com/microsoft/typespec into pyt…
Dec 9, 2024
6332813
format
Dec 9, 2024
c2b721b
wrap calls to install python deps in a try catch
Dec 9, 2024
f1c28a8
black formatting on generator code
Dec 9, 2024
a65e1c3
try diff building format
Dec 9, 2024
19e1fb8
install pyodide deps
Dec 9, 2024
f3d4a25
try to not have python command line pop up in call
Dec 9, 2024
d6da896
don't rerun python command in python3 script
Dec 9, 2024
a6f2efe
Merge branch 'main' of https://github.com/microsoft/typespec into pyt…
Dec 10, 2024
e8f93f2
update location of compiled files
Dec 10, 2024
e6ec03a
fix check for pyodide
Dec 10, 2024
14ef204
try online
Dec 10, 2024
2fe4bf7
temp
Dec 10, 2024
5fefb93
try to fix path issue
tadelesh Dec 11, 2024
5299c1c
roll back packaging of eng in dist
Dec 11, 2024
63d4a15
add back duplicate run-python3 files
Dec 11, 2024
871fed1
fix emit path
Dec 11, 2024
0b66e28
format
Dec 11, 2024
8584e56
fix root
Dec 11, 2024
5d35a5d
general command working
Dec 11, 2024
f56b5d8
Merge remote-tracking branch 'origin/main' into python/addPyodide
tadelesh Dec 17, 2024
20280dc
resolve all path issues
tadelesh Dec 18, 2024
1680204
Merge remote-tracking branch 'origin/main' into python/addPyodide
tadelesh Dec 18, 2024
b2adef4
changelog
tadelesh Dec 18, 2024
5e26307
fix format
tadelesh Dec 18, 2024
930c71c
consolidate dep version
tadelesh Dec 18, 2024
84fb968
Merge branch 'main' into python/addPyodide
tadelesh Dec 18, 2024
2fe27ea
fix version conflict
tadelesh Dec 18, 2024
ff2955c
remove useless dep
tadelesh Dec 18, 2024
1414b94
remove load package
tadelesh Dec 18, 2024
5255adc
consolidate dep version
tadelesh Dec 18, 2024
efd55ff
refine
tadelesh Dec 19, 2024
8c69582
try remove useless part
tadelesh Dec 19, 2024
99aa67b
fix pyodide load path issue
tadelesh Dec 19, 2024
8324181
format
tadelesh Dec 19, 2024
706db7a
fix path
tadelesh Dec 19, 2024
43ab26c
resolve pip not found issue
tadelesh Dec 23, 2024
cbe96e6
refine ci script
tadelesh Dec 23, 2024
161f92d
fix path
tadelesh Dec 23, 2024
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
5 changes: 5 additions & 0 deletions cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ words:
- debugpy
- Declipse
- Dedupes
- deps
- destructures
- devdiv
- Diagnoser
Expand Down Expand Up @@ -132,6 +133,7 @@ words:
- nanos
- nexted
- nihao
- NODEFS
- noformat
- noopener
- noreferrer
Expand Down Expand Up @@ -164,12 +166,15 @@ words:
- pwsh
- pyexpat
- pygen
- pyimport
- pylint
- pylintrc
- pyodide
- pyproject
- pyright
- pyrightconfig
- pytest
- pyyaml
- rcfile
- reactivex
- recase
Expand Down
6 changes: 6 additions & 0 deletions packages/http-client-python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log - @typespec/http-client-python

## 0.5.0

### Features

- Add support for generation in enviroments without a Python installation

## 0.4.4

### Bug Fixes
Expand Down
111 changes: 88 additions & 23 deletions packages/http-client-python/emitter/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { EmitContext, NoTarget } from "@typespec/compiler";
import { execSync } from "child_process";
import fs from "fs";
import path, { dirname } from "path";
import { loadPyodide } from "pyodide";
import { fileURLToPath } from "url";
import { emitCodeModel } from "./code-model.js";
import { saveCodeModelAsYaml } from "./external-process.js";
import { PythonEmitterOptions, PythonSdkContext, reportDiagnostic } from "./lib.js";
import { runPython3 } from "./run-python3.js";
import { removeUnderscoresFromNamespace } from "./utils.js";

export function getModelsMode(context: SdkContext): "dpg" | "none" {
Expand Down Expand Up @@ -85,51 +87,114 @@ export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
});
return;
}

addDefaultOptions(sdkContext);
const yamlPath = await saveCodeModelAsYaml("python-yaml-path", yamlMap);
let venvPath = path.join(root, "venv");
if (fs.existsSync(path.join(venvPath, "bin"))) {
venvPath = path.join(venvPath, "bin", "python");
} else if (fs.existsSync(path.join(venvPath, "Scripts"))) {
venvPath = path.join(venvPath, "Scripts", "python.exe");
} else {
throw new Error("Virtual environment doesn't exist.");
}
const commandArgs = [
venvPath,
`${root}/eng/scripts/setup/run_tsp.py`,
`--output-folder=${outputDir}`,
`--cadl-file=${yamlPath}`,
];
addDefaultOptions(sdkContext);
const resolvedOptions = sdkContext.emitContext.options;
const commandArgs: Record<string, string> = {};
if (resolvedOptions["packaging-files-config"]) {
const keyValuePairs = Object.entries(resolvedOptions["packaging-files-config"]).map(
([key, value]) => {
return `${key}:${value}`;
},
);
commandArgs.push(`--packaging-files-config='${keyValuePairs.join("|")}'`);
commandArgs["packaging-files-config"] = keyValuePairs.join("|");
resolvedOptions["packaging-files-config"] = undefined;
}
if (
resolvedOptions["package-pprint-name"] !== undefined &&
!resolvedOptions["package-pprint-name"].startsWith('"')
) {
resolvedOptions["package-pprint-name"] = `"${resolvedOptions["package-pprint-name"]}"`;
resolvedOptions["package-pprint-name"] = `${resolvedOptions["package-pprint-name"]}`;
}

for (const [key, value] of Object.entries(resolvedOptions)) {
commandArgs.push(`--${key}=${value}`);
commandArgs[key] = value;
}
if (sdkContext.arm === true) {
commandArgs.push("--azure-arm=true");
commandArgs["azure-arm"] = "true";
}
if (resolvedOptions.flavor === "azure") {
commandArgs.push("--emit-cross-language-definition-file=true");
commandArgs["emit-cross-language-definition-file"] = "true";
}
commandArgs.push("--from-typespec=true");
commandArgs["from-typespec"] = "true";

if (!program.compilerOptions.noEmit && !program.hasError()) {
execSync(commandArgs.join(" "));
// if not using pyodide and there's no venv, we try to create venv
if (!resolvedOptions["use-pyodide"] && !fs.existsSync(path.join(root, "venv"))) {
try {
await runPython3(path.join(root, "/eng/scripts/setup/install.py"));
await runPython3(path.join(root, "/eng/scripts/setup/prepare.py"));
} catch (error) {
// if the python env is not ready, we use pyodide instead
resolvedOptions["use-pyodide"] = true;
}
}

if (resolvedOptions["use-pyodide"]) {
// here we run with pyodide
const pyodide = await setupPyodideCall(root);
// create the output folder if not exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// mount output folder to pyodide
pyodide.FS.mkdirTree("/output");
pyodide.FS.mount(pyodide.FS.filesystems.NODEFS, { root: outputDir }, "/output");
// mount yaml file to pyodide
pyodide.FS.mkdirTree("/yaml");
pyodide.FS.mount(pyodide.FS.filesystems.NODEFS, { root: path.dirname(yamlPath) }, "/yaml");
const globals = pyodide.toPy({
outputFolder: "/output",
yamlFile: `/yaml/${path.basename(yamlPath)}`,
commandArgs,
});
const pythonCode = `
async def main():
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", SyntaxWarning) # bc of m2r2 dep issues
from pygen import m2r, preprocess, codegen, black
m2r.M2R(output_folder=outputFolder, cadl_file=yamlFile, **commandArgs).process()
preprocess.PreProcessPlugin(output_folder=outputFolder, cadl_file=yamlFile, **commandArgs).process()
codegen.CodeGenerator(output_folder=outputFolder, cadl_file=yamlFile, **commandArgs).process()
black.BlackScriptPlugin(output_folder=outputFolder, **commandArgs).process()

await main()`;
await pyodide.runPythonAsync(pythonCode, { globals });
} else {
// here we run with native python
let venvPath = path.join(root, "venv");
if (fs.existsSync(path.join(venvPath, "bin"))) {
venvPath = path.join(venvPath, "bin", "python");
} else if (fs.existsSync(path.join(venvPath, "Scripts"))) {
venvPath = path.join(venvPath, "Scripts", "python.exe");
} else {
throw new Error("Virtual environment doesn't exist.");
}
commandArgs["output-folder"] = outputDir;
commandArgs["cadl-file"] = yamlPath;
const commandFlags = Object.entries(commandArgs)
.map(([key, value]) => `--${key}=${value}`)
.join(" ");
const command = `${venvPath} ${root}/eng/scripts/setup/run_tsp.py ${commandFlags}`;
execSync(command);
}
}
}

async function setupPyodideCall(root: string) {
const pyodide = await loadPyodide({
indexURL: path.dirname(fileURLToPath(import.meta.resolve("pyodide"))),
});
// mount generator to pyodide
pyodide.FS.mkdirTree("/generator");
pyodide.FS.mount(
pyodide.FS.filesystems.NODEFS,
{ root: path.join(root, "generator") },
"/generator",
);
await pyodide.loadPackage("micropip");
const micropip = pyodide.pyimport("micropip");
await micropip.install("emfs:/generator/dist/pygen-0.1.0-py3-none-any.whl");
return pyodide;
}
2 changes: 2 additions & 0 deletions packages/http-client-python/emitter/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PythonEmitterOptions {
debug?: boolean;
flavor?: "azure";
"examples-dir"?: string;
"use-pyodide"?: boolean;
}

export interface PythonSdkContext<TServiceOperation extends SdkServiceOperation>
Expand All @@ -43,6 +44,7 @@ const EmitterOptionsSchema: JSONSchemaType<PythonEmitterOptions> = {
debug: { type: "boolean", nullable: true },
flavor: { type: "string", nullable: true },
"examples-dir": { type: "string", nullable: true, format: "absolute-path" },
"use-pyodide": { type: "boolean", nullable: true },
},
required: [],
};
Expand Down
20 changes: 20 additions & 0 deletions packages/http-client-python/emitter/src/run-python3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This script wraps logic in @azure-tools/extension to resolve
// the path to Python 3 so that a Python script file can be run
// from an npm script in package.json. It uses the same Python 3
// path resolution algorithm as AutoRest so that the behavior
// is fully consistent (and also supports AUTOREST_PYTHON_EXE).
//
// Invoke it like so: "tsx run-python3.ts script.py"

import cp from "child_process";
import { patchPythonPath } from "./system-requirements.js";

export async function runPython3(...args: string[]) {
const command = await patchPythonPath(["python", ...args], {
version: ">=3.8",
environmentVariable: "AUTOREST_PYTHON_EXE",
});
cp.execSync(command.join(" "), {
stdio: [0, 1, 2],
});
}
Loading
Loading