Skip to content

Commit c13df5f

Browse files
committed
feat: Added venv resource
1 parent acd286e commit c13df5f

File tree

4 files changed

+178
-1
lines changed

4 files changed

+178
-1
lines changed

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { NvmResource } from './resources/node/nvm/nvm.js';
1818
import { Pnpm } from './resources/node/pnpm/pnpm.js';
1919
import { PgcliResource } from './resources/pgcli/pgcli.js';
2020
import { PyenvResource } from './resources/python/pyenv/pyenv.js';
21+
import { VenvProject } from './resources/python/venv/venv-project.js';
2122
import { Virtualenv } from './resources/python/virtualenv/virtualenv.js';
2223
import { VirtualenvProject } from './resources/python/virtualenv/virtualenv-project.js';
2324
import { ActionResource } from './resources/scripting/action.js';
@@ -63,6 +64,7 @@ runPlugin(Plugin.create(
6364
new Virtualenv(),
6465
new VirtualenvProject(),
6566
new Pnpm(),
66-
new WaitGithubSshKey()
67+
new WaitGithubSshKey(),
68+
new VenvProject(),
6769
])
6870
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/virtualenv-project.json",
4+
"title": "Venv project resource",
5+
"type": "object",
6+
"description": "Install and manage local packages for a project with venv",
7+
"properties": {
8+
"envDir": {
9+
"type": "string",
10+
"description": "A directory to create the environment in."
11+
},
12+
"systemSitePackages": {
13+
"type": "boolean",
14+
"description": "Give the virtual environment access to the system site-packages dir."
15+
},
16+
"symlinks": {
17+
"type": "boolean",
18+
"description": "Try to use symlinks rather than copies, when symlinks are not the default for the platform."
19+
},
20+
"copies": {
21+
"type": "boolean",
22+
"description": "Delete the contents of the environment directory if it already exists, before environment creation."
23+
},
24+
"clear": {
25+
"type": "boolean",
26+
"description": "Try to use symlinks rather than copies (default: true)."
27+
},
28+
"upgrade": {
29+
"type": "boolean",
30+
"description": "Upgrade the environment directory to use this version of Python, assuming Python has been upgraded in-place."
31+
},
32+
"withoutPip": {
33+
"type": "boolean",
34+
"description": "Skips installing or upgrading pip in the virtual environment (pip is bootstrapped by default)."
35+
},
36+
"prompt": {
37+
"type": "string",
38+
"description": "Provides an alternative prompt prefix for this environment."
39+
},
40+
"upgradeDeps": {
41+
"type": "boolean",
42+
"description": "Upgrade core dependencies: pip setuptools to the latest version in PyPI."
43+
},
44+
"cwd": {
45+
"type": "string",
46+
"description": "The cwd to create virtualenv from. This allows a relative path to be used for dest."
47+
},
48+
"automaticallyInstallRequirementsTxt": {
49+
"type": "boolean",
50+
"description": "If an requirements.txt is available in the cwd, automatically install it when a virtual env is first created."
51+
}
52+
},
53+
"additionalProperties": false,
54+
"required": ["envDir"]
55+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
CreatePlan,
3+
DestroyPlan,
4+
Resource,
5+
ResourceSettings,
6+
} from 'codify-plugin-lib';
7+
import { ResourceConfig } from 'codify-schemas';
8+
import fs from 'node:fs/promises';
9+
import path from 'node:path';
10+
11+
import { codifySpawn } from '../../../utils/codify-spawn.js';
12+
import { FileUtils } from '../../../utils/file-utils.js';
13+
import schema from './venv-project-schema.json';
14+
15+
export interface VenvProjectConfig extends ResourceConfig {
16+
envDir: string;
17+
systemSitePackages?: boolean;
18+
symlinks?: boolean;
19+
copies?: boolean;
20+
clear?: boolean;
21+
upgrade?: boolean;
22+
withoutPip?: boolean;
23+
prompt?: string;
24+
upgradeDeps?: boolean;
25+
cwd?: string;
26+
automaticallyInstallRequirementsTxt?: boolean;
27+
}
28+
29+
export class VenvProject extends Resource<VenvProjectConfig> {
30+
31+
getSettings(): ResourceSettings<VenvProjectConfig> {
32+
return {
33+
id: 'venv-project',
34+
schema,
35+
parameterSettings: {
36+
envDir: { type: 'directory' },
37+
systemSitePackages: { type: 'boolean', setting: true },
38+
symlinks: { type: 'boolean', setting: true },
39+
copies: { type: 'boolean', setting: true },
40+
upgrade: { type: 'boolean', setting: true },
41+
withoutPip: { type: 'boolean', setting: true },
42+
prompt: { type: 'string', setting: true },
43+
upgradeDeps: { type: 'boolean', setting: true },
44+
cwd: { type: 'directory', setting: true },
45+
automaticallyInstallRequirementsTxt: { type: 'boolean', setting: true },
46+
},
47+
allowMultiple: {
48+
identifyingParameters: ['envDir'],
49+
},
50+
dependencies: ['homebrew', 'pyenv', 'git-repository']
51+
}
52+
}
53+
54+
async refresh(parameters: Partial<VenvProjectConfig>): Promise<Partial<VenvProjectConfig> | Partial<VenvProjectConfig>[] | null> {
55+
const dir = parameters.cwd
56+
? path.join(parameters.cwd, parameters.envDir!)
57+
: parameters.envDir!;
58+
59+
if (!(await FileUtils.exists(dir))) {
60+
return null;
61+
}
62+
63+
if (!(await FileUtils.exists(path.join(dir, 'pyvenv.cfg')))) {
64+
return null;
65+
}
66+
67+
return parameters;
68+
}
69+
70+
async create(plan: CreatePlan<VenvProjectConfig>): Promise<void> {
71+
const desired = plan.desiredConfig;
72+
73+
const command = 'python -m venv ' +
74+
(desired.systemSitePackages ? `--system-site-packages=${desired.systemSitePackages} ` : '') +
75+
(desired.symlinks ? '--symlinks ' : '') +
76+
(desired.copies ? '--copies ' : '') +
77+
(desired.clear ? '--clear ' : '') +
78+
(desired.upgrade ? '--upgrade ' : '') +
79+
(desired.withoutPip ? '--withoutPip ' : '') +
80+
(desired.prompt ? `--prompt ${desired.prompt} ` : '') +
81+
(desired.upgradeDeps ? '--upgradeDeps ' : '') +
82+
desired.envDir;
83+
84+
await codifySpawn(command, { cwd: desired.cwd ?? undefined });
85+
86+
if (desired.automaticallyInstallRequirementsTxt) {
87+
await codifySpawn(`source ${desired.envDir}/bin/activate; pip install -r requirements.txt`, { cwd: desired.cwd });
88+
}
89+
}
90+
91+
async destroy(plan: DestroyPlan<VenvProjectConfig>): Promise<void> {
92+
const current = plan.currentConfig;
93+
94+
const dir = current.cwd
95+
? path.join(current.cwd, current.envDir!)
96+
: current.envDir!;
97+
98+
await fs.rm(dir, { recursive: true, force: true });
99+
}
100+
}

test/python/venv-project.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, it } from 'vitest';
2+
import { PluginTester } from 'codify-plugin-test';
3+
import path from 'node:path';
4+
import fs from 'node:fs/promises';
5+
6+
describe('Virtualenv project tests', () => {
7+
const pluginPath = path.resolve('./src/index.ts');
8+
9+
it('Can install and uninstall a virtualenv directory', { timeout: 300000 }, async () => {
10+
await fs.mkdir('Projects/python-project', { recursive: true });
11+
await fs.writeFile('Projects/python-project/requirements.txt', 'ffmpeg==1.4')
12+
13+
console.log(await fs.readdir('Projects/python-project'));
14+
15+
await PluginTester.fullTest(pluginPath, [
16+
{ type: 'pyenv', pythonVersions: ['3.11'], global: '3.11' },
17+
{ type: 'venv-project', envDir: '.venv', cwd: 'Projects/python-project', automaticallyInstallRequirementsTxt: true },
18+
])
19+
})
20+
})

0 commit comments

Comments
 (0)