Skip to content

Commit f325366

Browse files
authored
Merge pull request #3440 from hey-api/chore/py-dsl-types
chore: clean up some assertions
2 parents 14f2894 + 406fa1c commit f325366

49 files changed

Lines changed: 813 additions & 398 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dev/inputs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const inputs = {
1919
export type InputKey = keyof typeof inputs;
2020

2121
export function getInput(key: InputKey = (process.env.INPUT as InputKey) || 'opencode') {
22-
const input = inputs[key];
22+
const input = inputs[key] || key;
2323
if (!input) {
2424
throw new Error(`Unknown input: ${key}. Available: ${Object.keys(inputs).join(', ')}`);
2525
}

packages/openapi-python/src/plugins/pydantic/shared/export.ts

Lines changed: 30 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ import { $ } from '../../../py-dsl';
55
import type { ProcessorContext } from './processor';
66
// import { identifiers } from '../v2/constants';
77
// import { pipesToNode } from './pipes';
8-
import type { Ast, IrSchemaToAstOptions } from './types';
8+
import type { PydanticFinal } from './types';
99

1010
export function exportAst({
11+
final,
1112
meta,
1213
naming,
1314
namingAnchor,
1415
path,
1516
plugin,
1617
tags,
17-
}: Pick<IrSchemaToAstOptions, 'state'> &
18-
ProcessorContext & {
19-
ast: Ast;
20-
}): void {
18+
}: ProcessorContext & {
19+
final: PydanticFinal;
20+
}): void {
2121
const name = pathToName(path, { anchor: namingAnchor });
2222
const symbol = plugin.symbol(applyNaming(name, naming), {
2323
meta: {
@@ -29,54 +29,29 @@ export function exportAst({
2929
},
3030
});
3131

32-
const baseModel = plugin.external('pydantic.BaseModel');
33-
const classDef = $.class(symbol).extends(baseModel);
34-
// .export()
35-
// .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v))
36-
// .$if(state.hasLazyExpression['~ref'], (c) =>
37-
// c.type($.type(v).attr(ast.typeName || identifiers.types.GenericSchema)),
38-
// )
39-
// .assign(pipesToNode(ast.pipes, plugin));
40-
plugin.node(classDef);
41-
// if (schema.type === 'object' && schema.properties) {
42-
// const baseModelSymbol = plugin.external('pydantic.BaseModel');
43-
// const fieldSymbol = plugin.external('pydantic.Field');
44-
// const classDef = $.class(symbol).extends(baseModelSymbol);
45-
46-
// if (plugin.config.comments && schema.description) {
47-
// classDef.doc(schema.description);
48-
// }
49-
50-
// for (const name in schema.properties) {
51-
// const property = schema.properties[name]!;
52-
// const isOptional = !schema.required?.includes(name);
53-
54-
// const propertyAst = irSchemaToAst({
55-
// optional: isOptional,
56-
// plugin,
57-
// schema: property,
58-
// state: {
59-
// ...state,
60-
// path: ref([...fromRef(state.path), 'properties', name]),
61-
// },
62-
// });
63-
64-
// let typeAnnotation = propertyAst.typeAnnotation;
65-
66-
// if (isOptional && !typeAnnotation.startsWith('Optional[')) {
67-
// typeAnnotation = `Optional[${typeAnnotation}]`;
68-
// }
69-
70-
// if (propertyAst.fieldConstraints && Object.keys(propertyAst.fieldConstraints).length > 0) {
71-
// const constraints = Object.entries(propertyAst.fieldConstraints)
72-
// .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
73-
// .join(', ');
74-
// classDef.do($.stmt($.expr(`${name}: ${typeAnnotation} = Field(${constraints})`)));
75-
// } else {
76-
// classDef.do($.stmt($.expr(`${name}: ${typeAnnotation}`)));
77-
// }
78-
// }
79-
80-
// plugin.node(classDef);
81-
// }
32+
if (final.fields) {
33+
const baseModel = plugin.external('pydantic.BaseModel');
34+
const classDef = $.class(symbol).extends(baseModel);
35+
// .export()
36+
// .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v))
37+
// .$if(state.hasLazyExpression['~ref'], (c) =>
38+
// c.type($.type(v).attr(ast.typeName || identifiers.types.GenericSchema)),
39+
// )
40+
// .assign(pipesToNode(ast.pipes, plugin));
41+
42+
for (const field of final.fields) {
43+
// TODO: Field(...) constraints in next pass
44+
classDef.do($.var(field.name).assign($.literal('hey')));
45+
// classDef.do($.var(field.name).annotate(field.typeAnnotation));
46+
}
47+
48+
plugin.node(classDef);
49+
} else {
50+
const statement = $.var(symbol)
51+
// .export()
52+
// .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v))
53+
.assign(final.typeAnnotation);
54+
55+
plugin.node(statement);
56+
}
8257
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { IR } from '@hey-api/shared';
2+
3+
import type { PydanticMeta, PydanticResult } from './types';
4+
5+
/**
6+
* Creates default metadata from a schema.
7+
*/
8+
export function defaultMeta(schema: IR.SchemaObject): PydanticMeta {
9+
return {
10+
default: schema.default,
11+
format: schema.format,
12+
hasLazy: false,
13+
nullable: false,
14+
readonly: schema.accessScope === 'read',
15+
};
16+
}
17+
18+
/**
19+
* Composes metadata from child results.
20+
*
21+
* Automatically propagates hasLazy, nullable, readonly from children.
22+
*
23+
* @param children - Results from walking child schemas
24+
* @param overrides - Explicit overrides (e.g., from parent schema)
25+
*/
26+
export function composeMeta(
27+
children: ReadonlyArray<PydanticResult>,
28+
overrides?: Partial<PydanticMeta>,
29+
): PydanticMeta {
30+
return {
31+
default: overrides?.default,
32+
format: overrides?.format,
33+
hasLazy: overrides?.hasLazy ?? children.some((c) => c.meta.hasLazy),
34+
nullable: overrides?.nullable ?? children.some((c) => c.meta.nullable),
35+
readonly: overrides?.readonly ?? children.some((c) => c.meta.readonly),
36+
};
37+
}
38+
39+
/**
40+
* Merges parent schema metadata with composed child metadata.
41+
*
42+
* @param parent - The parent schema
43+
* @param children - Results from walking child schemas
44+
*/
45+
export function inheritMeta(
46+
parent: IR.SchemaObject,
47+
children: ReadonlyArray<PydanticResult>,
48+
): PydanticMeta {
49+
return composeMeta(children, {
50+
default: parent.default,
51+
format: parent.format,
52+
nullable: false,
53+
readonly: parent.accessScope === 'read',
54+
});
55+
}

packages/openapi-python/src/plugins/pydantic/shared/types.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Refs, Symbol, SymbolMeta } from '@hey-api/codegen-core';
22
import type { IR, SchemaExtractor } from '@hey-api/shared';
33

4-
import type { $ } from '../../../py-dsl';
4+
import type { $, MaybePyDsl } from '../../../py-dsl';
5+
import type { py } from '../../../ts-python';
56
import type { PydanticPlugin } from '../types';
67
import type { ProcessorContext } from './processor';
78

@@ -65,3 +66,56 @@ export interface ResolverContext {
6566
*/
6667
schema: IR.SchemaObject;
6768
}
69+
70+
// ..... ^^^^^^ OLD
71+
72+
/**
73+
* Metadata that flows through schema walking.
74+
*/
75+
export interface PydanticMeta {
76+
/** Default value from schema. */
77+
default?: unknown;
78+
/** Original format (for BigInt coercion). */
79+
format?: string;
80+
/** Whether this or any child contains a lazy reference. */
81+
hasLazy: boolean;
82+
/** Does this schema explicitly allow null? */
83+
nullable: boolean;
84+
/** Is this schema read-only? */
85+
readonly: boolean;
86+
}
87+
88+
/**
89+
* Result from walking a schema node.
90+
*/
91+
export interface PydanticResult {
92+
fieldConstraints: Record<string, unknown>;
93+
fields?: Array<PydanticField>;
94+
meta: PydanticMeta;
95+
typeAnnotation: string | MaybePyDsl<py.Expression>;
96+
}
97+
98+
export interface PydanticField {
99+
fieldConstraints: Record<string, unknown>;
100+
isOptional: boolean;
101+
name: string;
102+
typeAnnotation: string | MaybePyDsl<py.Expression>;
103+
}
104+
105+
/**
106+
* Finalized result after applyModifiers.
107+
*/
108+
export interface PydanticFinal {
109+
fieldConstraints: Record<string, unknown>;
110+
fields?: Array<PydanticField>; // present = emit class, absent = emit type alias
111+
typeAnnotation: string | MaybePyDsl<py.Expression>;
112+
}
113+
114+
/**
115+
* Result from composite handlers that walk children.
116+
*/
117+
export interface PydanticCompositeHandlerResult {
118+
childResults: Array<PydanticResult>;
119+
fieldConstraints: Record<string, unknown>;
120+
typeAnnotation: string | MaybePyDsl<py.Expression>;
121+
}

packages/openapi-python/src/plugins/pydantic/v2/plugin.ts

Lines changed: 1 addition & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,8 @@
1-
import type { SymbolMeta } from '@hey-api/codegen-core';
2-
import { fromRef, ref } from '@hey-api/codegen-core';
3-
import type { IR, SchemaWithType } from '@hey-api/shared';
4-
import { deduplicateSchema, pathToJsonPointer } from '@hey-api/shared';
1+
import { pathToJsonPointer } from '@hey-api/shared';
52

63
// import { $ } from '../../../py-dsl';
7-
import type { Ast, IrSchemaToAstOptions } from '../shared/types';
84
import type { PydanticPlugin } from '../types';
95
import { createProcessor } from './processor';
10-
import { irSchemaWithTypeToAst } from './toAst';
11-
12-
export function irSchemaToAst({
13-
optional,
14-
plugin,
15-
schema,
16-
schemaExtractor,
17-
state,
18-
}: IrSchemaToAstOptions & {
19-
optional?: boolean;
20-
schema: IR.SchemaObject;
21-
}): Ast {
22-
if (schemaExtractor && !schema.$ref) {
23-
const extracted = schemaExtractor({
24-
meta: {
25-
resource: 'definition',
26-
resourceId: pathToJsonPointer(fromRef(state.path)),
27-
},
28-
naming: plugin.config.definitions,
29-
path: fromRef(state.path),
30-
plugin,
31-
schema,
32-
});
33-
if (extracted !== schema) schema = extracted;
34-
}
35-
36-
if (schema.$ref) {
37-
const query: SymbolMeta = {
38-
category: 'schema',
39-
resource: 'definition',
40-
resourceId: schema.$ref,
41-
tool: 'pydantic',
42-
};
43-
const refSymbol = plugin.referenceSymbol(query);
44-
const refName = typeof refSymbol === 'string' ? refSymbol : refSymbol.name;
45-
46-
return {
47-
// expression: $.expr(refName),
48-
fieldConstraints: optional ? { default: null } : undefined,
49-
hasLazyExpression: !plugin.isSymbolRegistered(query),
50-
models: [],
51-
// pipes: [],
52-
typeAnnotation: refName,
53-
};
54-
}
55-
56-
if (schema.type) {
57-
const typeAst = irSchemaWithTypeToAst({
58-
plugin,
59-
schema: schema as SchemaWithType,
60-
state,
61-
});
62-
63-
const constraints: Record<string, unknown> = {};
64-
if (optional) {
65-
constraints.default = null;
66-
}
67-
if (schema.default !== undefined) {
68-
constraints.default = schema.default;
69-
}
70-
if (schema.description) {
71-
constraints.description = schema.description;
72-
}
73-
74-
return {
75-
...typeAst,
76-
fieldConstraints: { ...typeAst.fieldConstraints, ...constraints },
77-
// pipes: [],
78-
};
79-
}
80-
81-
if (schema.items) {
82-
schema = deduplicateSchema({ schema });
83-
84-
if (schema.items) {
85-
const itemsAnnotations: string[] = [];
86-
const itemsConstraints: Record<string, unknown>[] = [];
87-
88-
for (const item of schema.items) {
89-
const itemAst = irSchemaToAst({
90-
plugin,
91-
schema: item,
92-
state: {
93-
...state,
94-
path: ref([...fromRef(state.path), 'items']),
95-
},
96-
});
97-
itemsAnnotations.push(itemAst.typeAnnotation);
98-
if (itemAst.fieldConstraints) {
99-
itemsConstraints.push(itemAst.fieldConstraints);
100-
}
101-
}
102-
103-
const unionType = itemsAnnotations.join(' | ');
104-
return {
105-
// expression: $.expr(`list[${unionType}]`),
106-
fieldConstraints: itemsConstraints.length > 0 ? itemsConstraints[0] : undefined,
107-
hasLazyExpression: false,
108-
models: [],
109-
// pipes: [],
110-
typeAnnotation: `list[${unionType}]`,
111-
};
112-
}
113-
}
114-
115-
return {
116-
// expression: $.expr('Any'),
117-
hasLazyExpression: false,
118-
models: [],
119-
// pipes: [],
120-
typeAnnotation: 'Any',
121-
};
122-
}
1236

1247
export const handlerV2: PydanticPlugin['Handler'] = ({ plugin }) => {
1258
plugin.symbol('Any', {

0 commit comments

Comments
 (0)