mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
[python] Add discontinue date for Python 3.6 (#7709)
This PR does a few things: - Changes the existing warning message for Python 3.6 to print a discontinue date - Will automatically fail new Python 3.6 deployments created after that date - Consolidates logic to make Python version selection work in a similar manner to Node.js version selection - Changes tests from JS to TS
This commit is contained in:
5
packages/python/jest.config.js
Normal file
5
packages/python/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
@@ -15,11 +15,13 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build",
|
||||
"test-integration-once": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "jest --env node --verbose --runInBand --bail test/unit.test.ts ",
|
||||
"test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.ts",
|
||||
"prepublishOnly": "node build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/execa": "^0.9.0",
|
||||
"@types/jest": "27.0.1",
|
||||
"@vercel/build-utils": "2.15.2-canary.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "^1.0.0",
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
NowBuildError,
|
||||
} from '@vercel/build-utils';
|
||||
import { installRequirement, installRequirementsFile } from './install';
|
||||
import { getLatestPythonVersion, getSupportedPythonVersion } from './version';
|
||||
|
||||
async function pipenvConvert(cmd: string, srcDir: string) {
|
||||
debug('Running pipfile2req...');
|
||||
@@ -59,9 +60,7 @@ export const build = async ({
|
||||
meta = {},
|
||||
config,
|
||||
}: BuildOptions) => {
|
||||
let pipPath = meta.isDev ? 'pip3' : 'pip3.9';
|
||||
let pythonPath = meta.isDev ? 'python3' : 'python3.9';
|
||||
let pythonRuntime = meta.isDev ? 'python3' : 'python3.9';
|
||||
let pythonVersion = getLatestPythonVersion(meta);
|
||||
|
||||
workPath = await downloadFilesInWorkPath({
|
||||
workPath,
|
||||
@@ -91,8 +90,8 @@ export const build = async ({
|
||||
console.log('Installing required dependencies...');
|
||||
|
||||
await installRequirement({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
pythonPath: pythonVersion.pythonPath,
|
||||
pipPath: pythonVersion.pipPath,
|
||||
dependency: 'werkzeug',
|
||||
version: '1.0.1',
|
||||
workPath,
|
||||
@@ -114,25 +113,10 @@ export const build = async ({
|
||||
try {
|
||||
const json = await readFile(join(pipfileLockDir, 'Pipfile.lock'), 'utf8');
|
||||
const obj = JSON.parse(json);
|
||||
const version = obj?._meta?.requires?.python_version;
|
||||
if (!meta.isDev) {
|
||||
if (version === '3.6') {
|
||||
pipPath = 'pip3.6';
|
||||
pythonPath = 'python3.6';
|
||||
pythonRuntime = 'python3.6';
|
||||
console.warn(
|
||||
`Warning: Python version "${version}" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade. http://vercel.link/python-version`
|
||||
);
|
||||
} else if (version === '3.9') {
|
||||
pipPath = 'pip3.9';
|
||||
pythonPath = 'python3.9';
|
||||
pythonRuntime = 'python3.9';
|
||||
} else {
|
||||
console.warn(
|
||||
`Warning: Invalid Python version "${version}" detected in Pipfile.lock will be ignored. http://vercel.link/python-version`
|
||||
);
|
||||
}
|
||||
}
|
||||
pythonVersion = getSupportedPythonVersion({
|
||||
isDev: meta.isDev,
|
||||
pipLockPythonVersion: obj?._meta?.requires?.python_version,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new NowBuildError({
|
||||
code: 'INVALID_PIPFILE_LOCK',
|
||||
@@ -146,8 +130,8 @@ export const build = async ({
|
||||
// it into a separate folder.
|
||||
const tempDir = await getWriteableDirectory();
|
||||
await installRequirement({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
pythonPath: pythonVersion.pythonPath,
|
||||
pipPath: pythonVersion.pipPath,
|
||||
dependency: 'pipfile-requirements',
|
||||
version: '0.3.0',
|
||||
workPath: tempDir,
|
||||
@@ -169,8 +153,8 @@ export const build = async ({
|
||||
debug('Found local "requirements.txt"');
|
||||
const requirementsTxtPath = fsFiles[requirementsTxt].fsPath;
|
||||
await installRequirementsFile({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
pythonPath: pythonVersion.pythonPath,
|
||||
pipPath: pythonVersion.pipPath,
|
||||
filePath: requirementsTxtPath,
|
||||
workPath,
|
||||
meta,
|
||||
@@ -179,8 +163,8 @@ export const build = async ({
|
||||
debug('Found global "requirements.txt"');
|
||||
const requirementsTxtPath = fsFiles['requirements.txt'].fsPath;
|
||||
await installRequirementsFile({
|
||||
pythonPath,
|
||||
pipPath,
|
||||
pythonPath: pythonVersion.pythonPath,
|
||||
pipPath: pythonVersion.pipPath,
|
||||
filePath: requirementsTxtPath,
|
||||
workPath,
|
||||
meta,
|
||||
@@ -205,9 +189,6 @@ export const build = async ({
|
||||
|
||||
await writeFile(join(workPath, `${handlerPyFilename}.py`), handlerPyContents);
|
||||
|
||||
// Use the system-installed version of `python3` when running via `vercel dev`
|
||||
const runtime = meta.isDev ? 'python3' : pythonRuntime;
|
||||
|
||||
const globOptions: GlobOptions = {
|
||||
cwd: workPath,
|
||||
ignore:
|
||||
@@ -219,7 +200,7 @@ export const build = async ({
|
||||
const lambda = await createLambda({
|
||||
files: await glob('**', globOptions),
|
||||
handler: `${handlerPyFilename}.vc_handler`,
|
||||
runtime,
|
||||
runtime: pythonVersion.runtime,
|
||||
environment: {},
|
||||
});
|
||||
|
||||
|
||||
95
packages/python/src/version.ts
Normal file
95
packages/python/src/version.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
|
||||
interface PythonVersion {
|
||||
version: string;
|
||||
pipPath: string;
|
||||
pythonPath: string;
|
||||
runtime: string;
|
||||
discontinueDate?: Date;
|
||||
}
|
||||
|
||||
// The order must be most recent first
|
||||
const allOptions: PythonVersion[] = [
|
||||
{
|
||||
version: '3.9',
|
||||
pipPath: 'pip3.9',
|
||||
pythonPath: 'python3.9',
|
||||
runtime: 'python3.9',
|
||||
},
|
||||
{
|
||||
version: '3.6',
|
||||
pipPath: 'pip3.6',
|
||||
pythonPath: 'python3.6',
|
||||
runtime: 'python3.6',
|
||||
discontinueDate: new Date('2022-07-18'),
|
||||
},
|
||||
];
|
||||
|
||||
const upstreamProvider =
|
||||
'This change is the result of a decision made by an upstream infrastructure provider (AWS)';
|
||||
|
||||
function getDevPythonVersion(): PythonVersion {
|
||||
// Use the system-installed version of `python3` when running `vercel dev`
|
||||
return {
|
||||
version: '3',
|
||||
pipPath: 'pip3',
|
||||
pythonPath: 'python3',
|
||||
runtime: 'python3',
|
||||
};
|
||||
}
|
||||
export function getLatestPythonVersion({
|
||||
isDev,
|
||||
}: {
|
||||
isDev?: boolean;
|
||||
}): PythonVersion {
|
||||
if (isDev) {
|
||||
return getDevPythonVersion();
|
||||
}
|
||||
return allOptions[0];
|
||||
}
|
||||
|
||||
export function getSupportedPythonVersion({
|
||||
isDev,
|
||||
pipLockPythonVersion,
|
||||
}: {
|
||||
isDev?: boolean;
|
||||
pipLockPythonVersion: string | undefined;
|
||||
}): PythonVersion {
|
||||
if (isDev) {
|
||||
return getDevPythonVersion();
|
||||
}
|
||||
let selection = getLatestPythonVersion({ isDev: false });
|
||||
|
||||
if (typeof pipLockPythonVersion === 'string') {
|
||||
const found = allOptions.find(o => o.version === pipLockPythonVersion);
|
||||
if (found) {
|
||||
selection = found;
|
||||
} else {
|
||||
console.warn(
|
||||
`Warning: Python version "${pipLockPythonVersion}" detected in Pipfile.lock is invalid and will be ignored. http://vercel.link/python-version`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDiscontinued(selection)) {
|
||||
throw new NowBuildError({
|
||||
code: 'BUILD_UTILS_PYTHON_VERSION_DISCONTINUED',
|
||||
link: 'http://vercel.link/python-version',
|
||||
message: `Python version "${selection.version}" detected in Pipfile.lock is discontinued and must be upgraded. ${upstreamProvider}.`,
|
||||
});
|
||||
}
|
||||
|
||||
if (selection.discontinueDate) {
|
||||
const d = selection.discontinueDate.toISOString().split('T')[0];
|
||||
console.warn(
|
||||
`Error: Python version "${selection.version}" detected in Pipfile.lock is deprecated. Deployments created on or after ${d} will fail to build. ${upstreamProvider}. http://vercel.link/python-version`
|
||||
);
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
function isDiscontinued({ discontinueDate }: PythonVersion): boolean {
|
||||
const today = Date.now();
|
||||
return discontinueDate !== undefined && discontinueDate.getTime() <= today;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "wsgi:RANDOMNESS_PLACEHOLDER",
|
||||
"logMustContain": "Warning: Python version \"3.6\" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade."
|
||||
"logMustContain": "Python version \"3.6\" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "RANDOMNESS_PLACEHOLDER:env",
|
||||
"logMustContain": "Warning: Python version \"3.6\" detected in Pipfile.lock will reach End-Of-Life December 2021. Please upgrade."
|
||||
"logMustContain": "Python version \"3.6\" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "pip:RANDOMNESS_PLACEHOLDER",
|
||||
"logMustContain": "Warning: Invalid Python version \"3.7\" detected in Pipfile.lock will be ignored."
|
||||
"logMustContain": "Python version \"3.7\" detected in Pipfile.lock is invalid and will be ignored."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const {
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
const buildUtilsUrl = '@canary';
|
||||
let builderUrl;
|
||||
let builderUrl: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const builderPath = path.resolve(__dirname, '..');
|
||||
4
packages/python/test/tsconfig.json
vendored
Normal file
4
packages/python/test/tsconfig.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["*.test.ts"]
|
||||
}
|
||||
75
packages/python/test/unit.test.ts
vendored
Normal file
75
packages/python/test/unit.test.ts
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import { getSupportedPythonVersion } from '../src/version';
|
||||
|
||||
let warningMessages: string[];
|
||||
const originalConsoleWarn = console.warn;
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
beforeEach(() => {
|
||||
warningMessages = [];
|
||||
console.warn = m => {
|
||||
warningMessages.push(m);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.warn = originalConsoleWarn;
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
it('should only match supported versions, otherwise throw an error', async () => {
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '3.9' })
|
||||
).toHaveProperty('runtime', 'python3.9');
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
|
||||
).toHaveProperty('runtime', 'python3.6');
|
||||
});
|
||||
|
||||
it('should ignore minor version in vercel dev', async () => {
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '3.9', isDev: true })
|
||||
).toHaveProperty('runtime', 'python3');
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '3.6', isDev: true })
|
||||
).toHaveProperty('runtime', 'python3');
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '999', isDev: true })
|
||||
).toHaveProperty('runtime', 'python3');
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should select latest version when no Piplock detected', async () => {
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: undefined })
|
||||
).toHaveProperty('runtime', 'python3.9');
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should select latest version and warn when invalid Piplock detected', async () => {
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '999' })
|
||||
).toHaveProperty('runtime', 'python3.9');
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Warning: Python version "999" detected in Pipfile.lock is invalid and will be ignored. http://vercel.link/python-version',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw for discontinued versions', async () => {
|
||||
global.Date.now = () => new Date('2022-07-31').getTime();
|
||||
expect(() =>
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
|
||||
).toThrow(
|
||||
'Python version "3.6" detected in Pipfile.lock is discontinued and must be upgraded. This change is the result of a decision made by an upstream infrastructure provider (AWS).'
|
||||
);
|
||||
expect(warningMessages).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
global.Date.now = () => new Date('2021-07-01').getTime();
|
||||
|
||||
expect(
|
||||
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
|
||||
).toHaveProperty('runtime', 'python3.6');
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Error: Python version "3.6" detected in Pipfile.lock is deprecated. Deployments created on or after 2022-07-18 will fail to build. This change is the result of a decision made by an upstream infrastructure provider (AWS). http://vercel.link/python-version',
|
||||
]);
|
||||
});
|
||||
@@ -11,8 +11,10 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"types": ["node"],
|
||||
"types": ["node", "jest"],
|
||||
"strict": true,
|
||||
"target": "es2018"
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user