Skip to content

Commit c8d8bbb

Browse files
committed
Add option to initialize python functions.
1 parent d1a42b2 commit c8d8bbb

File tree

7 files changed

+114
-11
lines changed

7 files changed

+114
-11
lines changed

src/deploy/functions/runtimes/python/index.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { runWithVirtualEnv } from "../../../../functions/python";
1313
import { FirebaseError } from "../../../../error";
1414
import { Build } from "../../build";
1515

16-
const LATEST_VERSION: runtimes.Runtime = "python310";
16+
export const LATEST_VERSION: runtimes.Runtime = "python310";
1717

1818
/**
1919
* Create a runtime delegate for the Python runtime, if applicable.
@@ -37,6 +37,24 @@ export async function tryCreateDelegate(
3737
return Promise.resolve(new Delegate(context.projectId, context.sourceDir, runtime));
3838
}
3939

40+
/**
41+
* Get corresponding python binary name for a given runtime.
42+
*
43+
* By default, returns "python"
44+
*/
45+
export function getPythonBinary(runtime: runtimes.Runtime): string {
46+
if (process.platform === "win32") {
47+
// There is no easy way to get specific version of python executable in Windows.
48+
return "python.exe";
49+
}
50+
if (runtime === "python310") {
51+
return "python3.10";
52+
} else if (runtime === "python311") {
53+
return "python3.11";
54+
}
55+
return "python";
56+
}
57+
4058
export class Delegate implements runtimes.RuntimeDelegate {
4159
public readonly name = "python";
4260
constructor(
@@ -82,16 +100,7 @@ export class Delegate implements runtimes.RuntimeDelegate {
82100
}
83101

84102
getPythonBinary(): string {
85-
if (process.platform === "win32") {
86-
// There is no easy way to get specific version of python executable in Windows.
87-
return "python.exe";
88-
}
89-
if (this.runtime === "python310") {
90-
return "python3.10";
91-
} else if (this.runtime === "python311") {
92-
return "python3.11";
93-
}
94-
return "python";
103+
return getPythonBinary(this.runtime);
95104
}
96105

97106
validate(): Promise<void> {

src/functions/python.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function runWithVirtualEnv(
1212
commandAndArgs: string[],
1313
cwd: string,
1414
envs: Record<string, string>,
15+
spawnOpts: cp.SpawnOptions = {},
1516
venvDir = DEFAULT_VENV_DIR
1617
): cp.ChildProcess {
1718
const activateScriptPath =
@@ -25,6 +26,7 @@ export function runWithVirtualEnv(
2526
shell: true,
2627
cwd,
2728
stdio: [/* stdin= */ "pipe", /* stdout= */ "pipe", /* stderr= */ "pipe", "pipe"],
29+
...spawnOpts,
2830
// Linting disabled since internal types expect NODE_ENV which does not apply to Python runtimes.
2931
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
3032
env: envs as any,

src/init/features/functions/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
assertUnique,
1414
} from "../../../functions/projectConfig";
1515
import { FirebaseError } from "../../../error";
16+
import { isEnabled } from "../../../experiments";
1617

1718
const MAX_ATTEMPTS = 5;
1819

@@ -167,6 +168,12 @@ async function languageSetup(setup: any, config: Config): Promise<any> {
167168
value: "typescript",
168169
},
169170
];
171+
if (isEnabled("pythonfunctions")) {
172+
choices.push({
173+
name: "Python",
174+
value: "python",
175+
});
176+
}
170177
const language = await promptOnce({
171178
type: "list",
172179
message: "What language would you like to use to write Cloud Functions?",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import * as fs from "fs";
2+
import * as spawn from "cross-spawn";
3+
import * as path from "path";
4+
5+
import { Config } from "../../../config";
6+
import { getPythonBinary, LATEST_VERSION } from "../../../deploy/functions/runtimes/python";
7+
import { runWithVirtualEnv } from "../../../functions/python";
8+
import { promptOnce } from "../../../prompt";
9+
10+
const TEMPLATE_ROOT = path.resolve(__dirname, "../../../../templates/init/functions/python");
11+
const MAIN_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "main.py"), "utf8");
12+
const REQUIREMENTS_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "requirements.txt"), "utf8");
13+
const GITIGNORE_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "_gitignore"), "utf8");
14+
15+
/**
16+
* Create a Python Firebase Functions project.
17+
*/
18+
export async function setup(setup: any, config: Config): Promise<void> {
19+
await config.askWriteProjectFile(
20+
`${setup.functions.source}/requirements.txt`,
21+
REQUIREMENTS_TEMPLATE
22+
);
23+
await config.askWriteProjectFile(`${setup.functions.source}/.gitignore`, GITIGNORE_TEMPLATE);
24+
await config.askWriteProjectFile(`${setup.functions.source}/main.py`, MAIN_TEMPLATE);
25+
26+
// Write the latest supported runtime version to the config.
27+
config.set("functions.runtime", LATEST_VERSION);
28+
// Add python specific ignores to config.
29+
config.set("functions.ignore", ["venv", "__pycache__"]);
30+
31+
// Setup VENV.
32+
const venvProcess = spawn(getPythonBinary(LATEST_VERSION), ["-m", "venv", "venv"], {
33+
shell: true,
34+
cwd: config.path(setup.functions.source),
35+
stdio: [/* stdin= */ "pipe", /* stdout= */ "pipe", /* stderr= */ "pipe", "pipe"],
36+
});
37+
await new Promise((resolve, reject) => {
38+
venvProcess.on("exit", resolve);
39+
venvProcess.on("error", reject);
40+
});
41+
42+
const install = await promptOnce({
43+
name: "install",
44+
type: "confirm",
45+
message: "Do you want to install dependencies now?",
46+
default: true,
47+
});
48+
if (install) {
49+
// Update pip to support dependencies like pyyaml.
50+
const upgradeProcess = runWithVirtualEnv(
51+
["pip3", "install", "--upgrade", "pip"],
52+
config.path(setup.functions.source),
53+
{},
54+
{ stdio: ["inherit", "inherit", "inherit"] }
55+
);
56+
await new Promise((resolve, reject) => {
57+
upgradeProcess.on("exit", resolve);
58+
upgradeProcess.on("error", reject);
59+
});
60+
const installProcess = runWithVirtualEnv(
61+
[getPythonBinary(LATEST_VERSION), "-m", "pip", "install", "-r", "requirements.txt"],
62+
config.path(setup.functions.source),
63+
{},
64+
{ stdio: ["inherit", "inherit", "inherit"] }
65+
);
66+
await new Promise((resolve, reject) => {
67+
installProcess.on("exit", resolve);
68+
installProcess.on("error", reject);
69+
});
70+
}
71+
}

templates/init/functions/python/_gitignore

Whitespace-only changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Welcome to Cloud Functions for Firebase for Python!
2+
# To get started, simply uncomment the below code or create your own.
3+
# Deploy with `firebase deploy`
4+
5+
from firebase_functions import https
6+
from firebase_admin import initialize_app
7+
8+
# initialize_app()
9+
#
10+
#
11+
# @https.on_request()
12+
# def on_request_example(req: https.Request) -> https.Response:
13+
# return https.Response("Hello world!")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
git+https://github.com/firebase/firebase-functions-python.git@main#egg=firebase-functions

0 commit comments

Comments
 (0)