tywrap 0.2.1 → 0.4.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 +100 -32
- package/dist/cli.js +70 -5
- 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 +62 -20
- 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 +46 -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/dev.d.ts +57 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +743 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +8 -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 +206 -32
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +6 -5
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +6 -5
- 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 +17 -0
- package/dist/runtime/worker-pool.d.ts.map +1 -1
- package/dist/runtime/worker-pool.js +97 -22
- package/dist/runtime/worker-pool.js.map +1 -1
- package/dist/types/index.d.ts +51 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +6 -0
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +237 -331
- 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 +47 -21
- package/runtime/python_bridge.py +78 -12
- package/src/cli.ts +85 -5
- package/src/config/index.ts +91 -32
- package/src/core/analyzer.ts +51 -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/dev.ts +983 -0
- package/src/index.ts +35 -6
- 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 +312 -46
- package/src/runtime/optimized-node.ts +9 -5
- 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 +120 -24
- package/src/types/global.d.ts +11 -1
- package/src/types/index.ts +63 -8
- package/src/tywrap.ts +296 -361
- 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
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import type { PythonGenericParameter, PythonType } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export interface AnnotationParserOptions {
|
|
4
|
+
onUnknownTypeName?: (name: string) => void;
|
|
5
|
+
knownTypeVarNames?: Iterable<string>;
|
|
6
|
+
typeParameters?: readonly PythonGenericParameter[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function parseAnnotationToPythonType(
|
|
10
|
+
annotation: unknown,
|
|
11
|
+
options: AnnotationParserOptions = {}
|
|
12
|
+
): PythonType {
|
|
13
|
+
const onUnknownTypeName = options.onUnknownTypeName;
|
|
14
|
+
const knownTypeVarNames = new Set(options.knownTypeVarNames ?? []);
|
|
15
|
+
const knownTypeParameters = new Map(
|
|
16
|
+
(options.typeParameters ?? []).map(param => [param.name, param] as const)
|
|
17
|
+
);
|
|
18
|
+
const modulePrefixes = ['', 'typing.', 'typing_extensions.', 'collections.abc.'] as const;
|
|
19
|
+
|
|
20
|
+
const unknownType = (): PythonType => ({ kind: 'custom', name: 'Any', module: 'typing' });
|
|
21
|
+
|
|
22
|
+
const recordUnknown = (name: string): void => {
|
|
23
|
+
try {
|
|
24
|
+
onUnknownTypeName?.(name);
|
|
25
|
+
} catch {
|
|
26
|
+
// ignore diagnostics hooks
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const mapLiteral = (text: string): PythonType => {
|
|
31
|
+
const t = text.trim();
|
|
32
|
+
if ((t.startsWith("'") && t.endsWith("'")) || (t.startsWith('"') && t.endsWith('"'))) {
|
|
33
|
+
return { kind: 'literal', value: t.slice(1, -1) } as PythonType;
|
|
34
|
+
}
|
|
35
|
+
if (t === 'True' || t === 'False') {
|
|
36
|
+
return { kind: 'literal', value: t === 'True' } as PythonType;
|
|
37
|
+
}
|
|
38
|
+
if (t === 'None') {
|
|
39
|
+
return { kind: 'literal', value: null } as PythonType;
|
|
40
|
+
}
|
|
41
|
+
const num = Number(t);
|
|
42
|
+
if (t !== '' && !Number.isNaN(num)) {
|
|
43
|
+
return { kind: 'literal', value: num } as PythonType;
|
|
44
|
+
}
|
|
45
|
+
return { kind: 'custom', name: t } as PythonType;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const mapKnownTypeParameter = (name: string): PythonType | null => {
|
|
49
|
+
const normalized = name.replace(/^~/, '').trim();
|
|
50
|
+
const param = knownTypeParameters.get(normalized);
|
|
51
|
+
if (!param) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
switch (param.kind) {
|
|
55
|
+
case 'typevar':
|
|
56
|
+
return {
|
|
57
|
+
kind: 'typevar',
|
|
58
|
+
name: param.name,
|
|
59
|
+
bound: param.bound,
|
|
60
|
+
constraints: param.constraints,
|
|
61
|
+
variance: param.variance,
|
|
62
|
+
} satisfies PythonType;
|
|
63
|
+
case 'paramspec':
|
|
64
|
+
return { kind: 'paramspec', name: param.name } satisfies PythonType;
|
|
65
|
+
case 'typevartuple':
|
|
66
|
+
return { kind: 'typevartuple', name: param.name } satisfies PythonType;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const mapSimpleName = (name: string): PythonType => {
|
|
71
|
+
const n = name.replace(/^(typing\.|typing_extensions\.|collections\.abc\.)/, '').trim();
|
|
72
|
+
const known = mapKnownTypeParameter(n);
|
|
73
|
+
if (known) {
|
|
74
|
+
return known;
|
|
75
|
+
}
|
|
76
|
+
if (knownTypeVarNames.has(n)) {
|
|
77
|
+
return { kind: 'typevar', name: n };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (n === 'int' || n === 'float' || n === 'str' || n === 'bool' || n === 'bytes') {
|
|
81
|
+
return { kind: 'primitive', name: n };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
n === 'Any' ||
|
|
86
|
+
n === 'Never' ||
|
|
87
|
+
n === 'LiteralString' ||
|
|
88
|
+
n === 'ClassVar' ||
|
|
89
|
+
n === 'Final' ||
|
|
90
|
+
n === 'TypeAlias' ||
|
|
91
|
+
n === 'Required' ||
|
|
92
|
+
n === 'NotRequired'
|
|
93
|
+
) {
|
|
94
|
+
recordUnknown(n);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (n === 'None' || n.toLowerCase() === 'nonetype') {
|
|
98
|
+
return { kind: 'primitive', name: 'None' };
|
|
99
|
+
}
|
|
100
|
+
if (n === 'list' || n === 'List') {
|
|
101
|
+
return { kind: 'collection', name: 'list', itemTypes: [] };
|
|
102
|
+
}
|
|
103
|
+
if (n === 'dict' || n === 'Dict') {
|
|
104
|
+
return { kind: 'collection', name: 'dict', itemTypes: [] };
|
|
105
|
+
}
|
|
106
|
+
if (n === 'tuple' || n === 'Tuple') {
|
|
107
|
+
return { kind: 'collection', name: 'tuple', itemTypes: [] };
|
|
108
|
+
}
|
|
109
|
+
if (n === 'set' || n === 'Set') {
|
|
110
|
+
return { kind: 'collection', name: 'set', itemTypes: [] };
|
|
111
|
+
}
|
|
112
|
+
if (n === 'frozenset' || n === 'FrozenSet') {
|
|
113
|
+
return { kind: 'collection', name: 'frozenset', itemTypes: [] };
|
|
114
|
+
}
|
|
115
|
+
if (n.startsWith('~')) {
|
|
116
|
+
return { kind: 'typevar', name: n.slice(1) };
|
|
117
|
+
}
|
|
118
|
+
return { kind: 'custom', name: n };
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const parseTypingFactoryName = (text: string, name: string): string | null => {
|
|
122
|
+
for (const prefix of modulePrefixes) {
|
|
123
|
+
const start = `${prefix}${name}(`;
|
|
124
|
+
if (!text.startsWith(start) || !text.endsWith(')')) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const inner = text.slice(start.length, -1).trim();
|
|
129
|
+
if (inner.length < 2) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const quote = inner[0];
|
|
134
|
+
if ((quote !== "'" && quote !== '"') || inner[inner.length - 1] !== quote) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const commaIndex = inner.indexOf(',');
|
|
139
|
+
const quoted = commaIndex === -1 ? inner : inner.slice(0, commaIndex).trimEnd();
|
|
140
|
+
if (quoted.length < 2 || quoted[quoted.length - 1] !== quote) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return quoted.slice(1, -1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const splitQualifiedName = (raw: string): { name: string; module?: string } => {
|
|
151
|
+
const trimmed = raw.trim();
|
|
152
|
+
const parts = trimmed.split('.').filter(Boolean);
|
|
153
|
+
if (parts.length <= 1) {
|
|
154
|
+
return { name: trimmed };
|
|
155
|
+
}
|
|
156
|
+
const name = parts[parts.length - 1] ?? trimmed;
|
|
157
|
+
return { name, module: parts.slice(0, -1).join('.') || undefined };
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const normalizeCollectionName = (
|
|
161
|
+
raw: string
|
|
162
|
+
): { name: 'list' | 'dict' | 'tuple' | 'set' | 'frozenset'; inner?: string } | null => {
|
|
163
|
+
const m = raw.match(
|
|
164
|
+
/^(typing\.|typing_extensions\.)?(List|Dict|Tuple|Set|FrozenSet|list|dict|tuple|set|frozenset)\[(.*)\]$/
|
|
165
|
+
);
|
|
166
|
+
if (!m) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const nameRaw = String(m[2] ?? '');
|
|
170
|
+
const nameLower = nameRaw.toLowerCase();
|
|
171
|
+
const name =
|
|
172
|
+
nameLower === 'list'
|
|
173
|
+
? 'list'
|
|
174
|
+
: nameLower === 'dict'
|
|
175
|
+
? 'dict'
|
|
176
|
+
: nameLower === 'tuple'
|
|
177
|
+
? 'tuple'
|
|
178
|
+
: nameLower === 'frozenset'
|
|
179
|
+
? 'frozenset'
|
|
180
|
+
: 'set';
|
|
181
|
+
return { name, inner: String(m[3] ?? '') };
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const splitTopLevel = (input: string, sep: '|' | ','): string[] => {
|
|
185
|
+
const results: string[] = [];
|
|
186
|
+
let level = 0;
|
|
187
|
+
let cur = '';
|
|
188
|
+
let inQuote: "'" | '"' | null = null;
|
|
189
|
+
let escaped = false;
|
|
190
|
+
let guard = 0;
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < input.length; i++) {
|
|
193
|
+
guard++;
|
|
194
|
+
if (guard > 20000) {
|
|
195
|
+
if (cur.trim()) {
|
|
196
|
+
results.push(cur.trim());
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const ch = input.charAt(i);
|
|
202
|
+
|
|
203
|
+
if (escaped) {
|
|
204
|
+
cur += ch;
|
|
205
|
+
escaped = false;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (inQuote) {
|
|
210
|
+
cur += ch;
|
|
211
|
+
if (ch === '\\') {
|
|
212
|
+
escaped = true;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (ch === inQuote) {
|
|
216
|
+
inQuote = null;
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (ch === "'" || ch === '"') {
|
|
222
|
+
inQuote = ch;
|
|
223
|
+
cur += ch;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (ch === '[' || ch === '(') {
|
|
228
|
+
level++;
|
|
229
|
+
} else if (ch === ']' || ch === ')') {
|
|
230
|
+
level = Math.max(0, level - 1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (level === 0 && ch === sep) {
|
|
234
|
+
results.push(cur.trim());
|
|
235
|
+
cur = '';
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
cur += ch;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (cur.trim()) {
|
|
243
|
+
results.push(cur.trim());
|
|
244
|
+
}
|
|
245
|
+
return results;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const splitGenericInvocation = (raw: string): { name: string; inner: string } | null => {
|
|
249
|
+
if (!raw.endsWith(']')) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const bracketStart = raw.indexOf('[');
|
|
253
|
+
if (bracketStart <= 0) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let depth = 0;
|
|
258
|
+
for (let i = bracketStart; i < raw.length; i++) {
|
|
259
|
+
const ch = raw.charAt(i);
|
|
260
|
+
if (ch === '[') {
|
|
261
|
+
depth++;
|
|
262
|
+
} else if (ch === ']') {
|
|
263
|
+
depth--;
|
|
264
|
+
if (depth === 0 && i !== raw.length - 1) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (depth !== 0) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
name: raw.slice(0, bracketStart).trim(),
|
|
276
|
+
inner: raw.slice(bracketStart + 1, -1),
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const parse = (ann: unknown, depth = 0): PythonType => {
|
|
281
|
+
if (ann === null || ann === undefined) {
|
|
282
|
+
return unknownType();
|
|
283
|
+
}
|
|
284
|
+
if (depth > 100) {
|
|
285
|
+
return unknownType();
|
|
286
|
+
}
|
|
287
|
+
const rawText = String(ann).trim();
|
|
288
|
+
const raw = rawText.startsWith('~') ? rawText.slice(1).trim() : rawText;
|
|
289
|
+
if (raw === '') {
|
|
290
|
+
return unknownType();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(raw) && rawText.startsWith('~')) {
|
|
294
|
+
return mapKnownTypeParameter(raw) ?? { kind: 'typevar', name: raw };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const paramspecArgsMatch = rawText.match(/^~?([A-Za-z_][A-Za-z0-9_]*)\.(args|kwargs)$/);
|
|
298
|
+
if (paramspecArgsMatch?.[1] && paramspecArgsMatch[2]) {
|
|
299
|
+
const baseName = paramspecArgsMatch[1];
|
|
300
|
+
const known = mapKnownTypeParameter(baseName);
|
|
301
|
+
if (known?.kind === 'paramspec' || rawText.startsWith('~')) {
|
|
302
|
+
return paramspecArgsMatch[2] === 'args'
|
|
303
|
+
? ({ kind: 'paramspec_args', name: baseName } satisfies PythonType)
|
|
304
|
+
: ({ kind: 'paramspec_kwargs', name: baseName } satisfies PythonType);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const builtInClassMatch = raw.match(/^<class ['"][^'"]+['"]>$/);
|
|
309
|
+
if (builtInClassMatch) {
|
|
310
|
+
const inner = (raw.match(/^<class ['"]([^'"]+)['"]>$/) ?? [])[1] ?? '';
|
|
311
|
+
const name = (inner.split('.').pop() ?? '').toString();
|
|
312
|
+
return mapSimpleName(name);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (raw.includes('|')) {
|
|
316
|
+
const parts = splitTopLevel(raw, '|');
|
|
317
|
+
if (parts.length > 1) {
|
|
318
|
+
const types = parts.map(p => parse(p.trim(), depth + 1));
|
|
319
|
+
return { kind: 'union', types };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (
|
|
324
|
+
raw.startsWith('typing.Union[') ||
|
|
325
|
+
raw.startsWith('typing_extensions.Union[') ||
|
|
326
|
+
raw.startsWith('Union[')
|
|
327
|
+
) {
|
|
328
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
329
|
+
const parts = splitTopLevel(inner, ',');
|
|
330
|
+
const types = parts.map(p => parse(p.trim(), depth + 1));
|
|
331
|
+
return { kind: 'union', types };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (
|
|
335
|
+
raw.startsWith('typing.Optional[') ||
|
|
336
|
+
raw.startsWith('typing_extensions.Optional[') ||
|
|
337
|
+
raw.startsWith('Optional[')
|
|
338
|
+
) {
|
|
339
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
340
|
+
return { kind: 'optional', type: parse(inner, depth + 1) };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (
|
|
344
|
+
raw.startsWith('typing.Literal[') ||
|
|
345
|
+
raw.startsWith('typing_extensions.Literal[') ||
|
|
346
|
+
raw.startsWith('Literal[')
|
|
347
|
+
) {
|
|
348
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
349
|
+
const parts = splitTopLevel(inner, ',');
|
|
350
|
+
if (parts.length === 1) {
|
|
351
|
+
return mapLiteral(String(parts[0] ?? '').trim());
|
|
352
|
+
}
|
|
353
|
+
return { kind: 'union', types: parts.map(p => mapLiteral(String(p).trim())) };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const extMatch = raw.match(
|
|
357
|
+
/^(typing\.|typing_extensions\.)?(ClassVar|Final|Required|NotRequired)\[(.*)\]$/
|
|
358
|
+
);
|
|
359
|
+
if (extMatch) {
|
|
360
|
+
return parse(extMatch[3] ?? '', depth + 1);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const typeVarName = parseTypingFactoryName(rawText, 'TypeVar');
|
|
364
|
+
if (typeVarName) {
|
|
365
|
+
return mapKnownTypeParameter(typeVarName) ?? { kind: 'typevar', name: typeVarName };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const paramSpecName = parseTypingFactoryName(rawText, 'ParamSpec');
|
|
369
|
+
if (paramSpecName) {
|
|
370
|
+
return mapKnownTypeParameter(paramSpecName) ?? { kind: 'paramspec', name: paramSpecName };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const typeVarTupleName = parseTypingFactoryName(rawText, 'TypeVarTuple');
|
|
374
|
+
if (typeVarTupleName) {
|
|
375
|
+
return (
|
|
376
|
+
mapKnownTypeParameter(typeVarTupleName) ?? {
|
|
377
|
+
kind: 'typevartuple',
|
|
378
|
+
name: typeVarTupleName,
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (
|
|
383
|
+
raw === 'typing.LiteralString' ||
|
|
384
|
+
raw === 'typing_extensions.LiteralString' ||
|
|
385
|
+
raw === 'LiteralString'
|
|
386
|
+
) {
|
|
387
|
+
return { kind: 'primitive', name: 'str' };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
raw.startsWith('typing.Callable[') ||
|
|
392
|
+
raw.startsWith('typing_extensions.Callable[') ||
|
|
393
|
+
raw.startsWith('Callable[')
|
|
394
|
+
) {
|
|
395
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
396
|
+
const parts = splitTopLevel(inner, ',');
|
|
397
|
+
if (parts.length >= 2) {
|
|
398
|
+
const paramsPart = (parts[0] ?? '').trim();
|
|
399
|
+
const returnPart = parts.slice(1).join(',').trim();
|
|
400
|
+
const returnType = parse(returnPart, depth + 1);
|
|
401
|
+
|
|
402
|
+
if (paramsPart === '...' || paramsPart === 'Ellipsis') {
|
|
403
|
+
return {
|
|
404
|
+
kind: 'callable',
|
|
405
|
+
parameters: [{ kind: 'custom', name: '...' }],
|
|
406
|
+
returnType,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const directParamSpec = parse(paramsPart, depth + 1);
|
|
411
|
+
if (directParamSpec.kind === 'paramspec') {
|
|
412
|
+
return {
|
|
413
|
+
kind: 'callable',
|
|
414
|
+
parameters: [],
|
|
415
|
+
parameterSpec: directParamSpec,
|
|
416
|
+
returnType,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const paramInner =
|
|
421
|
+
paramsPart.startsWith('[') && paramsPart.endsWith(']') ? paramsPart.slice(1, -1) : '';
|
|
422
|
+
const trimmed = paramInner.trim();
|
|
423
|
+
if (trimmed === '...' || trimmed === 'Ellipsis') {
|
|
424
|
+
return {
|
|
425
|
+
kind: 'callable',
|
|
426
|
+
parameters: [{ kind: 'custom', name: '...' }],
|
|
427
|
+
returnType,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const parameters = trimmed
|
|
432
|
+
? splitTopLevel(trimmed, ',').map(p => parse(p.trim(), depth + 1))
|
|
433
|
+
: [];
|
|
434
|
+
return { kind: 'callable', parameters, returnType };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
raw.startsWith('typing.Mapping[') ||
|
|
440
|
+
raw.startsWith('typing_extensions.Mapping[') ||
|
|
441
|
+
raw.startsWith('Mapping[')
|
|
442
|
+
) {
|
|
443
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
444
|
+
const parts = splitTopLevel(inner, ',');
|
|
445
|
+
return {
|
|
446
|
+
kind: 'collection',
|
|
447
|
+
name: 'dict',
|
|
448
|
+
itemTypes: parts.map(p => parse(p.trim(), depth + 1)),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (
|
|
453
|
+
raw.startsWith('typing.Annotated[') ||
|
|
454
|
+
raw.startsWith('typing_extensions.Annotated[') ||
|
|
455
|
+
raw.startsWith('Annotated[')
|
|
456
|
+
) {
|
|
457
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
458
|
+
const parts = splitTopLevel(inner, ',');
|
|
459
|
+
if (parts.length > 0) {
|
|
460
|
+
return {
|
|
461
|
+
kind: 'annotated',
|
|
462
|
+
base: parse(parts[0] ?? '', depth + 1),
|
|
463
|
+
metadata: parts.slice(1).map(p => String(p).trim()),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (
|
|
469
|
+
raw.startsWith('typing.Unpack[') ||
|
|
470
|
+
raw.startsWith('typing_extensions.Unpack[') ||
|
|
471
|
+
raw.startsWith('Unpack[')
|
|
472
|
+
) {
|
|
473
|
+
const inner = raw.slice(raw.indexOf('[') + 1, raw.lastIndexOf(']'));
|
|
474
|
+
return { kind: 'unpack', type: parse(inner, depth + 1) };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const coll = normalizeCollectionName(raw);
|
|
478
|
+
if (coll) {
|
|
479
|
+
const itemParts = splitTopLevel(coll.inner ?? '', ',');
|
|
480
|
+
const itemTypes = (coll.inner ? itemParts : []).map(p => parse(p.trim(), depth + 1));
|
|
481
|
+
return { kind: 'collection', name: coll.name, itemTypes };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const generic = splitGenericInvocation(raw);
|
|
485
|
+
if (generic) {
|
|
486
|
+
const typeArgs = splitTopLevel(generic.inner, ',').map(part => parse(part.trim(), depth + 1));
|
|
487
|
+
const qualified = splitQualifiedName(generic.name);
|
|
488
|
+
return {
|
|
489
|
+
kind: 'generic',
|
|
490
|
+
name: qualified.name,
|
|
491
|
+
module: qualified.module,
|
|
492
|
+
typeArgs,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return mapSimpleName(raw);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
return parse(annotation, 0);
|
|
500
|
+
}
|
package/src/core/discovery.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface DiscoveryOptions {
|
|
|
26
26
|
searchPaths?: string[];
|
|
27
27
|
excludePatterns?: string[];
|
|
28
28
|
includeStdLib?: boolean;
|
|
29
|
+
timeoutMs?: number;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export class ModuleDiscovery {
|
|
@@ -39,6 +40,13 @@ export class ModuleDiscovery {
|
|
|
39
40
|
includeStdLib: false,
|
|
40
41
|
excludePatterns: ['__pycache__', '*.pyc', '.git', '.svn'],
|
|
41
42
|
...options,
|
|
43
|
+
// Avoid "no timeout by default" for subprocess discovery paths.
|
|
44
|
+
timeoutMs:
|
|
45
|
+
typeof options.timeoutMs === 'number' &&
|
|
46
|
+
Number.isFinite(options.timeoutMs) &&
|
|
47
|
+
options.timeoutMs > 0
|
|
48
|
+
? options.timeoutMs
|
|
49
|
+
: 30000,
|
|
42
50
|
};
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -89,10 +97,14 @@ export class ModuleDiscovery {
|
|
|
89
97
|
if (processUtils.isAvailable()) {
|
|
90
98
|
try {
|
|
91
99
|
const pythonPath = await this.getPythonExecutable();
|
|
92
|
-
const result = await processUtils.exec(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
const result = await processUtils.exec(
|
|
101
|
+
pythonPath,
|
|
102
|
+
[
|
|
103
|
+
'-c',
|
|
104
|
+
`import ${moduleName}; print(${moduleName}.__file__ if hasattr(${moduleName}, '__file__') else '${moduleName}.__path__[0]' if hasattr(${moduleName}, '__path__') else 'builtin')`,
|
|
105
|
+
],
|
|
106
|
+
{ timeoutMs: this.options.timeoutMs }
|
|
107
|
+
);
|
|
96
108
|
|
|
97
109
|
if (result.code === 0 && result.stdout.trim() !== 'builtin') {
|
|
98
110
|
const modulePath = result.stdout.trim();
|
|
@@ -297,10 +309,11 @@ export class ModuleDiscovery {
|
|
|
297
309
|
if (processUtils.isAvailable()) {
|
|
298
310
|
try {
|
|
299
311
|
const pythonPath = await this.getPythonExecutable();
|
|
300
|
-
const result = await processUtils.exec(
|
|
301
|
-
|
|
302
|
-
'import sys; print("\\n".join(sys.path))',
|
|
303
|
-
|
|
312
|
+
const result = await processUtils.exec(
|
|
313
|
+
pythonPath,
|
|
314
|
+
['-c', 'import sys; print("\\n".join(sys.path))'],
|
|
315
|
+
{ timeoutMs: this.options.timeoutMs }
|
|
316
|
+
);
|
|
304
317
|
|
|
305
318
|
if (result.code === 0) {
|
|
306
319
|
const sysPaths = result.stdout
|
|
@@ -376,10 +389,11 @@ export class ModuleDiscovery {
|
|
|
376
389
|
|
|
377
390
|
try {
|
|
378
391
|
const pythonPath = await this.getPythonExecutable();
|
|
379
|
-
const result = await processUtils.exec(
|
|
380
|
-
|
|
381
|
-
`import ${moduleName}; print(getattr(${moduleName}, '__version__', 'unknown'))
|
|
382
|
-
|
|
392
|
+
const result = await processUtils.exec(
|
|
393
|
+
pythonPath,
|
|
394
|
+
['-c', `import ${moduleName}; print(getattr(${moduleName}, '__version__', 'unknown'))`],
|
|
395
|
+
{ timeoutMs: this.options.timeoutMs }
|
|
396
|
+
);
|
|
383
397
|
|
|
384
398
|
if (result.code === 0 && result.stdout.trim() !== 'unknown') {
|
|
385
399
|
return result.stdout.trim();
|