motia 0.6.4-beta.131-595093 → 0.6.4-beta.131-211436

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/cjs/cloud/build/builders/archiver.d.ts +1 -0
  2. package/dist/cjs/cloud/build/builders/archiver.js +24 -0
  3. package/dist/cjs/cloud/build/builders/python/index.d.ts +5 -6
  4. package/dist/cjs/cloud/build/builders/python/index.js +56 -101
  5. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts +1 -0
  6. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.js +24 -0
  7. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts +1 -0
  8. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.js +36 -0
  9. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.d.ts +1 -0
  10. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.js +70 -0
  11. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts +1 -0
  12. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/read-requirements.test.js +116 -0
  13. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.d.ts +1 -0
  14. package/dist/cjs/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.js +29 -0
  15. package/dist/cjs/cloud/build/builders/python/python-data/constants.d.ts +1 -0
  16. package/dist/cjs/cloud/build/builders/python/python-data/constants.js +110 -0
  17. package/dist/cjs/cloud/build/builders/python/python-data/convert-import-path.d.ts +1 -0
  18. package/dist/cjs/cloud/build/builders/python/python-data/convert-import-path.js +24 -0
  19. package/dist/cjs/cloud/build/builders/python/python-data/extract-python-data.d.ts +7 -0
  20. package/dist/cjs/cloud/build/builders/python/python-data/extract-python-data.js +24 -0
  21. package/dist/cjs/cloud/build/builders/python/python-data/get-dependencies-from-file.d.ts +7 -0
  22. package/dist/cjs/cloud/build/builders/python/python-data/get-dependencies-from-file.js +71 -0
  23. package/dist/cjs/cloud/build/builders/python/python-data/python-errors.d.ts +13 -0
  24. package/dist/cjs/cloud/build/builders/python/python-data/python-errors.js +28 -0
  25. package/dist/cjs/cloud/build/builders/python/python-data/read-requirements.d.ts +15 -0
  26. package/dist/cjs/cloud/build/builders/python/python-data/read-requirements.js +32 -0
  27. package/dist/cjs/cloud/build/builders/python/python-data/resolve-dep-name.d.ts +6 -0
  28. package/dist/cjs/cloud/build/builders/python/python-data/resolve-dep-name.js +53 -0
  29. package/dist/cjs/cloud/build/builders/python/python-data/traverse-tree.d.ts +7 -0
  30. package/dist/cjs/cloud/build/builders/python/python-data/traverse-tree.js +60 -0
  31. package/dist/cjs/cloud/build/builders/python/router_template.py +1 -4
  32. package/dist/cjs/cloud/build/builders/python/uv-packager.d.ts +2 -4
  33. package/dist/cjs/cloud/build/builders/python/uv-packager.js +20 -72
  34. package/dist/cjs/generate-locked-data.d.ts +1 -0
  35. package/dist/cjs/generate-locked-data.js +11 -6
  36. package/dist/cjs/generate-types.js +3 -8
  37. package/dist/cjs/utils/activate-python-env.d.ts +1 -0
  38. package/dist/cjs/utils/activate-python-env.js +9 -4
  39. package/dist/esm/cloud/build/builders/archiver.d.ts +1 -0
  40. package/dist/esm/cloud/build/builders/archiver.js +24 -0
  41. package/dist/esm/cloud/build/builders/python/index.d.ts +5 -6
  42. package/dist/esm/cloud/build/builders/python/index.js +56 -101
  43. package/dist/esm/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts +1 -0
  44. package/dist/esm/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.js +22 -0
  45. package/dist/esm/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts +1 -0
  46. package/dist/esm/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.js +31 -0
  47. package/dist/esm/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.d.ts +1 -0
  48. package/dist/esm/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.js +68 -0
  49. package/dist/esm/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts +1 -0
  50. package/dist/esm/cloud/build/builders/python/python-data/__tests__/read-requirements.test.js +111 -0
  51. package/dist/esm/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.d.ts +1 -0
  52. package/dist/esm/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.js +24 -0
  53. package/dist/esm/cloud/build/builders/python/python-data/constants.d.ts +1 -0
  54. package/dist/esm/cloud/build/builders/python/python-data/constants.js +107 -0
  55. package/dist/esm/cloud/build/builders/python/python-data/convert-import-path.d.ts +1 -0
  56. package/dist/esm/cloud/build/builders/python/python-data/convert-import-path.js +17 -0
  57. package/dist/esm/cloud/build/builders/python/python-data/extract-python-data.d.ts +7 -0
  58. package/dist/esm/cloud/build/builders/python/python-data/extract-python-data.js +20 -0
  59. package/dist/esm/cloud/build/builders/python/python-data/get-dependencies-from-file.d.ts +7 -0
  60. package/dist/esm/cloud/build/builders/python/python-data/get-dependencies-from-file.js +67 -0
  61. package/dist/esm/cloud/build/builders/python/python-data/python-errors.d.ts +13 -0
  62. package/dist/esm/cloud/build/builders/python/python-data/python-errors.js +21 -0
  63. package/dist/esm/cloud/build/builders/python/python-data/read-requirements.d.ts +15 -0
  64. package/dist/esm/cloud/build/builders/python/python-data/read-requirements.js +25 -0
  65. package/dist/esm/cloud/build/builders/python/python-data/resolve-dep-name.d.ts +6 -0
  66. package/dist/esm/cloud/build/builders/python/python-data/resolve-dep-name.js +46 -0
  67. package/dist/esm/cloud/build/builders/python/python-data/traverse-tree.d.ts +7 -0
  68. package/dist/esm/cloud/build/builders/python/python-data/traverse-tree.js +53 -0
  69. package/dist/esm/cloud/build/builders/python/router_template.py +1 -4
  70. package/dist/esm/cloud/build/builders/python/uv-packager.d.ts +2 -4
  71. package/dist/esm/cloud/build/builders/python/uv-packager.js +20 -72
  72. package/dist/esm/generate-locked-data.d.ts +1 -0
  73. package/dist/esm/generate-locked-data.js +9 -5
  74. package/dist/esm/generate-types.js +3 -5
  75. package/dist/esm/utils/activate-python-env.d.ts +1 -0
  76. package/dist/esm/utils/activate-python-env.js +7 -3
  77. package/dist/types/cloud/build/builders/archiver.d.ts +1 -0
  78. package/dist/types/cloud/build/builders/python/index.d.ts +5 -6
  79. package/dist/types/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts +1 -0
  80. package/dist/types/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts +1 -0
  81. package/dist/types/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.d.ts +1 -0
  82. package/dist/types/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts +1 -0
  83. package/dist/types/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.d.ts +1 -0
  84. package/dist/types/cloud/build/builders/python/python-data/constants.d.ts +1 -0
  85. package/dist/types/cloud/build/builders/python/python-data/convert-import-path.d.ts +1 -0
  86. package/dist/types/cloud/build/builders/python/python-data/extract-python-data.d.ts +7 -0
  87. package/dist/types/cloud/build/builders/python/python-data/get-dependencies-from-file.d.ts +7 -0
  88. package/dist/types/cloud/build/builders/python/python-data/python-errors.d.ts +13 -0
  89. package/dist/types/cloud/build/builders/python/python-data/read-requirements.d.ts +15 -0
  90. package/dist/types/cloud/build/builders/python/python-data/resolve-dep-name.d.ts +6 -0
  91. package/dist/types/cloud/build/builders/python/python-data/traverse-tree.d.ts +7 -0
  92. package/dist/types/cloud/build/builders/python/uv-packager.d.ts +2 -4
  93. package/dist/types/generate-locked-data.d.ts +1 -0
  94. package/dist/types/utils/activate-python-env.d.ts +1 -0
  95. package/package.json +7 -4
  96. package/dist/cjs/cloud/build/builders/python/package-copier.d.ts +0 -13
  97. package/dist/cjs/cloud/build/builders/python/package-copier.js +0 -38
  98. package/dist/cjs/cloud/build/builders/python/package-handler.d.ts +0 -27
  99. package/dist/cjs/cloud/build/builders/python/package-handler.js +0 -209
  100. package/dist/cjs/cloud/build/builders/python/python-dependency-analyzer.d.ts +0 -31
  101. package/dist/cjs/cloud/build/builders/python/python-dependency-analyzer.js +0 -348
  102. package/dist/esm/cloud/build/builders/python/package-copier.d.ts +0 -13
  103. package/dist/esm/cloud/build/builders/python/package-copier.js +0 -31
  104. package/dist/esm/cloud/build/builders/python/package-handler.d.ts +0 -27
  105. package/dist/esm/cloud/build/builders/python/package-handler.js +0 -202
  106. package/dist/esm/cloud/build/builders/python/python-dependency-analyzer.d.ts +0 -31
  107. package/dist/esm/cloud/build/builders/python/python-dependency-analyzer.js +0 -341
  108. package/dist/types/cloud/build/builders/python/package-copier.d.ts +0 -13
  109. package/dist/types/cloud/build/builders/python/package-handler.d.ts +0 -27
  110. package/dist/types/cloud/build/builders/python/python-dependency-analyzer.d.ts +0 -31
