fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #3549 from hey-api/feat/pydantic-resolvers

feat: add more resolvers to pydantic

authored by

Lubos and committed by
GitHub
13fcece7 c34225fa

+702 -179
+10
packages/openapi-python/src/plugins/pydantic/index.ts
··· 1 1 export { defaultConfig, defineConfig } from './config'; 2 2 export type { 3 + ArrayResolverContext, 4 + BooleanResolverContext, 3 5 EnumResolverContext, 6 + IntersectionResolverContext, 7 + NeverResolverContext, 8 + NullResolverContext, 4 9 NumberResolverContext, 5 10 ObjectResolverContext, 6 11 Resolvers, 7 12 StringResolverContext, 13 + TupleResolverContext, 14 + UndefinedResolverContext, 15 + UnionResolverContext, 16 + UnknownResolverContext, 17 + VoidResolverContext, 8 18 } from './resolvers'; 9 19 export type { PydanticPlugin } from './types';
+200 -1
packages/openapi-python/src/plugins/pydantic/resolvers.ts
··· 1 - import type { Plugin, SchemaVisitorContext, SchemaWithType, Walker } from '@hey-api/shared'; 1 + import type { IR, Plugin, SchemaVisitorContext, SchemaWithType, Walker } from '@hey-api/shared'; 2 2 3 3 import type { DollarPyDsl } from '../../py-dsl'; 4 4 import type { PydanticField, PydanticFinal, PydanticResult, PydanticType } from './shared/types'; ··· 6 6 7 7 export type Resolvers = Plugin.Resolvers<{ 8 8 /** 9 + * Resolver for array schemas. 10 + * 11 + * Allows customization of how array types are rendered. 12 + * 13 + * Returning `undefined` will execute the default resolver logic. 14 + */ 15 + array?: (ctx: ArrayResolverContext) => PydanticType | undefined; 16 + /** 17 + * Resolver for boolean schemas. 18 + * 19 + * Allows customization of how boolean types are rendered. 20 + * 21 + * Returning `undefined` will execute the default resolver logic. 22 + */ 23 + boolean?: (ctx: BooleanResolverContext) => PydanticType | undefined; 24 + /** 9 25 * Resolver for enum schemas. 10 26 * 11 27 * Allows customization of how enum types are rendered. ··· 14 30 */ 15 31 enum?: (ctx: EnumResolverContext) => PydanticType | undefined; 16 32 /** 33 + * Resolver for intersection schemas. 34 + * 35 + * Allows customization of how intersection types are rendered. 36 + * 37 + * Returning `undefined` will execute the default resolver logic. 38 + */ 39 + intersection?: (ctx: IntersectionResolverContext) => PydanticType | undefined; 40 + /** 41 + * Resolver for never schemas. 42 + * 43 + * Allows customization of how never types are rendered. 44 + * 45 + * Returning `undefined` will execute the default resolver logic. 46 + */ 47 + never?: (ctx: NeverResolverContext) => PydanticType | undefined; 48 + /** 49 + * Resolver for null schemas. 50 + * 51 + * Allows customization of how null types are rendered. 52 + * 53 + * Returning `undefined` will execute the default resolver logic. 54 + */ 55 + null?: (ctx: NullResolverContext) => PydanticType | undefined; 56 + /** 17 57 * Resolver for number schemas. 18 58 * 19 59 * Allows customization of how number types are rendered. ··· 39 79 * Returning `undefined` will execute the default resolver logic. 40 80 */ 41 81 string?: (ctx: StringResolverContext) => PydanticType | undefined; 82 + /** 83 + * Resolver for tuple schemas. 84 + * 85 + * Allows customization of how tuple types are rendered. 86 + * 87 + * Returning `undefined` will execute the default resolver logic. 88 + */ 89 + tuple?: (ctx: TupleResolverContext) => PydanticType | undefined; 90 + /** 91 + * Resolver for undefined schemas. 92 + * 93 + * Allows customization of how undefined types are rendered. 94 + * 95 + * Returning `undefined` will execute the default resolver logic. 96 + */ 97 + undefined?: (ctx: UndefinedResolverContext) => PydanticType | undefined; 98 + /** 99 + * Resolver for union schemas. 100 + * 101 + * Allows customization of how union types are rendered. 102 + * 103 + * Returning `undefined` will execute the default resolver logic. 104 + */ 105 + union?: (ctx: UnionResolverContext) => PydanticType | undefined; 106 + /** 107 + * Resolver for unknown schemas. 108 + * 109 + * Allows customization of how unknown types are rendered. 110 + * 111 + * Returning `undefined` will execute the default resolver logic. 112 + */ 113 + unknown?: (ctx: UnknownResolverContext) => PydanticType | undefined; 114 + /** 115 + * Resolver for void schemas. 116 + * 117 + * Allows customization of how void types are rendered. 118 + * 119 + * Returning `undefined` will execute the default resolver logic. 120 + */ 121 + void?: (ctx: VoidResolverContext) => PydanticType | undefined; 42 122 }>; 43 123 44 124 interface BaseContext extends DollarPyDsl { ··· 46 126 plugin: PydanticPlugin['Instance']; 47 127 } 48 128 129 + export interface ArrayResolverContext extends BaseContext { 130 + applyModifiers: (result: PydanticResult, opts?: { optional?: boolean }) => PydanticFinal; 131 + childResults: Array<PydanticResult>; 132 + /** 133 + * Nodes used to build different parts of the result. 134 + */ 135 + nodes: { 136 + base: (ctx: ArrayResolverContext) => PydanticType; 137 + maxLength: (ctx: ArrayResolverContext) => PydanticType | undefined; 138 + minLength: (ctx: ArrayResolverContext) => PydanticType | undefined; 139 + }; 140 + schema: SchemaWithType<'array'>; 141 + walk: Walker<PydanticResult, PydanticPlugin['Instance']>; 142 + walkerCtx: SchemaVisitorContext<PydanticPlugin['Instance']>; 143 + } 144 + 145 + export interface BooleanResolverContext extends BaseContext { 146 + /** 147 + * Nodes used to build different parts of the result. 148 + */ 149 + nodes: { 150 + base: (ctx: BooleanResolverContext) => PydanticType; 151 + const: (ctx: BooleanResolverContext) => PydanticType | undefined; 152 + }; 153 + schema: SchemaWithType<'boolean'>; 154 + } 155 + 49 156 export interface EnumResolverContext extends BaseContext { 50 157 /** 51 158 * Nodes used to build different parts of the result. ··· 60 167 schema: SchemaWithType<'enum'>; 61 168 } 62 169 170 + export interface IntersectionResolverContext extends BaseContext { 171 + applyModifiers: (result: PydanticResult, opts?: { optional?: boolean }) => PydanticFinal; 172 + childResults: Array<PydanticResult>; 173 + /** 174 + * Nodes used to build different parts of the result. 175 + */ 176 + nodes: { 177 + base: (ctx: IntersectionResolverContext) => PydanticType; 178 + }; 179 + parentSchema: IR.SchemaObject; 180 + schema: IR.SchemaObject; 181 + } 182 + 183 + export interface NeverResolverContext extends BaseContext { 184 + /** 185 + * Nodes used to build different parts of the result. 186 + */ 187 + nodes: { 188 + base: (ctx: NeverResolverContext) => PydanticType; 189 + }; 190 + schema: SchemaWithType<'never'>; 191 + } 192 + 193 + export interface NullResolverContext extends BaseContext { 194 + /** 195 + * Nodes used to build different parts of the result. 196 + */ 197 + nodes: { 198 + base: (ctx: NullResolverContext) => PydanticType; 199 + }; 200 + schema: SchemaWithType<'null'>; 201 + } 202 + 63 203 export interface NumberResolverContext extends BaseContext { 64 204 /** 65 205 * Nodes used to build different parts of the result. ··· 97 237 }; 98 238 schema: SchemaWithType<'string'>; 99 239 } 240 + 241 + export interface TupleResolverContext extends BaseContext { 242 + applyModifiers: (result: PydanticResult, opts?: { optional?: boolean }) => PydanticFinal; 243 + childResults: Array<PydanticResult>; 244 + /** 245 + * Nodes used to build different parts of the result. 246 + */ 247 + nodes: { 248 + base: (ctx: TupleResolverContext) => PydanticType; 249 + const: (ctx: TupleResolverContext) => PydanticType | undefined; 250 + }; 251 + schema: SchemaWithType<'tuple'>; 252 + walk: Walker<PydanticResult, PydanticPlugin['Instance']>; 253 + walkerCtx: SchemaVisitorContext<PydanticPlugin['Instance']>; 254 + } 255 + 256 + export interface UndefinedResolverContext extends BaseContext { 257 + /** 258 + * Nodes used to build different parts of the result. 259 + */ 260 + nodes: { 261 + base: (ctx: UndefinedResolverContext) => PydanticType; 262 + }; 263 + schema: SchemaWithType<'undefined'>; 264 + } 265 + 266 + export interface UnionResolverContext extends BaseContext { 267 + applyModifiers: (result: PydanticResult, opts?: { optional?: boolean }) => PydanticFinal; 268 + childResults: Array<PydanticResult>; 269 + /** 270 + * Nodes used to build different parts of the result. 271 + */ 272 + nodes: { 273 + base: (ctx: UnionResolverContext) => PydanticType; 274 + }; 275 + parentSchema: IR.SchemaObject; 276 + schema: IR.SchemaObject; 277 + schemas: ReadonlyArray<IR.SchemaObject>; 278 + } 279 + 280 + export interface UnknownResolverContext extends BaseContext { 281 + /** 282 + * Nodes used to build different parts of the result. 283 + */ 284 + nodes: { 285 + base: (ctx: UnknownResolverContext) => PydanticType; 286 + }; 287 + schema: SchemaWithType<'unknown'>; 288 + } 289 + 290 + export interface VoidResolverContext extends BaseContext { 291 + /** 292 + * Nodes used to build different parts of the result. 293 + */ 294 + nodes: { 295 + base: (ctx: VoidResolverContext) => PydanticType; 296 + }; 297 + schema: SchemaWithType<'void'>; 298 + }
+97 -49
packages/openapi-python/src/plugins/pydantic/v2/toAst/array.ts
··· 2 2 import { childContext, deduplicateSchema } from '@hey-api/shared'; 3 3 4 4 import { $ } from '../../../../py-dsl'; 5 + import type { ArrayResolverContext } from '../../resolvers'; 5 6 import type { PydanticFinal, PydanticResult, PydanticType } from '../../shared/types'; 6 7 import type { PydanticPlugin } from '../../types'; 7 8 import type { FieldConstraints } from '../constants'; 8 9 9 - interface ArrayToTypeContext { 10 - applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; 11 - plugin: PydanticPlugin['Instance']; 12 - schema: SchemaWithType<'array'>; 13 - walk: Walker<PydanticResult, PydanticPlugin['Instance']>; 14 - walkerCtx: SchemaVisitorContext<PydanticPlugin['Instance']>; 15 - } 16 - 17 - export interface ArrayToTypeResult extends PydanticType { 18 - childResults: Array<PydanticResult>; 19 - } 20 - 21 - export function arrayToType(ctx: ArrayToTypeContext): ArrayToTypeResult { 22 - const { plugin, walk, walkerCtx } = ctx; 23 - let { schema } = ctx; 24 - 25 - const childResults: Array<PydanticResult> = []; 26 - const constraints: FieldConstraints = {}; 10 + function baseNode(ctx: ArrayResolverContext): PydanticType { 11 + const { applyModifiers, childResults, plugin } = ctx; 27 12 const list = plugin.external('typing.List'); 28 13 const any = plugin.external('typing.Any'); 29 14 30 - if (schema.minItems !== undefined) { 31 - constraints.min_length = schema.minItems; 32 - } 33 - 34 - if (schema.maxItems !== undefined) { 35 - constraints.max_length = schema.maxItems; 36 - } 37 - 38 - if (schema.description !== undefined) { 39 - constraints.description = schema.description; 40 - } 41 - 42 - if (!schema.items) { 15 + if (childResults.length === 0) { 43 16 return { 44 - childResults, 45 - fieldConstraints: constraints, 46 17 type: $(list).slice(any), 47 18 }; 48 19 } 49 20 50 - schema = deduplicateSchema({ schema }); 51 - 52 - for (let i = 0; i < schema.items!.length; i++) { 53 - const item = schema.items![i]!; 54 - const result = walk(item, childContext(walkerCtx, 'items', i)); 55 - childResults.push(result); 56 - } 57 - 58 21 if (childResults.length === 1) { 59 - const itemResult = ctx.applyModifiers(childResults[0]!); 22 + const itemResult = applyModifiers(childResults[0]!); 60 23 return { 61 - childResults, 62 - fieldConstraints: constraints, 63 24 type: $(list).slice(itemResult.type ?? any), 64 25 }; 65 26 } 66 27 67 28 if (childResults.length > 1) { 68 29 const union = plugin.external('typing.Union'); 69 - const itemTypes = childResults.map((r) => ctx.applyModifiers(r).type ?? any); 30 + const itemTypes = childResults.map((r) => applyModifiers(r).type ?? any); 70 31 return { 71 - childResults, 72 - fieldConstraints: constraints, 73 32 type: $(list).slice($(union).slice(...itemTypes)), 74 33 }; 75 34 } 76 35 77 36 return { 78 - childResults, 79 - fieldConstraints: constraints, 80 37 type: $(list).slice(any), 81 38 }; 82 39 } 40 + 41 + function minLengthNode(ctx: ArrayResolverContext): PydanticType | undefined { 42 + const { schema } = ctx; 43 + if (schema.minItems === undefined) return; 44 + return { 45 + fieldConstraints: { min_length: schema.minItems }, 46 + }; 47 + } 48 + 49 + function maxLengthNode(ctx: ArrayResolverContext): PydanticType | undefined { 50 + const { schema } = ctx; 51 + if (schema.maxItems === undefined) return; 52 + return { 53 + fieldConstraints: { max_length: schema.maxItems }, 54 + }; 55 + } 56 + 57 + function arrayResolver(ctx: ArrayResolverContext): PydanticType { 58 + const baseResult = ctx.nodes.base(ctx); 59 + const minLengthResult = ctx.nodes.minLength(ctx); 60 + const maxLengthResult = ctx.nodes.maxLength(ctx); 61 + 62 + const fieldConstraints: FieldConstraints = { 63 + ...(baseResult.fieldConstraints ?? {}), 64 + ...(minLengthResult?.fieldConstraints ?? {}), 65 + ...(maxLengthResult?.fieldConstraints ?? {}), 66 + }; 67 + 68 + if (ctx.schema.description !== undefined) { 69 + fieldConstraints.description = ctx.schema.description; 70 + } 71 + 72 + return { 73 + ...baseResult, 74 + fieldConstraints: Object.keys(fieldConstraints).length > 0 ? fieldConstraints : undefined, 75 + }; 76 + } 77 + 78 + export interface ArrayToTypeResult extends PydanticType { 79 + childResults: Array<PydanticResult>; 80 + } 81 + 82 + export function arrayToType(ctx: { 83 + applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; 84 + plugin: PydanticPlugin['Instance']; 85 + schema: SchemaWithType<'array'>; 86 + walk: Walker<PydanticResult, PydanticPlugin['Instance']>; 87 + walkerCtx: SchemaVisitorContext<PydanticPlugin['Instance']>; 88 + }): ArrayToTypeResult { 89 + const { applyModifiers, plugin, schema, walk, walkerCtx } = ctx; 90 + const any = plugin.external('typing.Any'); 91 + const list = plugin.external('typing.List'); 92 + 93 + const childResults: Array<PydanticResult> = []; 94 + 95 + if (schema.items) { 96 + const normalizedSchema = deduplicateSchema({ schema }); 97 + for (let i = 0; i < normalizedSchema.items!.length; i++) { 98 + const item = normalizedSchema.items![i]!; 99 + const result = walk(item, childContext(walkerCtx, 'items', i)); 100 + childResults.push(result); 101 + } 102 + } 103 + 104 + const resolverCtx: ArrayResolverContext = { 105 + $, 106 + applyModifiers, 107 + childResults, 108 + nodes: { 109 + base: baseNode, 110 + maxLength: maxLengthNode, 111 + minLength: minLengthNode, 112 + }, 113 + plugin, 114 + schema, 115 + walk, 116 + walkerCtx, 117 + }; 118 + 119 + const resolver = plugin.config['~resolvers']?.array; 120 + const resolved = resolver?.(resolverCtx) ?? arrayResolver(resolverCtx); 121 + 122 + if (!resolved.type) { 123 + resolved.type = $(list).slice(any); 124 + } 125 + 126 + return { 127 + ...resolved, 128 + childResults, 129 + }; 130 + }
+35 -7
packages/openapi-python/src/plugins/pydantic/v2/toAst/boolean.ts
··· 1 1 import type { SchemaWithType } from '@hey-api/shared'; 2 2 3 3 import { $ } from '../../../../py-dsl'; 4 + import type { BooleanResolverContext } from '../../resolvers'; 4 5 import type { PydanticType } from '../../shared/types'; 5 6 import type { PydanticPlugin } from '../../types'; 6 7 7 - export function booleanToType({ 8 - plugin, 9 - schema, 10 - }: { 11 - plugin: PydanticPlugin['Instance']; 12 - schema: SchemaWithType<'boolean'>; 13 - }): PydanticType { 8 + function constNode(ctx: BooleanResolverContext): PydanticType | undefined { 9 + const { plugin, schema } = ctx; 10 + 14 11 if (typeof schema.const === 'boolean') { 15 12 const literal = plugin.external('typing.Literal'); 16 13 return { 17 14 type: $(literal).slice($.literal(schema.const)), 18 15 }; 19 16 } 17 + } 20 18 19 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 20 + function baseNode(_ctx: BooleanResolverContext): PydanticType { 21 21 return { 22 22 type: 'bool', 23 23 }; 24 24 } 25 + 26 + function booleanResolver(ctx: BooleanResolverContext): PydanticType { 27 + const constResult = ctx.nodes.const(ctx); 28 + if (constResult) return constResult; 29 + 30 + return ctx.nodes.base(ctx); 31 + } 32 + 33 + export function booleanToType({ 34 + plugin, 35 + schema, 36 + }: { 37 + plugin: PydanticPlugin['Instance']; 38 + schema: SchemaWithType<'boolean'>; 39 + }): PydanticType { 40 + const ctx: BooleanResolverContext = { 41 + $, 42 + nodes: { 43 + base: baseNode, 44 + const: constNode, 45 + }, 46 + plugin, 47 + schema, 48 + }; 49 + 50 + const resolver = plugin.config['~resolvers']?.boolean; 51 + return resolver?.(ctx) ?? booleanResolver(ctx); 52 + }
+87 -30
packages/openapi-python/src/plugins/pydantic/v2/toAst/intersection.ts
··· 1 1 import type { IR } from '@hey-api/shared'; 2 2 3 - import type { VarType } from '../../../../py-dsl'; 3 + import { $, type VarType } from '../../../../py-dsl'; 4 + import type { IntersectionResolverContext } from '../../resolvers'; 4 5 import type { 5 6 PydanticField, 6 7 PydanticFinal, ··· 10 11 import type { PydanticPlugin } from '../../types'; 11 12 import type { FieldConstraints } from '../constants'; 12 13 14 + function baseNode(ctx: IntersectionResolverContext): PydanticType { 15 + const { applyModifiers, childResults, plugin } = ctx; 16 + 17 + if (childResults.length === 0) { 18 + return { 19 + type: plugin.external('typing.Any'), 20 + }; 21 + } 22 + 23 + if (childResults.length === 1) { 24 + const finalResult = applyModifiers(childResults[0]!); 25 + return finalResult; 26 + } 27 + 28 + const baseClasses: Array<string> = []; 29 + const mergedFields: Array<PydanticField> = []; 30 + const seenFieldIds = new Set<number>(); 31 + 32 + for (const result of childResults) { 33 + const finalResult = applyModifiers(result); 34 + 35 + // TODO: replace 36 + const typeStr = String(finalResult.type); 37 + const isReference = 38 + !finalResult.fields && 39 + typeStr !== '' && 40 + !typeStr.startsWith('dict[') && 41 + !typeStr.startsWith('Dict[') && 42 + typeStr !== String(plugin.external('typing.Any')); 43 + 44 + if (isReference) { 45 + const baseName = typeStr.replace(/^'|'$/g, ''); 46 + if (baseName && !baseClasses.includes(baseName)) { 47 + baseClasses.push(baseName); 48 + } 49 + } 50 + 51 + if (finalResult.fields) { 52 + for (const field of finalResult.fields) { 53 + if (!seenFieldIds.has(field.name.id)) { 54 + seenFieldIds.add(field.name.id); 55 + mergedFields.push(field); 56 + } 57 + } 58 + } 59 + } 60 + 61 + let type: VarType; 62 + 63 + if (baseClasses.length > 0 && mergedFields.length === 0) { 64 + type = baseClasses[0]!; 65 + } else if (mergedFields.length > 0) { 66 + // TODO: replace 67 + type = '__INTERSECTION_PLACEHOLDER__'; 68 + } else { 69 + type = plugin.external('typing.Any'); 70 + } 71 + 72 + return { 73 + type, 74 + }; 75 + } 76 + 77 + function intersectionResolver(ctx: IntersectionResolverContext): PydanticType { 78 + return ctx.nodes.base(ctx); 79 + } 80 + 13 81 export interface IntersectionToTypeResult extends PydanticType { 14 82 baseClasses?: Array<string>; 15 83 childResults: Array<PydanticResult>; ··· 33 101 constraints.description = parentSchema.description; 34 102 } 35 103 36 - if (childResults.length === 0) { 37 - return { 38 - childResults, 39 - fieldConstraints: constraints, 40 - type: plugin.external('typing.Any'), 41 - }; 42 - } 104 + const resolverCtx: IntersectionResolverContext = { 105 + $, 106 + applyModifiers, 107 + childResults, 108 + nodes: { 109 + base: baseNode, 110 + }, 111 + parentSchema, 112 + plugin, 113 + schema: parentSchema, 114 + }; 43 115 44 - if (childResults.length === 1) { 45 - const finalResult = applyModifiers(childResults[0]!); 46 - return { 47 - childResults, 48 - fieldConstraints: { ...constraints, ...finalResult.fieldConstraints }, 49 - mergedFields: finalResult.fields, 50 - type: finalResult.type, 51 - }; 52 - } 116 + const resolver = plugin.config['~resolvers']?.intersection; 117 + const resolved = resolver?.(resolverCtx) ?? intersectionResolver(resolverCtx); 53 118 54 119 const baseClasses: Array<string> = []; 55 120 const mergedFields: Array<PydanticField> = []; ··· 84 149 } 85 150 } 86 151 87 - let type: VarType; 88 - 89 - if (baseClasses.length > 0 && mergedFields.length === 0) { 90 - type = baseClasses[0]!; 91 - } else if (mergedFields.length > 0) { 92 - // TODO: replace 93 - type = '__INTERSECTION_PLACEHOLDER__'; 94 - } else { 95 - type = plugin.external('typing.Any'); 96 - } 97 - 98 152 return { 153 + ...resolved, 99 154 baseClasses: baseClasses.length > 0 ? baseClasses : undefined, 100 155 childResults, 101 - fieldConstraints: constraints, 156 + fieldConstraints: 157 + Object.keys(constraints).length > 0 158 + ? { ...constraints, ...resolved.fieldConstraints } 159 + : resolved.fieldConstraints, 102 160 mergedFields: mergedFields.length > 0 ? mergedFields : undefined, 103 - type, 104 161 }; 105 162 }
+24 -2
packages/openapi-python/src/plugins/pydantic/v2/toAst/never.ts
··· 1 1 import type { SchemaWithType } from '@hey-api/shared'; 2 2 3 + import { $ } from '../../../../py-dsl'; 4 + import type { NeverResolverContext } from '../../resolvers'; 3 5 import type { PydanticType } from '../../shared/types'; 4 6 import type { PydanticPlugin } from '../../types'; 7 + 8 + function baseNode(ctx: NeverResolverContext): PydanticType { 9 + const { plugin } = ctx; 10 + return { 11 + type: plugin.external('typing.NoReturn'), 12 + }; 13 + } 14 + 15 + function neverResolver(ctx: NeverResolverContext): PydanticType { 16 + return ctx.nodes.base(ctx); 17 + } 5 18 6 19 export function neverToType({ 7 20 plugin, 21 + schema, 8 22 }: { 9 23 plugin: PydanticPlugin['Instance']; 10 24 schema: SchemaWithType<'never'>; 11 25 }): PydanticType { 12 - return { 13 - type: plugin.external('typing.NoReturn'), 26 + const ctx: NeverResolverContext = { 27 + $, 28 + nodes: { 29 + base: baseNode, 30 + }, 31 + plugin, 32 + schema, 14 33 }; 34 + 35 + const resolver = plugin.config['~resolvers']?.never; 36 + return resolver?.(ctx) ?? neverResolver(ctx); 15 37 }
+26 -3
packages/openapi-python/src/plugins/pydantic/v2/toAst/null.ts
··· 1 1 import type { SchemaWithType } from '@hey-api/shared'; 2 2 3 + import { $ } from '../../../../py-dsl'; 4 + import type { NullResolverContext } from '../../resolvers'; 3 5 import type { PydanticType } from '../../shared/types'; 4 6 import type { PydanticPlugin } from '../../types'; 5 7 6 8 // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 - export function nullToType(args: { 9 + function baseNode(_ctx: NullResolverContext): PydanticType { 10 + return { 11 + type: 'None', 12 + }; 13 + } 14 + 15 + function nullResolver(ctx: NullResolverContext): PydanticType { 16 + return ctx.nodes.base(ctx); 17 + } 18 + 19 + export function nullToType({ 20 + plugin, 21 + schema, 22 + }: { 8 23 plugin: PydanticPlugin['Instance']; 9 24 schema: SchemaWithType<'null'>; 10 25 }): PydanticType { 11 - return { 12 - type: 'None', 26 + const ctx: NullResolverContext = { 27 + $, 28 + nodes: { 29 + base: baseNode, 30 + }, 31 + plugin, 32 + schema, 13 33 }; 34 + 35 + const resolver = plugin.config['~resolvers']?.null; 36 + return resolver?.(ctx) ?? nullResolver(ctx); 14 37 }
-2
packages/openapi-python/src/plugins/pydantic/v2/toAst/number.ts
··· 15 15 type: $(literal).slice($.literal(schema.const)), 16 16 }; 17 17 } 18 - 19 - return undefined; 20 18 } 21 19 22 20 function baseNode(ctx: NumberResolverContext): PydanticType {
+1 -1
packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts
··· 12 12 function additionalPropertiesNode(ctx: ObjectResolverContext): PydanticType | null | undefined { 13 13 const { schema } = ctx; 14 14 15 - if (!schema.additionalProperties || !schema.additionalProperties.type) return undefined; 15 + if (!schema.additionalProperties || !schema.additionalProperties.type) return; 16 16 if (schema.additionalProperties.type === 'never') return null; 17 17 18 18 const result = ctx.walk(
-2
packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts
··· 15 15 type: $(literal).slice($.literal(schema.const)), 16 16 }; 17 17 } 18 - 19 - return undefined; 20 18 } 21 19 22 20 function baseNode(ctx: StringResolverContext): PydanticType {
+73 -32
packages/openapi-python/src/plugins/pydantic/v2/toAst/tuple.ts
··· 2 2 import { childContext } from '@hey-api/shared'; 3 3 4 4 import { $, type VarType } from '../../../../py-dsl'; 5 + import type { TupleResolverContext } from '../../resolvers'; 5 6 import type { PydanticFinal, PydanticResult, PydanticType } from '../../shared/types'; 6 7 import type { PydanticPlugin } from '../../types'; 7 8 import type { FieldConstraints } from '../constants'; 8 9 9 - interface TupleToTypeContext { 10 - applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; 11 - plugin: PydanticPlugin['Instance']; 12 - schema: SchemaWithType<'tuple'>; 13 - walk: Walker<PydanticResult, PydanticPlugin['Instance']>; 14 - walkerCtx: SchemaVisitorContext<PydanticPlugin['Instance']>; 15 - } 16 - 17 - export interface TupleToTypeResult extends PydanticType { 18 - childResults: Array<PydanticResult>; 19 - } 10 + function baseNode(ctx: TupleResolverContext): PydanticType { 11 + const { applyModifiers, childResults, plugin } = ctx; 20 12 21 - export function tupleToType(ctx: TupleToTypeContext): TupleToTypeResult { 22 - const { applyModifiers, plugin, schema, walk, walkerCtx } = ctx; 23 - 24 - const childResults: Array<PydanticResult> = []; 25 - const constraints: FieldConstraints = {}; 26 13 const tuple = plugin.external('typing.Tuple'); 27 14 const any = plugin.external('typing.Any'); 28 15 29 - if (schema.description !== undefined) { 30 - constraints.description = schema.description; 31 - } 32 - 33 - if (!schema.items || schema.items.length === 0) { 16 + if (childResults.length === 0) { 34 17 return { 35 - childResults, 36 - fieldConstraints: constraints, 37 18 type: $(tuple).slice(), 38 19 }; 39 20 } 40 21 41 22 const itemTypes: Array<VarType> = []; 42 23 43 - for (let i = 0; i < schema.items.length; i++) { 44 - const item = schema.items[i]!; 45 - const result = walk(item, childContext(walkerCtx, 'items', i)); 46 - childResults.push(result); 47 - 24 + for (const result of childResults) { 48 25 const finalResult = applyModifiers(result); 49 26 if (finalResult.type !== undefined) { 50 27 itemTypes.push(finalResult.type); ··· 53 30 54 31 if (itemTypes.length === 0) { 55 32 return { 56 - childResults, 57 - fieldConstraints: constraints, 58 33 type: $(tuple).slice(any, '...'), 59 34 }; 60 35 } 61 36 62 37 return { 38 + type: $(tuple).slice(...itemTypes), 39 + }; 40 + } 41 + 42 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 43 + function constNode(_ctx: TupleResolverContext): PydanticType | undefined { 44 + return; 45 + } 46 + 47 + function tupleResolver(ctx: TupleResolverContext): PydanticType { 48 + const baseResult = ctx.nodes.base(ctx); 49 + 50 + const fieldConstraints: FieldConstraints = { 51 + ...(baseResult.fieldConstraints ?? {}), 52 + }; 53 + 54 + if (ctx.schema.description !== undefined) { 55 + fieldConstraints.description = ctx.schema.description; 56 + } 57 + 58 + return { 59 + ...baseResult, 60 + fieldConstraints: Object.keys(fieldConstraints).length > 0 ? fieldConstraints : undefined, 61 + }; 62 + } 63 + 64 + export interface TupleToTypeResult extends PydanticType { 65 + childResults: Array<PydanticResult>; 66 + } 67 + 68 + export function tupleToType(ctx: { 69 + applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; 70 + plugin: PydanticPlugin['Instance']; 71 + schema: SchemaWithType<'tuple'>; 72 + walk: Walker<PydanticResult, PydanticPlugin['Instance']>; 73 + walkerCtx: SchemaVisitorContext<PydanticPlugin['Instance']>; 74 + }): TupleToTypeResult { 75 + const { applyModifiers, plugin, schema, walk, walkerCtx } = ctx; 76 + 77 + const childResults: Array<PydanticResult> = []; 78 + 79 + if (schema.items && schema.items.length > 0) { 80 + for (let i = 0; i < schema.items.length; i++) { 81 + const item = schema.items[i]!; 82 + const result = walk(item, childContext(walkerCtx, 'items', i)); 83 + childResults.push(result); 84 + } 85 + } 86 + 87 + const resolverCtx: TupleResolverContext = { 88 + $, 89 + applyModifiers, 63 90 childResults, 64 - fieldConstraints: constraints, 65 - type: $(tuple).slice(...itemTypes), 91 + nodes: { 92 + base: baseNode, 93 + const: constNode, 94 + }, 95 + plugin, 96 + schema, 97 + walk, 98 + walkerCtx, 99 + }; 100 + 101 + const resolver = plugin.config['~resolvers']?.tuple; 102 + const resolved = resolver?.(resolverCtx) ?? tupleResolver(resolverCtx); 103 + 104 + return { 105 + ...resolved, 106 + childResults, 66 107 }; 67 108 }
+26 -3
packages/openapi-python/src/plugins/pydantic/v2/toAst/undefined.ts
··· 1 1 import type { SchemaWithType } from '@hey-api/shared'; 2 2 3 + import { $ } from '../../../../py-dsl'; 4 + import type { UndefinedResolverContext } from '../../resolvers'; 3 5 import type { PydanticType } from '../../shared/types'; 4 6 import type { PydanticPlugin } from '../../types'; 5 7 6 8 // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 - export function undefinedToType(args: { 9 + function baseNode(_ctx: UndefinedResolverContext): PydanticType { 10 + return { 11 + type: 'None', 12 + }; 13 + } 14 + 15 + function undefinedResolver(ctx: UndefinedResolverContext): PydanticType { 16 + return ctx.nodes.base(ctx); 17 + } 18 + 19 + export function undefinedToType({ 20 + plugin, 21 + schema, 22 + }: { 8 23 plugin: PydanticPlugin['Instance']; 9 24 schema: SchemaWithType<'undefined'>; 10 25 }): PydanticType { 11 - return { 12 - type: 'None', 26 + const ctx: UndefinedResolverContext = { 27 + $, 28 + nodes: { 29 + base: baseNode, 30 + }, 31 + plugin, 32 + schema, 13 33 }; 34 + 35 + const resolver = plugin.config['~resolvers']?.undefined; 36 + return resolver?.(ctx) ?? undefinedResolver(ctx); 14 37 }
+67 -32
packages/openapi-python/src/plugins/pydantic/v2/toAst/union.ts
··· 1 1 import type { IR } from '@hey-api/shared'; 2 2 3 3 import { $ } from '../../../../py-dsl'; 4 + import type { UnionResolverContext } from '../../resolvers'; 4 5 import type { PydanticFinal, PydanticResult, PydanticType } from '../../shared/types'; 5 6 import type { PydanticPlugin } from '../../types'; 6 7 import type { FieldConstraints } from '../constants'; 7 8 8 - export interface UnionToTypeResult extends PydanticType { 9 - childResults: Array<PydanticResult>; 10 - isNullable: boolean; 11 - } 12 - 13 - export function unionToType({ 14 - applyModifiers, 15 - childResults, 16 - parentSchema, 17 - plugin, 18 - }: { 19 - applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; 20 - childResults: Array<PydanticResult>; 21 - parentSchema: IR.SchemaObject; 22 - plugin: PydanticPlugin['Instance']; 23 - }): UnionToTypeResult { 24 - const constraints: FieldConstraints = {}; 25 - 26 - if (parentSchema.description !== undefined) { 27 - constraints.description = parentSchema.description; 28 - } 9 + function baseNode(ctx: UnionResolverContext): PydanticType { 10 + const { applyModifiers, childResults, plugin } = ctx; 29 11 30 12 const nonNullResults: Array<PydanticResult> = []; 31 13 let isNullable = false; ··· 42 24 43 25 if (nonNullResults.length === 0) { 44 26 return { 45 - childResults, 46 - fieldConstraints: constraints, 47 - isNullable: true, 48 27 type: 'None', 49 28 }; 50 29 } 51 30 52 31 if (nonNullResults.length === 1) { 53 32 const finalResult = applyModifiers(nonNullResults[0]!); 54 - return { 55 - childResults, 56 - fieldConstraints: { ...constraints, ...finalResult.fieldConstraints }, 57 - isNullable, 58 - type: finalResult.type, 59 - }; 33 + return finalResult; 60 34 } 61 35 62 36 const union = plugin.external('typing.Union'); ··· 69 43 } 70 44 71 45 return { 46 + type: $(union).slice(...itemTypes), 47 + }; 48 + } 49 + 50 + function unionResolver(ctx: UnionResolverContext): PydanticType { 51 + return ctx.nodes.base(ctx); 52 + } 53 + 54 + export interface UnionToTypeResult extends PydanticType { 55 + childResults: Array<PydanticResult>; 56 + isNullable: boolean; 57 + } 58 + 59 + export function unionToType({ 60 + applyModifiers, 61 + childResults, 62 + parentSchema, 63 + plugin, 64 + schemas, 65 + }: { 66 + applyModifiers: (result: PydanticResult, options?: { optional?: boolean }) => PydanticFinal; 67 + childResults: Array<PydanticResult>; 68 + parentSchema: IR.SchemaObject; 69 + plugin: PydanticPlugin['Instance']; 70 + schemas: ReadonlyArray<IR.SchemaObject>; 71 + }): UnionToTypeResult { 72 + const constraints: FieldConstraints = {}; 73 + 74 + if (parentSchema.description !== undefined) { 75 + constraints.description = parentSchema.description; 76 + } 77 + 78 + let isNullable = false; 79 + for (const result of childResults) { 80 + if (result.type === 'None') { 81 + isNullable = true; 82 + break; 83 + } 84 + } 85 + isNullable = isNullable || childResults.some((r) => r.meta.nullable); 86 + 87 + const resolverCtx: UnionResolverContext = { 88 + $, 89 + applyModifiers, 72 90 childResults, 73 - fieldConstraints: constraints, 91 + nodes: { 92 + base: baseNode, 93 + }, 94 + parentSchema, 95 + plugin, 96 + schema: parentSchema, 97 + schemas, 98 + }; 99 + 100 + const resolver = plugin.config['~resolvers']?.union; 101 + const resolved = resolver?.(resolverCtx) ?? unionResolver(resolverCtx); 102 + 103 + return { 104 + ...resolved, 105 + childResults, 106 + fieldConstraints: 107 + Object.keys(constraints).length > 0 108 + ? { ...constraints, ...resolved.fieldConstraints } 109 + : resolved.fieldConstraints, 74 110 isNullable, 75 - type: $(union).slice(...itemTypes), 76 111 }; 77 112 }
+24 -2
packages/openapi-python/src/plugins/pydantic/v2/toAst/unknown.ts
··· 1 1 import type { SchemaWithType } from '@hey-api/shared'; 2 2 3 + import { $ } from '../../../../py-dsl'; 4 + import type { UnknownResolverContext } from '../../resolvers'; 3 5 import type { PydanticType } from '../../shared/types'; 4 6 import type { PydanticPlugin } from '../../types'; 7 + 8 + function baseNode(ctx: UnknownResolverContext): PydanticType { 9 + const { plugin } = ctx; 10 + return { 11 + type: plugin.external('typing.Any'), 12 + }; 13 + } 14 + 15 + function unknownResolver(ctx: UnknownResolverContext): PydanticType { 16 + return ctx.nodes.base(ctx); 17 + } 5 18 6 19 export function unknownToType({ 7 20 plugin, 21 + schema, 8 22 }: { 9 23 plugin: PydanticPlugin['Instance']; 10 24 schema: SchemaWithType<'unknown'>; 11 25 }): PydanticType { 12 - return { 13 - type: plugin.external('typing.Any'), 26 + const ctx: UnknownResolverContext = { 27 + $, 28 + nodes: { 29 + base: baseNode, 30 + }, 31 + plugin, 32 + schema, 14 33 }; 34 + 35 + const resolver = plugin.config['~resolvers']?.unknown; 36 + return resolver?.(ctx) ?? unknownResolver(ctx); 15 37 }
+26 -3
packages/openapi-python/src/plugins/pydantic/v2/toAst/void.ts
··· 1 1 import type { SchemaWithType } from '@hey-api/shared'; 2 2 3 + import { $ } from '../../../../py-dsl'; 4 + import type { VoidResolverContext } from '../../resolvers'; 3 5 import type { PydanticType } from '../../shared/types'; 4 6 import type { PydanticPlugin } from '../../types'; 5 7 6 8 // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 - export function voidToType(args: { 9 + function baseNode(_ctx: VoidResolverContext): PydanticType { 10 + return { 11 + type: 'None', 12 + }; 13 + } 14 + 15 + function voidResolver(ctx: VoidResolverContext): PydanticType { 16 + return ctx.nodes.base(ctx); 17 + } 18 + 19 + export function voidToType({ 20 + plugin, 21 + schema, 22 + }: { 8 23 plugin: PydanticPlugin['Instance']; 9 24 schema: SchemaWithType<'void'>; 10 25 }): PydanticType { 11 - return { 12 - type: 'None', 26 + const ctx: VoidResolverContext = { 27 + $, 28 + nodes: { 29 + base: baseNode, 30 + }, 31 + plugin, 32 + schema, 13 33 }; 34 + 35 + const resolver = plugin.config['~resolvers']?.void; 36 + return resolver?.(ctx) ?? voidResolver(ctx); 14 37 }
+1
packages/openapi-python/src/plugins/pydantic/v2/walker.ts
··· 240 240 childResults: items, 241 241 parentSchema, 242 242 plugin: ctx.plugin, 243 + schemas, 243 244 }); 244 245 245 246 return {
+1 -1
packages/openapi-python/src/py-dsl/layout/doc.ts
··· 38 38 } 39 39 return lines; 40 40 }, []); 41 - if (!lines.length) return undefined; 41 + if (!lines.length) return; 42 42 return lines.join('\n'); 43 43 } 44 44
+1 -1
packages/openapi-python/src/py-dsl/mixins/returns.ts
··· 30 30 31 31 protected $returns(): py.Expression | undefined { 32 32 if (!this._returns) { 33 - return undefined; 33 + return; 34 34 } 35 35 if (this._returns instanceof IdPyDsl) { 36 36 return this._returns.toAst();
-1
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/boolean.ts
··· 14 14 if (schema.const !== undefined) { 15 15 return $.type.fromValue(schema.const); 16 16 } 17 - return undefined; 18 17 } 19 18 20 19 function booleanResolver(ctx: BooleanResolverContext): Type {
+1 -1
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/enum.ts
··· 11 11 schema: SchemaWithType<'enum'>, 12 12 ): TypeScriptEnumData | undefined { 13 13 if (!plugin.config.enums.enabled) { 14 - return undefined; 14 + return; 15 15 } 16 16 17 17 const items = schema.items ?? [];
-1
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/number.ts
··· 10 10 if (schema.const !== undefined) { 11 11 return $.type.fromValue(schema.const); 12 12 } 13 - return undefined; 14 13 } 15 14 16 15 function baseNode(ctx: NumberResolverContext): Type {
+1 -4
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/string.ts
··· 12 12 if (schema.const !== undefined) { 13 13 return $.type.fromValue(schema.const); 14 14 } 15 - return undefined; 16 15 } 17 16 18 17 function formatNode(ctx: StringResolverContext): Type | undefined { 19 18 const { plugin, schema } = ctx; 20 19 const { format } = schema; 21 20 22 - if (!format) return undefined; 21 + if (!format) return; 23 22 24 23 if (format === 'binary') { 25 24 return $.type.or($.type('Blob'), $.type('File')); ··· 74 73 } 75 74 return $.type(plugin.referenceSymbol(typeidQuery)); 76 75 } 77 - 78 - return undefined; 79 76 } 80 77 81 78 // eslint-disable-next-line @typescript-eslint/no-unused-vars
+1 -1
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/tuple.ts
··· 24 24 const { schema } = ctx; 25 25 26 26 if (!schema.const || !Array.isArray(schema.const)) { 27 - return undefined; 27 + return; 28 28 } 29 29 30 30 const itemTypes = schema.const.map((value) => $.type.fromValue(value));