tywrap 0.2.1 → 0.3.0
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.
- package/README.md +65 -31
- package/dist/cli.js +49 -2
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts +11 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +43 -7
- package/dist/config/index.js.map +1 -1
- package/dist/core/analyzer.d.ts +1 -0
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +47 -12
- package/dist/core/analyzer.js.map +1 -1
- package/dist/core/annotation-parser.d.ts +8 -0
- package/dist/core/annotation-parser.d.ts.map +1 -0
- package/dist/core/annotation-parser.js +409 -0
- package/dist/core/annotation-parser.js.map +1 -0
- package/dist/core/discovery.d.ts +1 -0
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +9 -9
- package/dist/core/discovery.js.map +1 -1
- package/dist/core/generator.d.ts +9 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +738 -104
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts +9 -7
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +129 -44
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/bridge-core.d.ts +1 -0
- package/dist/runtime/bridge-core.d.ts.map +1 -1
- package/dist/runtime/bridge-core.js +52 -17
- package/dist/runtime/bridge-core.js.map +1 -1
- package/dist/runtime/bridge-protocol.d.ts +17 -0
- package/dist/runtime/bridge-protocol.d.ts.map +1 -1
- package/dist/runtime/bridge-protocol.js +97 -0
- package/dist/runtime/bridge-protocol.js.map +1 -1
- package/dist/runtime/errors.d.ts +10 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +9 -0
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/http-io.d.ts.map +1 -1
- package/dist/runtime/http-io.js.map +1 -1
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +6 -0
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +170 -32
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +1 -1
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +1 -1
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +4 -0
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +2 -0
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/process-io.d.ts +5 -0
- package/dist/runtime/process-io.d.ts.map +1 -1
- package/dist/runtime/process-io.js +64 -30
- package/dist/runtime/process-io.js.map +1 -1
- package/dist/runtime/pyodide-io.d.ts +4 -5
- package/dist/runtime/pyodide-io.d.ts.map +1 -1
- package/dist/runtime/pyodide-io.js +99 -37
- package/dist/runtime/pyodide-io.js.map +1 -1
- package/dist/runtime/safe-codec.d.ts +25 -3
- package/dist/runtime/safe-codec.d.ts.map +1 -1
- package/dist/runtime/safe-codec.js +231 -57
- package/dist/runtime/safe-codec.js.map +1 -1
- package/dist/runtime/transport.d.ts +6 -1
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js.map +1 -1
- package/dist/runtime/validators.d.ts +0 -11
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +13 -3
- package/dist/runtime/validators.js.map +1 -1
- package/dist/runtime/worker-pool.d.ts +12 -0
- package/dist/runtime/worker-pool.d.ts.map +1 -1
- package/dist/runtime/worker-pool.js +53 -5
- package/dist/runtime/worker-pool.js.map +1 -1
- package/dist/types/index.d.ts +51 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +227 -328
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +53 -5
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +10 -16
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/ir-cache.d.ts +28 -0
- package/dist/utils/ir-cache.d.ts.map +1 -0
- package/dist/utils/ir-cache.js +29 -0
- package/dist/utils/ir-cache.js.map +1 -0
- package/dist/utils/parallel-processor.d.ts +1 -1
- package/dist/utils/parallel-processor.d.ts.map +1 -1
- package/dist/utils/parallel-processor.js +111 -40
- package/dist/utils/parallel-processor.js.map +1 -1
- package/dist/utils/runtime.d.ts +5 -1
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +112 -18
- package/dist/utils/runtime.js.map +1 -1
- package/package.json +40 -18
- package/runtime/python_bridge.py +78 -12
- package/src/cli.ts +61 -2
- package/src/config/index.ts +63 -17
- package/src/core/analyzer.ts +52 -15
- package/src/core/annotation-parser.ts +500 -0
- package/src/core/discovery.ts +26 -12
- package/src/core/generator.ts +956 -116
- package/src/core/mapper.ts +158 -51
- package/src/index.ts +34 -5
- package/src/runtime/bridge-core.ts +58 -18
- package/src/runtime/bridge-protocol.ts +164 -0
- package/src/runtime/errors.ts +14 -0
- package/src/runtime/http-io.ts +2 -1
- package/src/runtime/http.ts +7 -0
- package/src/runtime/node.ts +264 -44
- package/src/runtime/optimized-node.ts +4 -1
- package/src/runtime/pooled-transport.ts +12 -1
- package/src/runtime/process-io.ts +83 -55
- package/src/runtime/pyodide-io.ts +110 -44
- package/src/runtime/safe-codec.ts +290 -82
- package/src/runtime/transport.ts +6 -1
- package/src/runtime/validators.ts +14 -3
- package/src/runtime/worker-pool.ts +67 -6
- package/src/types/global.d.ts +11 -1
- package/src/types/index.ts +63 -0
- package/src/tywrap.ts +279 -358
- package/src/utils/cache.ts +59 -5
- package/src/utils/codec.ts +11 -15
- package/src/utils/ir-cache.ts +51 -0
- package/src/utils/parallel-processor.ts +119 -43
- package/src/utils/runtime.ts +129 -18
package/dist/tywrap.js
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { CodeGenerator } from './core/generator.js';
|
|
5
5
|
import { TypeMapper } from './core/mapper.js';
|
|
6
|
+
import { parseAnnotationToPythonType } from './core/annotation-parser.js';
|
|
6
7
|
import { createConfig } from './config/index.js';
|
|
7
|
-
import { fsUtils, pathUtils, processUtils,
|
|
8
|
+
import { fsUtils, pathUtils, processUtils, isWindows } from './utils/runtime.js';
|
|
8
9
|
import { globalCache } from './utils/cache.js';
|
|
9
10
|
import { globalParallelProcessor } from './utils/parallel-processor.js';
|
|
10
11
|
import { resolvePythonExecutable } from './utils/python.js';
|
|
12
|
+
import { computeIrCacheFilename } from './utils/ir-cache.js';
|
|
13
|
+
const TYWRAP_IR_VERSION = '0.2.0';
|
|
11
14
|
// Collect unknown typing constructs encountered during annotation parsing (per-generate run)
|
|
12
15
|
let unknownTypeNamesCollector = new Map();
|
|
13
16
|
function recordUnknown(name) {
|
|
@@ -88,13 +91,14 @@ export async function generate(options, runOptions = {}) {
|
|
|
88
91
|
const modules = resolvedOptions.pythonModules ?? {};
|
|
89
92
|
for (const entry of Object.entries(modules)) {
|
|
90
93
|
const moduleKey = entry[0];
|
|
94
|
+
const moduleConfig = entry[1];
|
|
91
95
|
// reset collector for each module
|
|
92
96
|
unknownTypeNamesCollector = new Map();
|
|
93
97
|
// Prefer Python IR extractor over TS analyzer with optional cache
|
|
94
|
-
const cacheKey = await computeCacheKey(moduleKey,
|
|
98
|
+
const cacheKey = await computeCacheKey(moduleKey, resolvedOptions);
|
|
95
99
|
let ir = null;
|
|
96
100
|
let irError;
|
|
97
|
-
if (caching && fsUtils.isAvailable()) {
|
|
101
|
+
if (caching && fsUtils.isAvailable() && !checkMode) {
|
|
98
102
|
try {
|
|
99
103
|
const cached = await fsUtils.readFile(pathUtils.join(cacheDir, cacheKey));
|
|
100
104
|
ir = JSON.parse(cached);
|
|
@@ -104,7 +108,10 @@ export async function generate(options, runOptions = {}) {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
if (!ir) {
|
|
107
|
-
const fetchResult = await fetchPythonIr(moduleKey, pythonPath
|
|
111
|
+
const fetchResult = await fetchPythonIr(moduleKey, pythonPath, {
|
|
112
|
+
timeoutMs: resolvedOptions.runtime?.node?.timeout,
|
|
113
|
+
pythonImportPath: resolvedOptions.pythonImportPath,
|
|
114
|
+
});
|
|
108
115
|
ir = fetchResult.ir;
|
|
109
116
|
irError = fetchResult.error;
|
|
110
117
|
if (ir && caching && fsUtils.isAvailable() && !checkMode) {
|
|
@@ -119,6 +126,65 @@ export async function generate(options, runOptions = {}) {
|
|
|
119
126
|
continue;
|
|
120
127
|
}
|
|
121
128
|
const moduleModel = transformIrToTsModel(ir);
|
|
129
|
+
// Apply module-level export filtering (functions/classes + excludes).
|
|
130
|
+
{
|
|
131
|
+
const builtInDefaultExcludes = new Set([
|
|
132
|
+
'dataclass',
|
|
133
|
+
'property',
|
|
134
|
+
'staticmethod',
|
|
135
|
+
'classmethod',
|
|
136
|
+
'abstractmethod',
|
|
137
|
+
'cached_property',
|
|
138
|
+
]);
|
|
139
|
+
const excludeExact = new Set((moduleConfig.exclude ?? []).map(String));
|
|
140
|
+
const excludeRegexes = [];
|
|
141
|
+
for (const pattern of moduleConfig.excludePatterns ?? []) {
|
|
142
|
+
try {
|
|
143
|
+
excludeRegexes.push(new RegExp(String(pattern)));
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
+
warnings.push(`Module ${moduleKey}: invalid excludePatterns regex "${String(pattern)}": ${message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const shouldExclude = (name, applyBuiltInDefaults) => {
|
|
151
|
+
if (excludeExact.has(name)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (excludeRegexes.some(r => r.test(name))) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (applyBuiltInDefaults && builtInDefaultExcludes.has(name)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
if (Array.isArray(moduleConfig.functions)) {
|
|
163
|
+
const allow = new Set(moduleConfig.functions.map(String));
|
|
164
|
+
for (const requested of allow) {
|
|
165
|
+
if (!moduleModel.functions.some(f => f.name === requested)) {
|
|
166
|
+
warnings.push(`Module ${moduleKey}: configured function "${requested}" not found in IR`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
moduleModel.functions = moduleModel.functions.filter(f => allow.has(f.name) && !shouldExclude(f.name, false));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
moduleModel.functions = moduleModel.functions.filter(f => !shouldExclude(f.name, true));
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(moduleConfig.classes)) {
|
|
175
|
+
const allow = new Set(moduleConfig.classes.map(String));
|
|
176
|
+
for (const requested of allow) {
|
|
177
|
+
if (!moduleModel.classes.some(c => c.name === requested)) {
|
|
178
|
+
warnings.push(`Module ${moduleKey}: configured class "${requested}" not found in IR`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
moduleModel.classes = moduleModel.classes.filter(c => allow.has(c.name) && !shouldExclude(c.name, false));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
moduleModel.classes = moduleModel.classes.filter(c => !shouldExclude(c.name, true));
|
|
185
|
+
}
|
|
186
|
+
moduleModel.typeAliases = (moduleModel.typeAliases ?? []).filter(alias => !shouldExclude(alias.name, false));
|
|
187
|
+
}
|
|
122
188
|
// Generate module code
|
|
123
189
|
const annotatedJSDoc = Boolean(resolvedOptions.output?.annotatedJSDoc);
|
|
124
190
|
const gen = instance.generator.generateModuleDefinition(moduleModel, annotatedJSDoc);
|
|
@@ -130,7 +196,7 @@ export async function generate(options, runOptions = {}) {
|
|
|
130
196
|
if (resolvedOptions.output?.declaration) {
|
|
131
197
|
filesToEmit.push({
|
|
132
198
|
path: pathUtils.join(outputDir, `${baseName}.generated.d.ts`),
|
|
133
|
-
content:
|
|
199
|
+
content: gen.declaration,
|
|
134
200
|
});
|
|
135
201
|
}
|
|
136
202
|
// Optional source map emission (placeholder mapping for now)
|
|
@@ -190,18 +256,21 @@ export async function generate(options, runOptions = {}) {
|
|
|
190
256
|
/**
|
|
191
257
|
* Invoke the Python IR CLI to get JSON IR for a module.
|
|
192
258
|
*/
|
|
193
|
-
async function fetchPythonIr(moduleName, pythonPath) {
|
|
259
|
+
async function fetchPythonIr(moduleName, pythonPath, options = {}) {
|
|
194
260
|
if (!processUtils.isAvailable()) {
|
|
195
261
|
return { ir: null, error: 'Subprocess operations not available in this runtime' };
|
|
196
262
|
}
|
|
263
|
+
const delimiter = isWindows() ? ';' : ':';
|
|
264
|
+
const extraPaths = (options.pythonImportPath ?? []).filter(Boolean);
|
|
265
|
+
const existingPyPath = typeof process !== 'undefined' && typeof process.env === 'object' && process.env
|
|
266
|
+
? process.env.PYTHONPATH
|
|
267
|
+
: undefined;
|
|
268
|
+
const mergedPyPath = [...extraPaths, ...(existingPyPath ? [existingPyPath] : [])]
|
|
269
|
+
.filter(Boolean)
|
|
270
|
+
.join(delimiter);
|
|
271
|
+
const env = mergedPyPath ? { PYTHONPATH: mergedPyPath } : undefined;
|
|
197
272
|
try {
|
|
198
|
-
const result = await processUtils.exec(pythonPath, [
|
|
199
|
-
'-m',
|
|
200
|
-
'tywrap_ir',
|
|
201
|
-
'--module',
|
|
202
|
-
moduleName,
|
|
203
|
-
'--no-pretty',
|
|
204
|
-
]);
|
|
273
|
+
const result = await processUtils.exec(pythonPath, ['-m', 'tywrap_ir', '--module', moduleName, '--ir-version', TYWRAP_IR_VERSION, '--no-pretty'], { timeoutMs: options.timeoutMs, env });
|
|
205
274
|
if (result.code === 0) {
|
|
206
275
|
try {
|
|
207
276
|
return { ir: JSON.parse(result.stdout) };
|
|
@@ -213,18 +282,34 @@ async function fetchPythonIr(moduleName, pythonPath) {
|
|
|
213
282
|
};
|
|
214
283
|
}
|
|
215
284
|
}
|
|
216
|
-
|
|
285
|
+
const stderrText = result.stderr.trim();
|
|
286
|
+
const isTywrapIrMissing = stderrText.includes('No module named') && stderrText.includes('tywrap_ir');
|
|
287
|
+
if (!isTywrapIrMissing) {
|
|
288
|
+
return { ir: null, error: `tywrap_ir failed. stderr: ${stderrText || 'empty'}` };
|
|
289
|
+
}
|
|
290
|
+
// Fallback to invoking local __main__.py (useful when running from the repo).
|
|
217
291
|
const localMain = pathUtils.join(process.cwd(), 'tywrap_ir', 'tywrap_ir', '__main__.py');
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
292
|
+
let localMainExists = false;
|
|
293
|
+
if (fsUtils.isAvailable()) {
|
|
294
|
+
try {
|
|
295
|
+
await fsUtils.readFile(localMain);
|
|
296
|
+
localMainExists = true;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
localMainExists = false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!localMainExists) {
|
|
303
|
+
return {
|
|
304
|
+
ir: null,
|
|
305
|
+
error: `tywrap_ir not found on PYTHONPATH. stderr: ${stderrText || 'empty'}`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const fallback = await processUtils.exec(pythonPath, [localMain, '--module', moduleName, '--ir-version', TYWRAP_IR_VERSION, '--no-pretty'], { timeoutMs: options.timeoutMs, env });
|
|
224
309
|
if (fallback.code !== 0) {
|
|
225
310
|
return {
|
|
226
311
|
ir: null,
|
|
227
|
-
error: `tywrap_ir failed. stderr: ${fallback.stderr.trim() ||
|
|
312
|
+
error: `tywrap_ir failed. stderr: ${fallback.stderr.trim() || stderrText || 'empty'}`,
|
|
228
313
|
};
|
|
229
314
|
}
|
|
230
315
|
try {
|
|
@@ -244,70 +329,134 @@ async function fetchPythonIr(moduleName, pythonPath) {
|
|
|
244
329
|
/**
|
|
245
330
|
* Convert JSON IR from Python into the internal TypeScript model used by the generator.
|
|
246
331
|
*/
|
|
332
|
+
function collectModuleTypeVarNames(obj) {
|
|
333
|
+
const names = new Set();
|
|
334
|
+
const constants = Array.isArray(obj.constants) ? obj.constants : [];
|
|
335
|
+
for (const constant of constants) {
|
|
336
|
+
const entry = constant;
|
|
337
|
+
const name = typeof entry.name === 'string' ? entry.name.trim() : '';
|
|
338
|
+
if (!name) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const valueRepr = typeof entry.value_repr === 'string' ? entry.value_repr.trim() : '';
|
|
342
|
+
if (/^~?[A-Za-z_][A-Za-z0-9_]*$/.test(valueRepr) && valueRepr.replace(/^~/, '') === name) {
|
|
343
|
+
names.add(name);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const annotation = typeof entry.annotation === 'string' ? entry.annotation.trim() : '';
|
|
347
|
+
if (/(^|\.)(TypeVar|ParamSpec|TypeVarTuple)(\[|\(|$)/.test(annotation)) {
|
|
348
|
+
names.add(name);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return names;
|
|
352
|
+
}
|
|
247
353
|
function transformIrToTsModel(ir) {
|
|
248
354
|
const obj = typeof ir === 'object' && ir !== null ? ir : {};
|
|
249
355
|
const functions = obj.functions ?? [];
|
|
250
356
|
const classes = obj.classes ?? [];
|
|
251
|
-
const
|
|
357
|
+
const aliases = obj.type_aliases ?? [];
|
|
358
|
+
const moduleTypeVarNames = collectModuleTypeVarNames(obj);
|
|
359
|
+
const parseType = (annotation, typeParameters = []) => parseAnnotationToPythonType(annotation, {
|
|
360
|
+
onUnknownTypeName: recordUnknown,
|
|
361
|
+
knownTypeVarNames: moduleTypeVarNames,
|
|
362
|
+
typeParameters,
|
|
363
|
+
});
|
|
364
|
+
const mapTypeParameters = (value) => Array.isArray(value.type_params)
|
|
365
|
+
? value.type_params.map(v => {
|
|
366
|
+
const param = (v ?? {});
|
|
367
|
+
return {
|
|
368
|
+
name: String(param.name ?? ''),
|
|
369
|
+
kind: String(param.kind ?? 'typevar'),
|
|
370
|
+
bound: param.bound ? parseType(param.bound) : undefined,
|
|
371
|
+
constraints: Array.isArray(param.constraints)
|
|
372
|
+
? param.constraints.map(item => parseType(item))
|
|
373
|
+
: undefined,
|
|
374
|
+
variance: param.variance === 'covariant' ||
|
|
375
|
+
param.variance === 'contravariant' ||
|
|
376
|
+
param.variance === 'invariant'
|
|
377
|
+
? param.variance
|
|
378
|
+
: undefined,
|
|
379
|
+
};
|
|
380
|
+
})
|
|
381
|
+
: [];
|
|
382
|
+
const mapParam = (p, typeParameters = []) => ({
|
|
252
383
|
name: String(p.name ?? ''),
|
|
253
|
-
type:
|
|
384
|
+
type: parseType(p.annotation, typeParameters),
|
|
254
385
|
optional: Boolean(p.default),
|
|
255
386
|
varArgs: p.kind === 'VAR_POSITIONAL',
|
|
256
387
|
kwArgs: p.kind === 'VAR_KEYWORD',
|
|
388
|
+
positionalOnly: p.kind === 'POSITIONAL_ONLY',
|
|
389
|
+
keywordOnly: p.kind === 'KEYWORD_ONLY',
|
|
257
390
|
});
|
|
258
|
-
const mapFunc = (f) =>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
391
|
+
const mapFunc = (f, inheritedTypeParameters = []) => {
|
|
392
|
+
const localTypeParameters = mapTypeParameters(f);
|
|
393
|
+
const annotationTypeParameters = [...inheritedTypeParameters, ...localTypeParameters];
|
|
394
|
+
return {
|
|
395
|
+
name: String(f.name ?? ''),
|
|
396
|
+
signature: {
|
|
397
|
+
parameters: Array.isArray(f.parameters)
|
|
398
|
+
? f.parameters.map(v => mapParam((v ?? {}), annotationTypeParameters))
|
|
399
|
+
: [],
|
|
400
|
+
returnType: parseType(f.returns, annotationTypeParameters),
|
|
401
|
+
isAsync: Boolean(f.is_async),
|
|
402
|
+
isGenerator: Boolean(f.is_generator),
|
|
403
|
+
},
|
|
404
|
+
docstring: f.docstring ?? undefined,
|
|
405
|
+
decorators: [],
|
|
265
406
|
isAsync: Boolean(f.is_async),
|
|
266
407
|
isGenerator: Boolean(f.is_generator),
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
: [],
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
408
|
+
typeParameters: [...localTypeParameters],
|
|
409
|
+
returnType: parseType(f.returns, annotationTypeParameters),
|
|
410
|
+
parameters: Array.isArray(f.parameters)
|
|
411
|
+
? f.parameters.map(v => mapParam((v ?? {}), annotationTypeParameters))
|
|
412
|
+
: [],
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
const mapClass = (c) => {
|
|
416
|
+
const classTypeParameters = mapTypeParameters(c);
|
|
417
|
+
return {
|
|
418
|
+
name: String(c.name ?? ''),
|
|
419
|
+
bases: Array.isArray(c.bases) ? c.bases : [],
|
|
420
|
+
methods: Array.isArray(c.methods)
|
|
421
|
+
? c.methods.map(v => mapFunc((v ?? {}), classTypeParameters))
|
|
422
|
+
: [],
|
|
423
|
+
properties: Array.isArray(c.fields)
|
|
424
|
+
? c.fields.map(v => {
|
|
425
|
+
const p = (v ?? {});
|
|
426
|
+
return {
|
|
427
|
+
name: String(p.name ?? ''),
|
|
428
|
+
type: parseType(p.annotation, classTypeParameters),
|
|
429
|
+
readonly: false,
|
|
430
|
+
setter: false,
|
|
431
|
+
getter: true,
|
|
432
|
+
optional: Boolean(p.default),
|
|
433
|
+
};
|
|
434
|
+
})
|
|
435
|
+
: [],
|
|
436
|
+
docstring: c.docstring ?? undefined,
|
|
437
|
+
decorators: c.typed_dict ? ['__typed_dict__'] : [],
|
|
438
|
+
kind: c.typed_dict
|
|
439
|
+
? 'typed_dict'
|
|
440
|
+
: c.is_protocol
|
|
441
|
+
? 'protocol'
|
|
442
|
+
: c.is_namedtuple
|
|
443
|
+
? 'namedtuple'
|
|
444
|
+
: c.is_dataclass
|
|
445
|
+
? 'dataclass'
|
|
446
|
+
: c.is_pydantic
|
|
447
|
+
? 'pydantic'
|
|
448
|
+
: 'class',
|
|
449
|
+
typeParameters: classTypeParameters,
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
const mapTypeAlias = (alias) => {
|
|
453
|
+
const typeParameters = mapTypeParameters(alias);
|
|
454
|
+
return {
|
|
455
|
+
name: String(alias.name ?? ''),
|
|
456
|
+
type: parseType(alias.definition, typeParameters),
|
|
457
|
+
typeParameters,
|
|
458
|
+
};
|
|
459
|
+
};
|
|
311
460
|
const moduleModel = {
|
|
312
461
|
name: obj.module ?? 'module',
|
|
313
462
|
path: undefined,
|
|
@@ -316,225 +465,12 @@ function transformIrToTsModel(ir) {
|
|
|
316
465
|
: undefined,
|
|
317
466
|
functions: functions.map(v => mapFunc((v ?? {}))),
|
|
318
467
|
classes: classes.map(v => mapClass((v ?? {}))),
|
|
468
|
+
typeAliases: aliases.map(v => mapTypeAlias((v ?? {}))),
|
|
319
469
|
imports: [],
|
|
320
470
|
exports: [],
|
|
321
471
|
};
|
|
322
472
|
return moduleModel;
|
|
323
473
|
}
|
|
324
|
-
function unknownType() {
|
|
325
|
-
return { kind: 'custom', name: 'Any', module: 'typing' };
|
|
326
|
-
}
|
|
327
|
-
// Parse Python IR annotation string -> PythonType
|
|
328
|
-
function parseAnnotationToPythonType(annotation, depth = 0) {
|
|
329
|
-
if (annotation === null || annotation === undefined) {
|
|
330
|
-
return unknownType();
|
|
331
|
-
}
|
|
332
|
-
if (depth > 100) {
|
|
333
|
-
return unknownType();
|
|
334
|
-
}
|
|
335
|
-
const raw = String(annotation).trim();
|
|
336
|
-
// Handle built-in class repr: <class 'int'>
|
|
337
|
-
const classMatch = raw.match(/^<class ['"][^'"]+['"]>$/);
|
|
338
|
-
if (classMatch) {
|
|
339
|
-
const inner = (raw.match(/^<class ['"]([^'"]+)['"]>$/) ?? [])[1] ?? '';
|
|
340
|
-
const name = (inner.split('.').pop() ?? '').toString();
|
|
341
|
-
return mapSimpleName(name);
|
|
342
|
-
}
|
|
343
|
-
// PEP 604 unions: int | str | None
|
|
344
|
-
if (raw.includes(' | ')) {
|
|
345
|
-
const parts = splitTopLevel(raw, '|');
|
|
346
|
-
const types = parts.map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
347
|
-
return { kind: 'union', types };
|
|
348
|
-
}
|
|
349
|
-
// typing.Union[...]
|
|
350
|
-
if (raw.startsWith('typing.Union[') || raw.startsWith('Union[')) {
|
|
351
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
352
|
-
const parts = splitTopLevel(inner, ',');
|
|
353
|
-
const types = parts.map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
354
|
-
return { kind: 'union', types };
|
|
355
|
-
}
|
|
356
|
-
// Optional[T]
|
|
357
|
-
if (raw.startsWith('typing.Optional[') || raw.startsWith('Optional[')) {
|
|
358
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
359
|
-
const base = parseAnnotationToPythonType(inner, depth + 1);
|
|
360
|
-
return { kind: 'optional', type: base };
|
|
361
|
-
}
|
|
362
|
-
// Literal[...] -> literal values union
|
|
363
|
-
if (raw.startsWith('typing.Literal[') || raw.startsWith('Literal[')) {
|
|
364
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
365
|
-
const parts = splitTopLevel(inner, ',');
|
|
366
|
-
if (parts.length === 1) {
|
|
367
|
-
return mapLiteral(String(parts[0] ?? '').trim());
|
|
368
|
-
}
|
|
369
|
-
return { kind: 'union', types: parts.map(p => mapLiteral(String(p).trim())) };
|
|
370
|
-
}
|
|
371
|
-
// typing_extensions wrappers: ClassVar[T], Final[T], Required[T], NotRequired[T]
|
|
372
|
-
const extMatch = raw.match(/^(typing\.|typing_extensions\.)?(ClassVar|Final|Required|NotRequired)\[(.*)\]$/);
|
|
373
|
-
if (extMatch) {
|
|
374
|
-
const inner = extMatch[3] ?? '';
|
|
375
|
-
const base = parseAnnotationToPythonType(inner, depth + 1);
|
|
376
|
-
return base;
|
|
377
|
-
}
|
|
378
|
-
// LiteralString
|
|
379
|
-
if (raw === 'typing.LiteralString' || raw === 'LiteralString') {
|
|
380
|
-
return { kind: 'primitive', name: 'str' };
|
|
381
|
-
}
|
|
382
|
-
// Callable[[...], R]
|
|
383
|
-
if (raw.startsWith('typing.Callable[') || raw.startsWith('Callable[')) {
|
|
384
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
385
|
-
const parts = splitTopLevel(inner, ',');
|
|
386
|
-
if (parts.length >= 2) {
|
|
387
|
-
const paramsPart = (parts[0] ?? '').trim();
|
|
388
|
-
const returnPart = parts.slice(1).join(',').trim();
|
|
389
|
-
const paramInner = paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
|
|
390
|
-
const paramTypes = (() => {
|
|
391
|
-
const trimmed = paramInner.trim();
|
|
392
|
-
if (trimmed === '...' || trimmed === 'Ellipsis') {
|
|
393
|
-
return [{ kind: 'custom', name: '...' }];
|
|
394
|
-
}
|
|
395
|
-
return trimmed
|
|
396
|
-
? splitTopLevel(trimmed, ',').map(p => parseAnnotationToPythonType(p.trim(), depth + 1))
|
|
397
|
-
: [];
|
|
398
|
-
})();
|
|
399
|
-
const returnType = parseAnnotationToPythonType(returnPart, depth + 1);
|
|
400
|
-
return { kind: 'callable', parameters: paramTypes, returnType };
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
// Mapping[K, V] / Dict[K, V] normalization
|
|
404
|
-
if (raw.startsWith('typing.Mapping[') || raw.startsWith('Mapping[')) {
|
|
405
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
406
|
-
const parts = splitTopLevel(inner, ',');
|
|
407
|
-
const itemTypes = parts.map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
408
|
-
return { kind: 'collection', name: 'dict', itemTypes };
|
|
409
|
-
}
|
|
410
|
-
// Annotated[T, ...] -> annotated node with base and metadata
|
|
411
|
-
if (raw.startsWith('typing.Annotated[') || raw.startsWith('Annotated[')) {
|
|
412
|
-
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
413
|
-
const parts = splitTopLevel(inner, ',');
|
|
414
|
-
if (parts.length > 0) {
|
|
415
|
-
const base = parseAnnotationToPythonType((parts[0] ?? '').trim(), depth + 1);
|
|
416
|
-
const metaParts = parts.slice(1).map(p => String(p).trim());
|
|
417
|
-
return { kind: 'annotated', base, metadata: metaParts };
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
// Collections: list[T], dict[K,V], tuple[...], set[T]
|
|
421
|
-
const coll = normalizeCollectionName(raw);
|
|
422
|
-
if (coll) {
|
|
423
|
-
const { name, inner } = coll;
|
|
424
|
-
const itemParts = name === 'dict' ? splitTopLevel(inner ?? '', ',') : splitTopLevel(inner ?? '', ',');
|
|
425
|
-
const itemTypes = (inner ? itemParts : []).map(p => parseAnnotationToPythonType(p.trim(), depth + 1));
|
|
426
|
-
return { kind: 'collection', name, itemTypes };
|
|
427
|
-
}
|
|
428
|
-
// typing.Callable[...] unlikely for now - treat as custom
|
|
429
|
-
// Bare names like int, str, float, bool, bytes, None
|
|
430
|
-
return mapSimpleName(raw);
|
|
431
|
-
}
|
|
432
|
-
function mapSimpleName(name) {
|
|
433
|
-
const n = name.replace(/^typing\./, '').trim();
|
|
434
|
-
if (n === 'int' || n === 'float' || n === 'str' || n === 'bool' || n === 'bytes') {
|
|
435
|
-
// cast to the specific union type without using any
|
|
436
|
-
return { kind: 'primitive', name: n };
|
|
437
|
-
}
|
|
438
|
-
// Track unknown typing-ish names for diagnostics
|
|
439
|
-
if (n === 'Any' ||
|
|
440
|
-
n === 'Never' ||
|
|
441
|
-
n === 'LiteralString' ||
|
|
442
|
-
n === 'ClassVar' ||
|
|
443
|
-
n === 'Final' ||
|
|
444
|
-
n === 'TypeAlias' ||
|
|
445
|
-
n === 'Required' ||
|
|
446
|
-
n === 'NotRequired') {
|
|
447
|
-
try {
|
|
448
|
-
recordUnknown(n);
|
|
449
|
-
}
|
|
450
|
-
catch { }
|
|
451
|
-
}
|
|
452
|
-
if (n === 'None' || n.toLowerCase() === 'nonetype') {
|
|
453
|
-
return { kind: 'primitive', name: 'None' };
|
|
454
|
-
}
|
|
455
|
-
if (n === 'list' || n === 'List') {
|
|
456
|
-
return { kind: 'collection', name: 'list', itemTypes: [] };
|
|
457
|
-
}
|
|
458
|
-
if (n === 'dict' || n === 'Dict') {
|
|
459
|
-
return { kind: 'collection', name: 'dict', itemTypes: [] };
|
|
460
|
-
}
|
|
461
|
-
if (n === 'tuple' || n === 'Tuple') {
|
|
462
|
-
return { kind: 'collection', name: 'tuple', itemTypes: [] };
|
|
463
|
-
}
|
|
464
|
-
if (n === 'set' || n === 'Set') {
|
|
465
|
-
return { kind: 'collection', name: 'set', itemTypes: [] };
|
|
466
|
-
}
|
|
467
|
-
return { kind: 'custom', name: n };
|
|
468
|
-
}
|
|
469
|
-
function normalizeCollectionName(raw) {
|
|
470
|
-
const m = raw.match(/^(typing\.)?(List|Dict|Tuple|Set|list|dict|tuple|set)\[(.*)\]$/);
|
|
471
|
-
if (!m) {
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
474
|
-
const nameRaw = String(m[2] ?? '');
|
|
475
|
-
const name = nameRaw.toLowerCase() === 'list'
|
|
476
|
-
? 'list'
|
|
477
|
-
: nameRaw.toLowerCase() === 'dict'
|
|
478
|
-
? 'dict'
|
|
479
|
-
: nameRaw.toLowerCase() === 'tuple'
|
|
480
|
-
? 'tuple'
|
|
481
|
-
: 'set';
|
|
482
|
-
return { name, inner: String(m[3] ?? '') };
|
|
483
|
-
}
|
|
484
|
-
function splitTopLevel(input, sep) {
|
|
485
|
-
const results = [];
|
|
486
|
-
let level = 0;
|
|
487
|
-
let cur = '';
|
|
488
|
-
let guard = 0;
|
|
489
|
-
for (let i = 0; i < input.length; i++) {
|
|
490
|
-
// Depth/length guard to avoid pathological recursion/loops
|
|
491
|
-
guard++;
|
|
492
|
-
if (guard > 20000) {
|
|
493
|
-
// Bail out safely; push remaining and break
|
|
494
|
-
if (cur.trim()) {
|
|
495
|
-
results.push(cur.trim());
|
|
496
|
-
}
|
|
497
|
-
break;
|
|
498
|
-
}
|
|
499
|
-
const ch = input.charAt(i);
|
|
500
|
-
if (ch === '[' || ch === '(') {
|
|
501
|
-
level++;
|
|
502
|
-
}
|
|
503
|
-
if (ch === ']' || ch === ')') {
|
|
504
|
-
level = Math.max(0, level - 1);
|
|
505
|
-
}
|
|
506
|
-
if (level === 0 && input.slice(i, i + sep.length) === sep) {
|
|
507
|
-
results.push(cur.trim());
|
|
508
|
-
cur = '';
|
|
509
|
-
i += sep.length - 1;
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
cur += ch;
|
|
513
|
-
}
|
|
514
|
-
if (cur.trim()) {
|
|
515
|
-
results.push(cur.trim());
|
|
516
|
-
}
|
|
517
|
-
return results;
|
|
518
|
-
}
|
|
519
|
-
function mapLiteral(text) {
|
|
520
|
-
// strip quotes if present
|
|
521
|
-
const t = text.trim();
|
|
522
|
-
if ((t.startsWith("'") && t.endsWith("'")) || (t.startsWith('"') && t.endsWith('"'))) {
|
|
523
|
-
return { kind: 'literal', value: t.slice(1, -1) };
|
|
524
|
-
}
|
|
525
|
-
if (t === 'True' || t === 'False') {
|
|
526
|
-
return { kind: 'literal', value: t === 'True' };
|
|
527
|
-
}
|
|
528
|
-
if (t === 'None') {
|
|
529
|
-
return { kind: 'literal', value: null };
|
|
530
|
-
}
|
|
531
|
-
const num = Number(t);
|
|
532
|
-
if (!Number.isNaN(num)) {
|
|
533
|
-
return { kind: 'literal', value: num };
|
|
534
|
-
}
|
|
535
|
-
// fallback
|
|
536
|
-
return { kind: 'custom', name: t };
|
|
537
|
-
}
|
|
538
474
|
/**
|
|
539
475
|
* Compute a stable cache key filename for a module and options
|
|
540
476
|
*/
|
|
@@ -549,6 +485,8 @@ async function computeCacheKey(moduleName, options) {
|
|
|
549
485
|
const keyObject = {
|
|
550
486
|
module: moduleName,
|
|
551
487
|
moduleVersion: moduleConfig?.version ?? null,
|
|
488
|
+
irVersion: TYWRAP_IR_VERSION,
|
|
489
|
+
pythonImportPath: options.pythonImportPath ?? [],
|
|
552
490
|
runtime: {
|
|
553
491
|
pythonPath: runtimePython,
|
|
554
492
|
virtualEnv: options.runtime?.node?.virtualEnv ?? null,
|
|
@@ -564,46 +502,7 @@ async function computeCacheKey(moduleName, options) {
|
|
|
564
502
|
},
|
|
565
503
|
typeHints: moduleConfig?.typeHints ?? 'strict',
|
|
566
504
|
};
|
|
567
|
-
|
|
568
|
-
const digest = await hashUtils.sha256Hex(normalized);
|
|
569
|
-
return `ir_${moduleName}_${digest.slice(0, 16)}.json`;
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Very lightweight .d.ts emitter derived from generated TS wrappers
|
|
573
|
-
* This is intentionally minimal and stable for our wrappers shape.
|
|
574
|
-
*/
|
|
575
|
-
function renderDts(generatedTs) {
|
|
576
|
-
const header = `// Generated by tywrap\n// Type Declarations\n// DO NOT EDIT MANUALLY\n\n`;
|
|
577
|
-
const lines = [header];
|
|
578
|
-
// Extract function exports
|
|
579
|
-
const funcRegex = /export\s+async\s+function\s+(\w+)\s*\(([^)]*)\)\s*:\s*Promise<([^>]+)>/g;
|
|
580
|
-
let m;
|
|
581
|
-
while ((m = funcRegex.exec(generatedTs)) !== null) {
|
|
582
|
-
const name = m[1];
|
|
583
|
-
const params = m[2];
|
|
584
|
-
const ret = m[3];
|
|
585
|
-
lines.push(`export function ${name}(${params}): Promise<${ret}>;`);
|
|
586
|
-
}
|
|
587
|
-
// Extract class exports and methods
|
|
588
|
-
const classRegex = /export\s+class\s+(\w+)\s*\{([\s\S]*?)\n\}/g;
|
|
589
|
-
while ((m = classRegex.exec(generatedTs)) !== null) {
|
|
590
|
-
const className = String(m[1] ?? '');
|
|
591
|
-
const body = String(m[2] ?? '');
|
|
592
|
-
// Constructor is always variadic unknown[] in current generator
|
|
593
|
-
const methods = [];
|
|
594
|
-
const methodRegex = /\n\s+async\s+(\w+)\s*\(([^)]*)\)\s*:\s*Promise<([^>]+)>/g;
|
|
595
|
-
let mm;
|
|
596
|
-
while ((mm = methodRegex.exec(body)) !== null) {
|
|
597
|
-
methods.push(` ${mm[1]}(${mm[2]}): Promise<${mm[3]}>;`);
|
|
598
|
-
}
|
|
599
|
-
lines.push(`export class ${className} {`);
|
|
600
|
-
lines.push(` constructor(...args: unknown[]);`);
|
|
601
|
-
if (methods.length > 0) {
|
|
602
|
-
lines.push(methods.join('\n'));
|
|
603
|
-
}
|
|
604
|
-
lines.push('}');
|
|
605
|
-
}
|
|
606
|
-
return `${lines.join('\n')}\n`;
|
|
505
|
+
return await computeIrCacheFilename(keyObject);
|
|
607
506
|
}
|
|
608
507
|
/**
|
|
609
508
|
* Minimal source map placeholder (stable, empty mappings)
|