@@ -8,6 +8,7 @@ export declare class Archiver {
8
8
  private readonly outputStream;
9
9
  private uncompressedSize;
10
10
  constructor(filePath: string);
11
+ appendDirectory(sourcePath: string, targetPath: string): void;
11
12
  append(stream: fs.ReadStream | string, filePath: string): void;
12
13
  finalize(): Promise<ArchiveResult>;
13
14
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Archiver = void 0;
7
7
  const archiver_1 = __importDefault(require("archiver"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
9
10
  class Archiver {
10
11
  constructor(filePath) {
11
12
  this.uncompressedSize = 0;
@@ -13,6 +14,29 @@ class Archiver {
13
14
  this.outputStream = fs_1.default.createWriteStream(filePath);
14
15
  this.archive.pipe(this.outputStream);
15
16
  }
17
+ appendDirectory(sourcePath, targetPath) {
18
+ try {
19
+ const items = fs_1.default.readdirSync(sourcePath);
20
+ for (const item of items) {
21
+ const fullPath = path_1.default.join(sourcePath, item);
22
+ try {
23
+ const stat = fs_1.default.statSync(fullPath);
24
+ if (stat.isDirectory()) {
25
+ this.appendDirectory(fullPath, path_1.default.join(targetPath, item));
26
+ }
27
+ else {
28
+ this.append(fs_1.default.createReadStream(fullPath), targetPath ? path_1.default.join(targetPath, item) : item);
29
+ }
30
+ }
31
+ catch (_error) {
32
+ // Ignore individual file errors
33
+ }
34
+ }
35
+ }
36
+ catch (_error) {
37
+ // Ignore directory read errors
38
+ }
39
+ }
16
40
  append(stream, filePath) {
17
41
  // Track uncompressed size
18
42
  if (typeof stream === 'string') {
@@ -1,17 +1,16 @@
1
1
  import { ApiRouteConfig, Step } from '@motiadev/core';
2
- import { Builder, RouterBuildResult, StepBuilder } from '../../builder';
3
2
  import { BuildListener } from '../../../new-deployment/listeners/listener.types';
3
+ import { Builder, RouterBuildResult, StepBuilder } from '../../builder';
4
4
  export declare class PythonBuilder implements StepBuilder {
5
5
  private readonly builder;
6
6
  private readonly listener;
7
- private packageHandler;
7
+ private packager;
8
8
  constructor(builder: Builder, listener: BuildListener);
9
+ private getRequirements;
9
10
  buildApiSteps(steps: Step<ApiRouteConfig>[]): Promise<RouterBuildResult>;
10
11
  build(step: Step): Promise<void>;
11
- private addStepToArchive;
12
- private findInternalFiles;
13
- private resolveModulePaths;
14
- private normalizeStepPath;
12
+ private generatePackage;
13
+ private cleanup;
15
14
  private createRouterTemplate;
16
15
  private getModuleName;
17
16
  }
@@ -6,40 +6,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PythonBuilder = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const activate_python_env_1 = require("../../../../utils/activate-python-env");
10
+ const constants_1 = require("../../../new-deployment/constants");
9
11
  const archiver_1 = require("../archiver");
10
12
  const include_static_files_1 = require("../include-static-files");
11
- const constants_1 = require("../../../new-deployment/constants");
12
- const activate_python_env_1 = require("../../../../utils/activate-python-env");
13
- const package_handler_1 = require("./package-handler");
13
+ const extract_python_data_1 = require("./python-data/extract-python-data");
14
+ const read_requirements_1 = require("./python-data/read-requirements");
15
+ const resolve_dep_name_1 = require("./python-data/resolve-dep-name");
16
+ const uv_packager_1 = require("./uv-packager");
14
17
  class PythonBuilder {
15
18
  constructor(builder, listener) {
16
19
  this.builder = builder;
17
20
  this.listener = listener;
18
21
  (0, activate_python_env_1.activatePythonVenv)({ baseDir: this.builder.projectDir });
19
- this.packageHandler = new package_handler_1.PackageHandler(this.builder.projectDir);
22
+ this.packager = new uv_packager_1.UvPackager();
23
+ }
24
+ getRequirements() {
25
+ const requirementsFile = path_1.default.join(this.builder.projectDir, 'requirements.txt');
26
+ const depNames = Object.keys((0, read_requirements_1.readRequirements)(requirementsFile, (name) => ({ name, importName: name })));
27
+ const sitePackagesPath = (0, activate_python_env_1.getSitePackagesPath)({ baseDir: this.builder.projectDir });
28
+ const mapper = (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesPath);
29
+ const describer = (name) => {
30
+ const [, to] = mapper.find(([from]) => from === name) ?? [];
31
+ return { name, importName: to ?? name };
32
+ };
33
+ return (0, read_requirements_1.readRequirements)(requirementsFile, describer);
20
34
  }
21
35
  async buildApiSteps(steps) {
22
36
  const zipName = 'router-python.zip';
23
37
  const archive = new archiver_1.Archiver(path_1.default.join(constants_1.distDir, zipName));
24
- let tempDirToCleanup;
38
+ const bundleDir = path_1.default.join(constants_1.distDir, 'python', 'router');
25
39
  try {
26
- // Collect all Python files for analysis
27
- const pythonFiles = steps.map((step) => step.filePath);
28
- // Build packages using the unified handler
29
- const buildResult = await this.packageHandler.buildPackages({
30
- pythonFiles,
31
- archive,
32
- projectDir: this.builder.projectDir,
33
- });
34
- tempDirToCleanup = buildResult.tempDirCreated;
35
- // Add all step files to archive
36
- for (const step of steps) {
37
- await this.addStepToArchive(step, archive);
38
- }
39
- // Add router template
40
+ fs_1.default.mkdirSync(bundleDir, { recursive: true });
40
41
  const routerTemplate = this.createRouterTemplate(steps);
41
42
  archive.append(routerTemplate, 'router.py');
42
- // Include static files
43
+ await this.generatePackage(bundleDir, 'router.py', archive, routerTemplate);
43
44
  (0, include_static_files_1.includeStaticFiles)(steps, this.builder, archive);
44
45
  const { compressedSize, uncompressedSize } = await archive.finalize();
45
46
  return { compressedSize, uncompressedSize, path: zipName };
@@ -48,34 +49,21 @@ class PythonBuilder {
48
49
  throw new Error(`Failed to build Python API router: ${error}`);
49
50
  }
50
51
  finally {
51
- this.packageHandler.cleanup(tempDirToCleanup);
52
+ this.cleanup(bundleDir);
52
53
  }
53
54
  }
54
55
  async build(step) {
55
- const entrypointPath = step.filePath.replace(this.builder.projectDir, '').replace(/\.step\.py$/, '_step.py');
56
+ const entrypointPath = step.filePath.replace(this.builder.projectDir, '');
56
57
  const bundlePath = path_1.default.join('python', entrypointPath.replace(/(.*)\.py$/, '$1.zip'));
58
+ const bundleDir = path_1.default.join(constants_1.distDir, 'python', entrypointPath.replace(/(.*)\.py$/, '$1'));
57
59
  const outfile = path_1.default.join(constants_1.distDir, bundlePath);
58
60
  this.builder.registerStep({ entrypointPath, bundlePath, step, type: 'python' });
59
61
  this.listener.onBuildStart(step);
60
- let tempDirToCleanup;
61
62
  try {
62
63
  fs_1.default.mkdirSync(path_1.default.dirname(outfile), { recursive: true });
64
+ fs_1.default.mkdirSync(bundleDir, { recursive: true });
63
65
  const archive = new archiver_1.Archiver(outfile);
64
- // Collect Python files for analysis (including internal files)
65
- const pythonFiles = [step.filePath];
66
- const internalFiles = await this.findInternalFiles(step.filePath);
67
- for (const file of internalFiles) {
68
- pythonFiles.push(path_1.default.join(this.builder.projectDir, file));
69
- }
70
- // Build packages using the unified handler
71
- const buildResult = await this.packageHandler.buildPackages({
72
- pythonFiles,
73
- archive,
74
- projectDir: this.builder.projectDir,
75
- });
76
- tempDirToCleanup = buildResult.tempDirCreated;
77
- // Add step file to archive
78
- await this.addStepToArchive(step, archive);
66
+ await this.generatePackage(bundleDir, entrypointPath, archive);
79
67
  // Include static files
80
68
  (0, include_static_files_1.includeStaticFiles)([step], this.builder, archive);
81
69
  const { compressedSize, uncompressedSize } = await archive.finalize();
@@ -87,93 +75,60 @@ class PythonBuilder {
87
75
  throw err;
88
76
  }
89
77
  finally {
90
- this.packageHandler.cleanup(tempDirToCleanup);
78
+ this.cleanup(bundleDir);
91
79
  }
92
80
  }
93
- async addStepToArchive(step, archive) {
94
- const normalizedPath = this.normalizeStepPath(step, false);
95
- archive.append(fs_1.default.createReadStream(step.filePath), normalizedPath);
96
- const internalFiles = await this.findInternalFiles(step.filePath);
97
- for (const file of internalFiles) {
98
- const fullPath = path_1.default.join(this.builder.projectDir, file);
99
- if (fs_1.default.existsSync(fullPath) && fullPath !== step.filePath) {
100
- const archivePath = file.replace(/\.step\.py$/, '_step.py');
101
- archive.append(fs_1.default.createReadStream(fullPath), archivePath);
81
+ async generatePackage(bundleDir, entrypointPath, archive, fileContent) {
82
+ const requirements = this.getRequirements();
83
+ const { externalDependencies, files } = (0, extract_python_data_1.extractPythonData)(this.builder.projectDir, entrypointPath, requirements, fileContent);
84
+ // move files
85
+ for (const file of files) {
86
+ fs_1.default.mkdirSync(path_1.default.dirname(path_1.default.join(bundleDir, file)), { recursive: true });
87
+ if (fileContent && file === entrypointPath) {
88
+ fs_1.default.writeFileSync(path_1.default.join(bundleDir, file), fileContent);
102
89
  }
90
+ else {
91
+ fs_1.default.copyFileSync(path_1.default.join(this.builder.projectDir, file), path_1.default.join(bundleDir, file));
92
+ }
93
+ }
94
+ const dependencies = Object.values(externalDependencies);
95
+ if (dependencies.length > 0) {
96
+ // create requirements.txt
97
+ fs_1.default.writeFileSync(path_1.default.join(bundleDir, 'requirements.txt'), Object.values(externalDependencies).join('\n'));
98
+ await this.packager.packageDependencies(bundleDir);
103
99
  }
100
+ // zip entire folder
101
+ archive.appendDirectory(bundleDir, '/');
104
102
  }
105
- async findInternalFiles(entryFile) {
106
- const files = [];
107
- const visited = new Set();
108
- const analyzeFile = (filePath) => {
109
- if (visited.has(filePath) || !fs_1.default.existsSync(filePath))
110
- return;
111
- visited.add(filePath);
112
- files.push(path_1.default.relative(this.builder.projectDir, filePath));
103
+ cleanup(bundleDir) {
104
+ if (bundleDir && fs_1.default.existsSync(bundleDir)) {
113
105
  try {
114
- const content = fs_1.default.readFileSync(filePath, 'utf-8');
115
- const importRegex = /^(?:from\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+import|import\s+([a-zA-Z_][a-zA-Z0-9_.]*))/gm;
116
- let match;
117
- while ((match = importRegex.exec(content)) !== null) {
118
- const moduleName = match[1] || match[2];
119
- this.resolveModulePaths(moduleName, path_1.default.dirname(filePath)).forEach((possiblePath) => {
120
- if (fs_1.default.existsSync(possiblePath)) {
121
- analyzeFile(possiblePath);
122
- }
123
- });
124
- }
106
+ fs_1.default.rmSync(bundleDir, { recursive: true, force: true });
125
107
  }
126
108
  catch (_error) {
127
- // Ignore file read/parse errors
109
+ // Ignore cleanup errors
128
110
  }
129
- };
130
- analyzeFile(entryFile);
131
- return files;
132
- }
133
- resolveModulePaths(moduleName, currentDir) {
134
- const parts = moduleName.split('.');
135
- const baseName = parts[0];
136
- const subPath = parts.length > 1 ? path_1.default.join(...parts) : baseName;
137
- return [
138
- path_1.default.join(currentDir, `${baseName}.py`),
139
- path_1.default.join(currentDir, baseName, '__init__.py'),
140
- path_1.default.join(currentDir, `${subPath}.py`),
141
- path_1.default.join(this.builder.projectDir, `${baseName}.py`),
142
- path_1.default.join(this.builder.projectDir, baseName, '__init__.py'),
143
- path_1.default.join(this.builder.projectDir, `${subPath}.py`),
144
- path_1.default.join(this.builder.projectDir, subPath + '.py'),
145
- path_1.default.join(this.builder.projectDir, subPath, '__init__.py'),
146
- ];
147
- }
148
- normalizeStepPath(step, normalizePythonModulePath) {
149
- let normalizedStepPath = step.filePath.replace(/[.]step.py$/, '_step.py').replace(`${this.builder.projectDir}/`, '');
150
- const pathParts = normalizedStepPath.split(path_1.default.sep).map((part) => part
151
- .replace(/[^a-zA-Z0-9._]/g, '_') // Replace any non-alphanumeric characters (except dots) with underscores
152
- .replace(/^_/, ''));
153
- normalizedStepPath = normalizePythonModulePath ? pathParts.join('.') : '/' + pathParts.join(path_1.default.sep);
154
- return normalizedStepPath;
111
+ }
155
112
  }
156
113
  createRouterTemplate(steps) {
157
114
  const imports = steps
158
- .map((step, index) => {
159
- const moduleName = this.getModuleName(step);
160
- return `route${index}_module = importlib.import_module('${moduleName}')`;
161
- })
115
+ .map((step, index) => `from ${this.getModuleName(step)} import handler as route${index}_handler, config as route${index}_config`)
162
116
  .join('\n');
163
117
  const routerPaths = steps
164
118
  .map((step, index) => {
165
119
  const method = step.config.method.toUpperCase();
166
120
  const path = step.config.path;
167
- return ` '${method} ${path}': RouterPath('${step.config.name}', '${step.config.method.toLowerCase()}', route${index}_module.handler, route${index}_module.config)`;
121
+ return ` '${method} ${path}': RouterPath('${step.config.name}', '${step.config.method.toLowerCase()}', route${index}_handler, route${index}_config)`;
168
122
  })
169
123
  .join(',\n');
170
124
  return fs_1.default
171
125
  .readFileSync(path_1.default.join(__dirname, 'router_template.py'), 'utf-8')
172
126
  .replace('# {{imports}}', imports)
173
- .replace('# {{router paths}}', routerPaths);
127
+ .replace(' # {{router paths}}', routerPaths);
174
128
  }
175
129
  getModuleName(step) {
176
- return this.normalizeStepPath(step, true).replace(/\.py$/, '').replace(/\//g, '.');
130
+ // return step path
131
+ return step.filePath.replace(this.builder.projectDir, '').substring(1).replace(/\.py$/, '').replace(/\//g, '.');
177
132
  }
178
133
  }
179
134
  exports.PythonBuilder = PythonBuilder;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const convert_import_path_1 = require("../convert-import-path");
4
+ describe('convertImportToPath', () => {
5
+ test('converts regular module imports', () => {
6
+ // Regular module imports
7
+ expect((0, convert_import_path_1.convertImportToPath)('mymodule')).toBe('mymodule');
8
+ expect((0, convert_import_path_1.convertImportToPath)('mymodule.submodule')).toBe('mymodule/submodule');
9
+ expect((0, convert_import_path_1.convertImportToPath)('package.module.submodule')).toBe('package/module/submodule');
10
+ });
11
+ test('converts relative imports', () => {
12
+ // Relative imports
13
+ expect((0, convert_import_path_1.convertImportToPath)('.module')).toBe('module');
14
+ expect((0, convert_import_path_1.convertImportToPath)('..module')).toBe('../module');
15
+ expect((0, convert_import_path_1.convertImportToPath)('...module')).toBe('../../module');
16
+ expect((0, convert_import_path_1.convertImportToPath)('..utils.database')).toBe('../utils/database');
17
+ });
18
+ test('handles edge cases', () => {
19
+ // Edge cases
20
+ expect((0, convert_import_path_1.convertImportToPath)('.')).toBe('.');
21
+ expect((0, convert_import_path_1.convertImportToPath)('..')).toBe('..');
22
+ expect((0, convert_import_path_1.convertImportToPath)('...')).toBe('../..');
23
+ });
24
+ });
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const glob_1 = require("glob");
8
+ const read_requirements_1 = require("../read-requirements");
9
+ const extract_python_data_1 = require("../extract-python-data");
10
+ const python_errors_1 = require("../python-errors");
11
+ const mockPackageDescriber = (name) => ({ name, importName: name });
12
+ describe('extractPythonData', () => {
13
+ test('extracts python data correctly', () => {
14
+ const rootDir = path_1.default.join(__dirname, './examples/example-1');
15
+ const requirements = (0, read_requirements_1.readRequirements)(path_1.default.join(rootDir, 'requirements.txt'), mockPackageDescriber);
16
+ const steps = (0, glob_1.globSync)('**/*_step.py', { absolute: false, cwd: path_1.default.join(rootDir, 'steps') });
17
+ for (const file of steps) {
18
+ const result = (0, extract_python_data_1.extractPythonData)(rootDir, `/steps/${file}`, requirements);
19
+ expect(result).toEqual({
20
+ standardLibDependencies: ['typing', 'enum'],
21
+ externalDependencies: { httpx: 'httpx>=0.28.1', pydantic: 'pydantic>=2.6.1' },
22
+ files: ['/steps/api_step.py', '/src/__init__.py', '/src/pet_store.py', '/src/types.py'],
23
+ });
24
+ }
25
+ });
26
+ test('extracts python data correctly with invalid dependency', () => {
27
+ const rootDir = path_1.default.join(__dirname, './examples/invalid-dependency');
28
+ const requirements = (0, read_requirements_1.readRequirements)(path_1.default.join(rootDir, 'requirements.txt'), mockPackageDescriber);
29
+ expect(() => (0, extract_python_data_1.extractPythonData)(rootDir, `/steps/api_step.py`, requirements)).toThrow(python_errors_1.PythonImportNotFoundError);
30
+ });
31
+ test('extracts python data correctly with compilation error', () => {
32
+ const rootDir = path_1.default.join(__dirname, './examples/compilation-error');
33
+ const requirements = (0, read_requirements_1.readRequirements)(path_1.default.join(rootDir, 'requirements.txt'), mockPackageDescriber);
34
+ expect(() => (0, extract_python_data_1.extractPythonData)(rootDir, `/steps/api_step.py`, requirements)).toThrow(new python_errors_1.PythonError("Compilation error: no viable alternative at input ':' at line 3:10 in /steps/api_step.py", '/steps/api_step.py'));
35
+ });
36
+ });
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const get_dependencies_from_file_1 = require("../get-dependencies-from-file");
4
+ describe('getDependenciesFromFile', () => {
5
+ test('categorizes dependencies correctly', () => {
6
+ const pythonCode = `
7
+ import os
8
+ import sys
9
+ from collections import defaultdict
10
+ import requests
11
+ from .local_module import something
12
+ from ..parent_module import other
13
+ `;
14
+ const requirements = {
15
+ requests: 'requests==2.25.1',
16
+ };
17
+ const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
18
+ expect(Array.from(deps.standardLibDependencies)).toEqual(['os', 'sys', 'collections']);
19
+ expect(Array.from(deps.externalDependencies)).toEqual(['requests']);
20
+ expect(Array.from(deps.projectDependencies)).toEqual(['.local_module', '..parent_module']);
21
+ });
22
+ test('handles relative imports correctly', () => {
23
+ const pythonCode = 'from ...utils.database import connect';
24
+ const requirements = {};
25
+ const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
26
+ expect(Array.from(deps.projectDependencies)).toEqual(['...utils.database']);
27
+ });
28
+ test('handles import statements with requirements dep', () => {
29
+ const pythonCode = `
30
+ import json
31
+ import numpy
32
+ import mypackage.submodule
33
+ `;
34
+ const requirements = {
35
+ numpy: 'numpy==1.21.0',
36
+ };
37
+ const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
38
+ expect(Array.from(deps.standardLibDependencies)).toEqual(['json']);
39
+ expect(Array.from(deps.externalDependencies)).toEqual(['numpy']);
40
+ expect(Array.from(deps.projectDependencies)).toEqual(['mypackage.submodule']);
41
+ });
42
+ test('handles import statements with requirements dep and alias', () => {
43
+ const pythonCode = `
44
+ import json
45
+ import numpy as np
46
+ import mypackage.submodule
47
+ `;
48
+ const requirements = {
49
+ numpy: 'numpy==1.21.0',
50
+ };
51
+ const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
52
+ expect(Array.from(deps.standardLibDependencies)).toEqual(['json']);
53
+ expect(Array.from(deps.externalDependencies)).toEqual(['numpy']);
54
+ expect(Array.from(deps.projectDependencies)).toEqual(['mypackage.submodule']);
55
+ });
56
+ test('handles empty file', () => {
57
+ const pythonCode = '';
58
+ const requirements = {};
59
+ const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
60
+ expect(deps.standardLibDependencies.size).toBe(0);
61
+ expect(deps.externalDependencies.size).toBe(0);
62
+ expect(deps.projectDependencies.size).toBe(0);
63
+ });
64
+ test('handles unknown external dependencies as project dependencies', () => {
65
+ const pythonCode = 'import unknown_package';
66
+ const requirements = {};
67
+ const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
68
+ expect(Array.from(deps.projectDependencies)).toEqual(['unknown_package']);
69
+ });
70
+ });
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const read_requirements_1 = require("../read-requirements");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = require("os");
10
+ describe('readRequirements', () => {
11
+ let tempFilePath;
12
+ const mockPackageDescriber = jest.fn((name) => ({ name, importName: name }));
13
+ beforeEach(() => {
14
+ // Create a temporary file path
15
+ tempFilePath = path_1.default.join((0, os_1.tmpdir)(), `requirements-${Date.now()}.txt`);
16
+ });
17
+ afterEach(() => {
18
+ // Clean up temporary file if it exists
19
+ if (fs_1.default.existsSync(tempFilePath)) {
20
+ fs_1.default.unlinkSync(tempFilePath);
21
+ }
22
+ });
23
+ test('reads basic requirements correctly', () => {
24
+ const content = `requests==2.25.1
25
+ numpy>=1.20.0
26
+ flask~=2.0`;
27
+ fs_1.default.writeFileSync(tempFilePath, content);
28
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
29
+ expect(Object.keys(requirements)).toHaveLength(3);
30
+ expect(requirements.requests).toBe('requests==2.25.1');
31
+ expect(requirements.numpy).toBe('numpy>=1.20.0');
32
+ expect(requirements.flask).toBe('flask~=2.0');
33
+ });
34
+ test('skips comments and empty lines', () => {
35
+ const content = `# This is a comment
36
+ requests==2.25.1
37
+
38
+ # Another comment
39
+ numpy>=1.20.0
40
+
41
+ flask~=2.0`;
42
+ fs_1.default.writeFileSync(tempFilePath, content);
43
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
44
+ expect(Object.keys(requirements)).toHaveLength(3);
45
+ expect(requirements.requests).toBe('requests==2.25.1');
46
+ expect(requirements.numpy).toBe('numpy>=1.20.0');
47
+ expect(requirements.flask).toBe('flask~=2.0');
48
+ });
49
+ test('handles complex package names and version specifiers', () => {
50
+ const content = `scikit-learn==1.0.2
51
+ python-dateutil==1.4
52
+ Django<4.0.0,>=3.2.0`;
53
+ fs_1.default.writeFileSync(tempFilePath, content);
54
+ const describer = jest.fn((name) => {
55
+ // mocking a describer resolution for these packages
56
+ // we're going to implement it separately
57
+ const map = {
58
+ 'scikit-learn': 'sklearn',
59
+ 'python-dateutil': 'dateutil',
60
+ Django: 'django',
61
+ };
62
+ return map[name] ? { name, importName: map[name] } : { name, importName: name };
63
+ });
64
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, describer);
65
+ expect(Object.keys(requirements)).toEqual(['sklearn', 'dateutil', 'django']);
66
+ expect(requirements['sklearn']).toBe('scikit-learn==1.0.2');
67
+ expect(requirements['dateutil']).toBe('python-dateutil==1.4');
68
+ expect(requirements['django']).toBe('Django<4.0.0,>=3.2.0');
69
+ });
70
+ test('handles requirements with extra dependencies', () => {
71
+ const content = `requests[security]==2.25.1
72
+ flask[async]==2.0.1`;
73
+ fs_1.default.writeFileSync(tempFilePath, content);
74
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
75
+ expect(Object.keys(requirements)).toHaveLength(2);
76
+ expect(requirements['requests']).toBe('requests[security]==2.25.1');
77
+ expect(requirements['flask']).toBe('flask[async]==2.0.1');
78
+ });
79
+ test('handles empty requirements file', () => {
80
+ const content = `# Only comments
81
+
82
+ # And empty lines`;
83
+ fs_1.default.writeFileSync(tempFilePath, content);
84
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
85
+ expect(Object.keys(requirements)).toHaveLength(0);
86
+ });
87
+ test('handles malformed lines gracefully', () => {
88
+ const content = `requests==2.25.1
89
+ # Valid package
90
+ numpy>=1.20.0
91
+ ==invalid-version-only`;
92
+ fs_1.default.writeFileSync(tempFilePath, content);
93
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
94
+ expect(Object.keys(requirements)).toHaveLength(2);
95
+ expect(requirements.requests).toBe('requests==2.25.1');
96
+ expect(requirements.numpy).toBe('numpy>=1.20.0');
97
+ });
98
+ test('handles mixed line endings', () => {
99
+ const content = 'requests==2.25.1\r\nnumpy>=1.20.0\nflask~=2.0';
100
+ fs_1.default.writeFileSync(tempFilePath, content);
101
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
102
+ expect(Object.keys(requirements)).toHaveLength(3);
103
+ expect(requirements.requests).toBe('requests==2.25.1');
104
+ expect(requirements.numpy).toBe('numpy>=1.20.0');
105
+ expect(requirements.flask).toBe('flask~=2.0');
106
+ });
107
+ test('handles requirements with no versions', () => {
108
+ const content = ['requests', 'numpy', 'flask'].join('\n');
109
+ fs_1.default.writeFileSync(tempFilePath, content);
110
+ const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
111
+ expect(Object.keys(requirements)).toHaveLength(3);
112
+ expect(requirements.requests).toBe('requests');
113
+ expect(requirements.numpy).toBe('numpy');
114
+ expect(requirements.flask).toBe('flask');
115
+ });
116
+ });
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const resolve_dep_name_1 = require("../resolve-dep-name");
8
+ const python_errors_1 = require("../python-errors");
9
+ const sitePackagesDir = path_1.default.join(__dirname, 'site-packages');
10
+ describe('resolveDepName', () => {
11
+ test('resolves dependency names correctly', () => {
12
+ const depNames = ['httpx'];
13
+ const result = (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesDir);
14
+ expect(result).toEqual([['httpx', 'httpx']]);
15
+ });
16
+ test('resolves multiple dependency names correctly', () => {
17
+ const depNames = ['httpx', 'scikit-learn', 'opencv-python'];
18
+ const result = (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesDir);
19
+ expect(result).toEqual([
20
+ ['httpx', 'httpx'],
21
+ ['scikit-learn', 'sklearn'],
22
+ ['opencv-python', 'cv2'],
23
+ ]);
24
+ });
25
+ test('should throw an error if the dependency is not found', () => {
26
+ const depNames = ['httpx', 'scikit-learn', 'opencv-python', 'pydantic'];
27
+ expect(() => (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesDir)).toThrow(new python_errors_1.PythonError('Could not find dependency name in site-packages: pydantic', 'pydantic'));
28
+ });
29
+ });
@@ -0,0 +1 @@
1
+ export declare const STANDARD_LIB_MODULES: Set<string>;