From 13c3d7d2be7a18ea72dff14e951841c843e3ea9a Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 28 May 2026 02:45:44 +0800 Subject: [PATCH] chore: add children and overrides to symbols --- .changeset/few-seas-count.md | 5 + .changeset/long-dolls-happen.md | 5 + .changeset/twenty-forks-argue.md | 5 + .../src/__tests__/exports.test.ts | 4 + packages/codegen-core/src/index.ts | 9 +- .../codegen-core/src/languages/resolvers.ts | 4 +- packages/codegen-core/src/planner/analyzer.ts | 14 ++- packages/codegen-core/src/planner/planner.ts | 46 +++---- .../codegen-core/src/planner/resolvers.ts | 7 +- packages/codegen-core/src/planner/scope.ts | 38 ++++-- packages/codegen-core/src/planner/types.ts | 9 +- packages/codegen-core/src/symbols/symbol.ts | 50 +++++++- packages/codegen-core/src/symbols/types.ts | 34 +++++ .../3.1.x/opencode/pydantic_gen.py | 16 +-- .../opencode/default/__init__.py | 4 +- .../opencode/default/pydantic_gen.py | 16 +-- .../__snapshots__/opencode/default/sdk_gen.py | 6 +- .../src/plugins/pydantic/dsl/decl/field.ts | 3 +- .../plugins/pydantic/dsl/utils/reserved.ts | 31 ----- .../openapi-python/src/py-dsl/decl/class.ts | 6 +- .../openapi-python/src/symbols/pydantic.ts | 116 +++++++++++++++++- packages/openapi-ts/src/ts-dsl/decl/class.ts | 3 + 22 files changed, 333 insertions(+), 98 deletions(-) create mode 100644 .changeset/few-seas-count.md create mode 100644 .changeset/long-dolls-happen.md create mode 100644 .changeset/twenty-forks-argue.md delete mode 100644 packages/openapi-python/src/plugins/pydantic/dsl/utils/reserved.ts diff --git a/.changeset/few-seas-count.md b/.changeset/few-seas-count.md new file mode 100644 index 0000000000..d5bf490b14 --- /dev/null +++ b/.changeset/few-seas-count.md @@ -0,0 +1,5 @@ +--- +"@hey-api/codegen-core": patch +--- + +**symbol**: add `children` and `override` property diff --git a/.changeset/long-dolls-happen.md b/.changeset/long-dolls-happen.md new file mode 100644 index 0000000000..2919ebe2fd --- /dev/null +++ b/.changeset/long-dolls-happen.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**dsl**: track extended class symbols diff --git a/.changeset/twenty-forks-argue.md b/.changeset/twenty-forks-argue.md new file mode 100644 index 0000000000..d1555f15a7 --- /dev/null +++ b/.changeset/twenty-forks-argue.md @@ -0,0 +1,5 @@ +--- +"@hey-api/codegen-core": patch +--- + +**symbols**: export `pythonNameConflictResolver`, `SymbolChild`, `SymbolKind`, and `SymbolRegistry` diff --git a/packages/codegen-core/src/__tests__/exports.test.ts b/packages/codegen-core/src/__tests__/exports.test.ts index dddd4fc084..4c74f76d23 100644 --- a/packages/codegen-core/src/__tests__/exports.test.ts +++ b/packages/codegen-core/src/__tests__/exports.test.ts @@ -18,6 +18,7 @@ const constExports = [ 'Logger', 'mergeConfigs', 'nodeBrand', + 'pythonNameConflictResolver', 'Project', 'ref', 'refs', @@ -65,9 +66,12 @@ export type _TypeExports = [ index.StructureShell, index.StructureShellResult, index.Symbol, + index.SymbolChild, index.SymbolIdentifier, index.SymbolIn, + index.SymbolKind, index.SymbolMeta, + index.SymbolRegistry, ]; describe('index exports', () => { diff --git a/packages/codegen-core/src/index.ts b/packages/codegen-core/src/index.ts index 332f311738..0617cc1541 100644 --- a/packages/codegen-core/src/index.ts +++ b/packages/codegen-core/src/index.ts @@ -29,7 +29,11 @@ export type { NodeScope, } from './nodes/node'; export type { IOutput as Output } from './output'; -export { simpleNameConflictResolver, underscoreNameConflictResolver } from './planner/resolvers'; +export { + pythonNameConflictResolver, + simpleNameConflictResolver, + underscoreNameConflictResolver, +} from './planner/resolvers'; export type { IAnalysisContext as AnalysisContext, NameConflictResolver } from './planner/types'; export { Project } from './project/project'; export type { IProject } from './project/types'; @@ -48,6 +52,9 @@ export type { export { Symbol } from './symbols/symbol'; export type { BindingKind, + ISymbolChild as SymbolChild, ISymbolIdentifier as SymbolIdentifier, ISymbolIn as SymbolIn, + SymbolKind, + ISymbolRegistry as SymbolRegistry, } from './symbols/types'; diff --git a/packages/codegen-core/src/languages/resolvers.ts b/packages/codegen-core/src/languages/resolvers.ts index 0e8a399539..98b184d453 100644 --- a/packages/codegen-core/src/languages/resolvers.ts +++ b/packages/codegen-core/src/languages/resolvers.ts @@ -1,8 +1,8 @@ -import { underscoreNameConflictResolver } from '../planner/resolvers'; +import { pythonNameConflictResolver, underscoreNameConflictResolver } from '../planner/resolvers'; import type { NameConflictResolvers } from './types'; export const defaultNameConflictResolvers: NameConflictResolvers = { php: underscoreNameConflictResolver, - python: underscoreNameConflictResolver, + python: pythonNameConflictResolver, ruby: underscoreNameConflictResolver, }; diff --git a/packages/codegen-core/src/planner/analyzer.ts b/packages/codegen-core/src/planner/analyzer.ts index 7d6c321522..aa47fc3764 100644 --- a/packages/codegen-core/src/planner/analyzer.ts +++ b/packages/codegen-core/src/planner/analyzer.ts @@ -4,7 +4,7 @@ import { fromRef, isRef, ref } from '../refs/refs'; import type { Ref } from '../refs/types'; import type { Symbol } from '../symbols/symbol'; import type { NameScopes, Scope } from './scope'; -import { createScope } from './scope'; +import { createScope, registerChildName } from './scope'; import type { IAnalysisContext, Input } from './types'; export class AnalysisContext implements IAnalysisContext { @@ -74,6 +74,18 @@ export class AnalysisContext implements IAnalysisContext { } } + injectChildren(input: Input): void { + const value = isRef(input) ? input : ref(input); + const symbol = isSymbolRef(value) ? fromRef(value) : undefined; + if (!symbol) return; + + for (const child of symbol.children) { + if (!child.overridable) { + registerChildName(this.scope, child.name, child.kind); + } + } + } + localNames(scope: Scope): NameScopes { const names: NameScopes = new Map(); for (const [name, kinds] of scope.localNames) { diff --git a/packages/codegen-core/src/planner/planner.ts b/packages/codegen-core/src/planner/planner.ts index 58ebd5cfb7..90fd062650 100644 --- a/packages/codegen-core/src/planner/planner.ts +++ b/packages/codegen-core/src/planner/planner.ts @@ -13,7 +13,7 @@ import type { SymbolKind } from '../symbols/types'; import type { AnalysisContext } from './analyzer'; import { Analyzer } from './analyzer'; import type { AssignOptions, Scope } from './scope'; -import { createScope } from './scope'; +import { createScope, registerName } from './scope'; const isTypeOnlyKind = (kind: SymbolKind) => kind === 'type' || kind === 'interface'; @@ -418,7 +418,13 @@ export class Planner { while (true) { const language = node?.language || symbol.node?.language || file.language; - const ok = this.nameIsAvailable({ kind: symbol.kind, language, name: finalName, scope }); + const ok = this.nameIsAvailable({ + kind: symbol.kind, + language, + name: finalName, + override: symbol.override, + scope, + }); if (ok) break; const resolver = @@ -440,7 +446,7 @@ export class Planner { this.cacheResolvedNames.add(symbol.id); const updateScopes = [scope, ...scopesToUpdate]; for (const scope of updateScopes) { - this.updateScope(symbol, scope); + registerName(scope, symbol.finalName, symbol.kind); } } @@ -455,37 +461,33 @@ export class Planner { kind, language, name, + override, scope, }: { kind: SymbolKind; language: string | undefined; name: string; + override?: boolean; scope: Scope; }): boolean { + function conflicts(kinds: Set | undefined): boolean { + if (!kinds) return false; + for (const existingKind of kinds) { + if (!canDeclarationsShareIdentifier(language, kind, existingKind)) { + return true; + } + } + return false; + } + let current: Scope | undefined = scope; while (current) { - const kinds = current.localNames.get(name); - if (kinds) { - for (const existingKind of kinds) { - if (!canDeclarationsShareIdentifier(language, kind, existingKind)) { - return false; - } - } + if (!override && conflicts(current.childNames.get(name))) { + return false; } + if (conflicts(current.localNames.get(name))) return false; current = current.parent; } return true; } - - /** - * Updates the provided name scope with the symbol's final name and kind. - * - * Ensures the name scope tracks all kinds associated with a given name. - */ - private updateScope(symbol: Symbol, scope: Scope): void { - const name = symbol.finalName; - const cache = scope.localNames.get(name) ?? new Set(); - cache.add(symbol.kind); - scope.localNames.set(name, cache); - } } diff --git a/packages/codegen-core/src/planner/resolvers.ts b/packages/codegen-core/src/planner/resolvers.ts index 4721b3d670..9e6b4ee8a9 100644 --- a/packages/codegen-core/src/planner/resolvers.ts +++ b/packages/codegen-core/src/planner/resolvers.ts @@ -1,7 +1,10 @@ import type { NameConflictResolver } from './types'; +export const pythonNameConflictResolver: NameConflictResolver = ({ attempt, baseName }) => + attempt === 1 ? `${baseName}_` : `${baseName}_${attempt}`; + export const simpleNameConflictResolver: NameConflictResolver = ({ attempt, baseName }) => - attempt === 0 ? baseName : `${baseName}${attempt + 1}`; + `${baseName}${attempt + 1}`; export const underscoreNameConflictResolver: NameConflictResolver = ({ attempt, baseName }) => - attempt === 0 ? baseName : `${baseName}_${attempt + 1}`; + `${baseName}_${attempt + 1}`; diff --git a/packages/codegen-core/src/planner/scope.ts b/packages/codegen-core/src/planner/scope.ts index d48821d126..a9b72a7014 100644 --- a/packages/codegen-core/src/planner/scope.ts +++ b/packages/codegen-core/src/planner/scope.ts @@ -5,9 +5,11 @@ import type { SymbolKind } from '../symbols/types'; export type NameScopes = Map>; export type Scope = { + /** Soft conflicts, inherited names from children symbols. */ + childNames: NameScopes; /** Child scopes. */ children: Array; - /** Resolved names in this scope. */ + /** Hard conflicts, declared names in this scope. */ localNames: NameScopes; /** Parent scope, if any. */ parent?: Scope; @@ -22,14 +24,26 @@ export type AssignOptions = { scopesToUpdate: ReadonlyArray; }; -export const createScope = ( - args: { - localNames?: NameScopes; - parent?: Scope; - } = {}, -): Scope => ({ - children: [], - localNames: args.localNames || new Map(), - parent: args.parent, - symbols: [], -}); +export function createScope( + args: Pick, 'childNames' | 'localNames' | 'parent'> = {}, +): Scope { + return { + childNames: args.childNames || new Map(), + children: [], + localNames: args.localNames || new Map(), + parent: args.parent, + symbols: [], + }; +} + +export function registerName(scope: Scope, name: string, kind: SymbolKind): void { + const kinds = scope.localNames.get(name) ?? new Set(); + kinds.add(kind); + scope.localNames.set(name, kinds); +} + +export function registerChildName(scope: Scope, name: string, kind: SymbolKind): void { + const kinds = scope.childNames.get(name) ?? new Set(); + kinds.add(kind); + scope.childNames.set(name, kinds); +} diff --git a/packages/codegen-core/src/planner/types.ts b/packages/codegen-core/src/planner/types.ts index 2d156294eb..94e75a3c33 100644 --- a/packages/codegen-core/src/planner/types.ts +++ b/packages/codegen-core/src/planner/types.ts @@ -4,13 +4,20 @@ import type { NameScopes, Scope } from './scope'; export type Input = Ref | object | string | number | undefined; -export type NameConflictResolver = (args: { attempt: number; baseName: string }) => string | null; +export type NameConflictResolver = (args: { + /** The current attempt number for generating a unique name. Starts at 1. */ + attempt: number; + /** The base name to resolve conflicts for. */ + baseName: string; +}) => string | null; export interface IAnalysisContext { /** Register a dependency on another symbol. */ addDependency(symbol: Ref): void; /** Register a dependency on another symbol or analyze further. */ analyze(input: Input): void; + /** Inject children symbols into the current scope. */ + injectChildren(input: Input): void; /** Get local names in the current scope. */ localNames(scope: Scope): NameScopes; /** Pop the current local scope. */ diff --git a/packages/codegen-core/src/symbols/symbol.ts b/packages/codegen-core/src/symbols/symbol.ts index 011508b863..65caf13ad9 100644 --- a/packages/codegen-core/src/symbols/symbol.ts +++ b/packages/codegen-core/src/symbols/symbol.ts @@ -3,7 +3,7 @@ import type { ISymbolMeta } from '../extensions'; import type { File } from '../files/file'; import { log } from '../log'; import type { INode } from '../nodes/node'; -import type { BindingKind, ISymbolIn, SymbolKind } from './types'; +import type { BindingKind, ISymbolChild, ISymbolIn, SymbolKind } from './types'; export class Symbol { /** @@ -14,6 +14,12 @@ export class Symbol { * should defer to the canonical symbol. */ private _canonical?: Symbol; + /** + * Child symbol bindings scoped under this symbol. + * + * @default [] + */ + private _children: Array; /** * True if this symbol is exported from its defining file. * @@ -77,6 +83,12 @@ export class Symbol { * Node that defines this symbol. */ private _node?: Node; + /** + * Indicates whether this symbol overrides another declaration. + * + * @default false + */ + private _override: boolean; /** Brand used for identifying symbols. */ readonly '~brand' = symbolBrand; @@ -84,6 +96,7 @@ export class Symbol { readonly id: number; constructor(input: ISymbolIn, id: number) { + this._children = input.children ?? []; this._exported = input.exported ?? false; this._external = input.external; this._getExportFromFilePath = input.getExportFromFilePath; @@ -93,6 +106,7 @@ export class Symbol { this._kind = input.kind ?? 'var'; this._meta = input.meta; this._name = input.name; + this._override = input.override ?? false; } /** @@ -106,6 +120,13 @@ export class Symbol { return this._canonical ?? this; } + /** + * Read-only accessor for child symbol bindings. + */ + get children(): ReadonlyArray { + return this.canonical._children; + } + /** * Indicates whether this symbol is exported from its defining file. */ @@ -197,6 +218,13 @@ export class Symbol { return this.canonical._node as Node | undefined; } + /** + * Indicates whether this symbol is marked as an override. + */ + get override(): boolean { + return this.canonical._override; + } + /** * Marks this symbol as a stub and assigns its canonical symbol. * @@ -209,6 +237,16 @@ export class Symbol { this._canonical = symbol; } + /** + * Assigns the child symbol bindings associated with this symbol. + * + * @param children — Child bindings to associate with the symbol. + */ + setChildren(children: Array): void { + this.assertCanonical(); + this._children = children; + } + /** * Marks the symbol as exported from its file. * @@ -297,6 +335,16 @@ export class Symbol { node.symbol = this; } + /** + * Marks whether this symbol should be treated as an override. + * + * @param override — Override marker value. + */ + setOverride(override: boolean): void { + this.assertCanonical(); + this._override = override; + } + /** * Returns a debug‑friendly string representation identifying the symbol. */ diff --git a/packages/codegen-core/src/symbols/types.ts b/packages/codegen-core/src/symbols/types.ts index 6c917c2e6f..051958c5ec 100644 --- a/packages/codegen-core/src/symbols/types.ts +++ b/packages/codegen-core/src/symbols/types.ts @@ -7,7 +7,34 @@ export type ISymbolIdentifier = number | ISymbolMeta; export type SymbolKind = 'class' | 'enum' | 'function' | 'interface' | 'namespace' | 'type' | 'var'; +export interface ISymbolChild { + /** + * Kind of symbol (class, type, alias, etc.). + * + * @default 'var' + */ + kind: SymbolKind; + /** + * User-facing name. + * + * @example "UserModel" + */ + name: string; + /** + * When true, subclasses may freely redeclare this name. + * + * @default false + */ + overridable?: boolean; +} + export type ISymbolIn = { + /** + * Child symbol bindings scoped under this symbol. + * + * @default undefined + */ + children?: Array; /** * Whether this symbol is exported from its own file. * @@ -59,6 +86,13 @@ export type ISymbolIn = { * @example "UserModel" */ name: string; + /** + * When true, this symbol intentionally overrides another declaration with + * the same name in the same scope. + * + * @default false + */ + override?: boolean; }; export interface ISymbolRegistry { diff --git a/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py b/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py index 30b21d2666..a5885fa183 100644 --- a/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py +++ b/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py @@ -456,11 +456,11 @@ class Part(BaseModel): command: Optional[Optional[str]] -Part_2: TypeAlias = Union[TextPart, Part, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart] +Part_: TypeAlias = Union[TextPart, Part, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart] class EventMessagePartUpdatedProperties(BaseModel): - part: Part_2 + part: Part_ delta: Optional[Optional[str]] @@ -521,23 +521,23 @@ class SessionStatus(BaseModel): type_: Literal["idle"] = Field(..., alias="type") -class SessionStatus_2(BaseModel): +class SessionStatus_(BaseModel): type_: Literal["retry"] = Field(..., alias="type") attempt: float message: str next_: float = Field(..., alias="next") -class SessionStatus_3(BaseModel): +class SessionStatus_2(BaseModel): type_: Literal["busy"] = Field(..., alias="type") -SessionStatus_4: TypeAlias = Union[SessionStatus, SessionStatus_2, SessionStatus_3] +SessionStatus_3: TypeAlias = Union[SessionStatus, SessionStatus_, SessionStatus_2] class EventSessionStatusProperties(BaseModel): session_id: str = Field(..., alias="sessionID") - status: SessionStatus_4 + status: SessionStatus_3 class EventSessionStatus(BaseModel): @@ -1003,7 +1003,7 @@ class PermissionConfig(BaseModel): doom_loop: Optional[Optional[PermissionActionConfig]] -PermissionConfig_2: TypeAlias = Union[PermissionConfig, PermissionActionConfig] +PermissionConfig_: TypeAlias = Union[PermissionConfig, PermissionActionConfig] AgentConfig: TypeAlias = dict[str, Any] @@ -1254,7 +1254,7 @@ class Config(BaseModel): lsp: Optional[Optional[Union[Literal[False], dict[str, Any]]]] instructions: Optional[Optional[list[str]]] = Field(default=None, description="Additional instruction files or patterns to include") layout: Optional[Optional[LayoutConfig]] - permission: Optional[Optional[PermissionConfig_2]] + permission: Optional[Optional[PermissionConfig_]] tools: Optional[Optional[dict[str, Any]]] enterprise: Optional[Optional[ConfigEnterprise]] compaction: Optional[Optional[ConfigCompaction]] diff --git a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/__init__.py b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/__init__.py index c5658c6b1c..e4baebd5ec 100644 --- a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/__init__.py +++ b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/__init__.py @@ -1,5 +1,5 @@ # This file is auto-generated by @hey-api/openapi-python -from .sdk_gen import App, Auth, Auth_2, Command, Config, Control, Event, File, Find, Formatter, Global, Instance, Lsp, Mcp, Oauth, Part, Path, Permission, Project, Provider, Pty, Sdk, Session, Tool, Tui, Vcs +from .sdk_gen import App, Auth, Auth_, Command, Config, Control, Event, File, Find, Formatter, Global, Instance, Lsp, Mcp, Oauth, Part, Path, Permission, Project, Provider, Pty, Sdk, Session, Tool, Tui, Vcs -__all__ = ["App", "Auth", "Auth_2", "Command", "Config", "Control", "Event", "File", "Find", "Formatter", "Global", "Instance", "Lsp", "Mcp", "Oauth", "Part", "Path", "Permission", "Project", "Provider", "Pty", "Sdk", "Session", "Tool", "Tui", "Vcs"] +__all__ = ["App", "Auth", "Auth_", "Command", "Config", "Control", "Event", "File", "Find", "Formatter", "Global", "Instance", "Lsp", "Mcp", "Oauth", "Part", "Path", "Permission", "Project", "Provider", "Pty", "Sdk", "Session", "Tool", "Tui", "Vcs"] diff --git a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py index 30b21d2666..a5885fa183 100644 --- a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py +++ b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py @@ -456,11 +456,11 @@ class Part(BaseModel): command: Optional[Optional[str]] -Part_2: TypeAlias = Union[TextPart, Part, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart] +Part_: TypeAlias = Union[TextPart, Part, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart] class EventMessagePartUpdatedProperties(BaseModel): - part: Part_2 + part: Part_ delta: Optional[Optional[str]] @@ -521,23 +521,23 @@ class SessionStatus(BaseModel): type_: Literal["idle"] = Field(..., alias="type") -class SessionStatus_2(BaseModel): +class SessionStatus_(BaseModel): type_: Literal["retry"] = Field(..., alias="type") attempt: float message: str next_: float = Field(..., alias="next") -class SessionStatus_3(BaseModel): +class SessionStatus_2(BaseModel): type_: Literal["busy"] = Field(..., alias="type") -SessionStatus_4: TypeAlias = Union[SessionStatus, SessionStatus_2, SessionStatus_3] +SessionStatus_3: TypeAlias = Union[SessionStatus, SessionStatus_, SessionStatus_2] class EventSessionStatusProperties(BaseModel): session_id: str = Field(..., alias="sessionID") - status: SessionStatus_4 + status: SessionStatus_3 class EventSessionStatus(BaseModel): @@ -1003,7 +1003,7 @@ class PermissionConfig(BaseModel): doom_loop: Optional[Optional[PermissionActionConfig]] -PermissionConfig_2: TypeAlias = Union[PermissionConfig, PermissionActionConfig] +PermissionConfig_: TypeAlias = Union[PermissionConfig, PermissionActionConfig] AgentConfig: TypeAlias = dict[str, Any] @@ -1254,7 +1254,7 @@ class Config(BaseModel): lsp: Optional[Optional[Union[Literal[False], dict[str, Any]]]] instructions: Optional[Optional[list[str]]] = Field(default=None, description="Additional instruction files or patterns to include") layout: Optional[Optional[LayoutConfig]] - permission: Optional[Optional[PermissionConfig_2]] + permission: Optional[Optional[PermissionConfig_]] tools: Optional[Optional[dict[str, Any]]] enterprise: Optional[Optional[ConfigEnterprise]] compaction: Optional[Optional[ConfigCompaction]] diff --git a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/sdk_gen.py b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/sdk_gen.py index 122cbf391d..0f9ec824fb 100644 --- a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/sdk_gen.py +++ b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/sdk_gen.py @@ -796,7 +796,7 @@ def control(self) -> Control: return Control(client=self.client) -class Auth_2(Client): +class Auth_(Client): def set(self): """ Set auth credentials @@ -900,8 +900,8 @@ def tui(self) -> Tui: return Tui(client=self.client) @cached_property - def auth(self) -> Auth_2: - return Auth_2(client=self.client) + def auth(self) -> Auth_: + return Auth_(client=self.client) @cached_property def event(self) -> Event: diff --git a/packages/openapi-python/src/plugins/pydantic/dsl/decl/field.ts b/packages/openapi-python/src/plugins/pydantic/dsl/decl/field.ts index 16837e6488..3c4bff4407 100644 --- a/packages/openapi-python/src/plugins/pydantic/dsl/decl/field.ts +++ b/packages/openapi-python/src/plugins/pydantic/dsl/decl/field.ts @@ -11,7 +11,6 @@ import { safeRuntimeName } from '../../../../py-dsl/utils/name'; import type { PydanticPlugin } from '../../types'; import { ConstraintsMixin } from '../mixins/constraints'; import { literalize } from '../utils/literal'; -import { BASE_MODEL_RESERVED } from '../utils/reserved'; const Mixed = ConstraintsMixin(OptionalMixin(PyDsl)); @@ -72,7 +71,7 @@ export class PydanticFieldDsl extends Mixed { const name = String(fromRef(this.name)); const snake = toCase(name, 'snake_case'); const safe = safeRuntimeName(snake); - const runtimeName = BASE_MODEL_RESERVED.has(safe) ? `${safe}_` : safe; + const runtimeName = safe; const needsAlias = runtimeName !== name; const alias = this._alias ?? (needsAlias ? name : undefined); diff --git a/packages/openapi-python/src/plugins/pydantic/dsl/utils/reserved.ts b/packages/openapi-python/src/plugins/pydantic/dsl/utils/reserved.ts deleted file mode 100644 index da2a701c3c..0000000000 --- a/packages/openapi-python/src/plugins/pydantic/dsl/utils/reserved.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const BASE_MODEL_RESERVED: ReadonlySet = new Set([ - // v2 model_* API - 'model_config', - 'model_fields', - 'model_computed_fields', - 'model_extra', - 'model_fields_set', - 'model_dump', - 'model_dump_json', - 'model_copy', - 'model_validate', - 'model_validate_json', - 'model_validate_strings', - 'model_json_schema', - 'model_parametrized_name', - 'model_post_init', - 'model_rebuild', - 'model_construct', - // v1 carryovers still present on BaseModel - 'dict', - 'json', - 'copy', - 'parse_obj', - 'parse_raw', - 'parse_file', - 'from_orm', - 'schema', - 'schema_json', - 'construct', - 'validate', -]); diff --git a/packages/openapi-python/src/py-dsl/decl/class.ts b/packages/openapi-python/src/py-dsl/decl/class.ts index c73c76a5a8..f0f7e4b4fe 100644 --- a/packages/openapi-python/src/py-dsl/decl/class.ts +++ b/packages/openapi-python/src/py-dsl/decl/class.ts @@ -2,7 +2,8 @@ import type { AnalysisContext, NodeName, Ref } from '@hey-api/codegen-core'; import { isSymbol, ref } from '@hey-api/codegen-core'; import { py } from '../../py-compiler'; -import { type MaybePyDsl, PyDsl } from '../base'; +import type { MaybePyDsl } from '../base'; +import { PyDsl } from '../base'; import { NewlinePyDsl } from '../layout/newline'; import { DecoratorMixin } from '../mixins/decorator'; import { DocMixin } from '../mixins/doc'; @@ -37,6 +38,9 @@ export class ClassPyDsl extends Mixed { ctx.analyze(this.name); ctx.pushScope(); try { + for (const baseClass of this.baseClasses) { + ctx.injectChildren(baseClass); + } for (const item of this.body) { ctx.analyze(item); } diff --git a/packages/openapi-python/src/symbols/pydantic.ts b/packages/openapi-python/src/symbols/pydantic.ts index 33b62cdcb1..ab645fcd0f 100644 --- a/packages/openapi-python/src/symbols/pydantic.ts +++ b/packages/openapi-python/src/symbols/pydantic.ts @@ -2,7 +2,121 @@ import type { PluginInstance } from '@hey-api/shared'; export function PYDANTIC(plugin: PluginInstance) { return { - BaseModel: plugin.symbol('BaseModel', { external: 'pydantic' }), + BaseModel: plugin.symbol('BaseModel', { + children: [ + // v2 model_* API + { + kind: 'function', + name: 'model_computed_fields', + }, + { + kind: 'var', + name: 'model_config', + }, + { + kind: 'function', + name: 'model_construct', + }, + { + kind: 'function', + name: 'model_copy', + }, + { + kind: 'function', + name: 'model_dump_json', + }, + { + kind: 'function', + name: 'model_dump', + }, + { + kind: 'var', + name: 'model_extra', + }, + { + kind: 'var', + name: 'model_fields_set', + }, + { + kind: 'function', + name: 'model_fields', + }, + { + kind: 'function', + name: 'model_json_schema', + }, + { + kind: 'function', + name: 'model_parametrized_name', + }, + { + kind: 'function', + name: 'model_post_init', + }, + { + kind: 'function', + name: 'model_rebuild', + }, + { + kind: 'function', + name: 'model_validate_json', + }, + { + kind: 'function', + name: 'model_validate_strings', + }, + { + kind: 'function', + name: 'model_validate', + }, + // v1 carryovers still present on BaseModel + { + kind: 'function', + name: 'construct', + }, + { + kind: 'function', + name: 'copy', + }, + { + kind: 'function', + name: 'dict', + }, + { + kind: 'function', + name: 'from_orm', + }, + { + kind: 'function', + name: 'json', + }, + { + kind: 'function', + name: 'parse_file', + }, + { + kind: 'function', + name: 'parse_obj', + }, + { + kind: 'function', + name: 'parse_raw', + }, + { + kind: 'function', + name: 'schema_json', + }, + { + kind: 'function', + name: 'schema', + }, + { + kind: 'function', + name: 'validate', + }, + ], + external: 'pydantic', + }), ConfigDict: plugin.symbol('ConfigDict', { external: 'pydantic' }), Field: plugin.symbol('Field', { external: 'pydantic' }), dataclass: plugin.symbol('dataclass', { external: 'pydantic.dataclasses' }), diff --git a/packages/openapi-ts/src/ts-dsl/decl/class.ts b/packages/openapi-ts/src/ts-dsl/decl/class.ts index 436a7a21b5..5b85ed1730 100644 --- a/packages/openapi-ts/src/ts-dsl/decl/class.ts +++ b/packages/openapi-ts/src/ts-dsl/decl/class.ts @@ -41,6 +41,9 @@ export class ClassTsDsl extends Mixed { ctx.analyze(this.name); ctx.pushScope(); try { + if (this.baseClass) { + ctx.injectChildren(this.baseClass); + } for (const item of this.body) { ctx.analyze(item); }