Skip to content

Commit 5a514cc

Browse files
committed
fix: add views and materialized_views, add insert and update methods too
1 parent 5f48036 commit 5a514cc

File tree

1 file changed

+82
-64
lines changed

1 file changed

+82
-64
lines changed

src/server/templates/python.ts

Lines changed: 82 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import type {
77
PostgresView,
88
} from '../../lib/index.js'
99
import type { GeneratorMetadata } from '../../lib/generators.js'
10-
import { console } from 'inspector/promises';
11-
12-
type Operation = 'Select' | 'Insert' | 'Update'
1310

1411
interface Serializable {
1512
serialize(): string
@@ -18,7 +15,7 @@ interface Serializable {
1815
class PythonContext {
1916
types: { [k: string]: PostgresType };
2017
user_enums: { [k: string]: PythonEnum };
21-
columns: Record<string, PostgresColumn[]>;
18+
columns: Record<number, PostgresColumn[]>;
2219
schemas: { [k: string]: PostgresSchema };
2320

2421
constructor(types: PostgresType[], columns: PostgresColumn[], schemas: PostgresSchema[]) {
@@ -32,7 +29,7 @@ class PythonContext {
3229
acc[curr.table_id].push(curr)
3330
return acc
3431
},
35-
{} as Record<string, PostgresColumn[]>
32+
{} as Record<number, PostgresColumn[]>
3633
);
3734
this.user_enums = Object.fromEntries(types
3835
.filter((type) => type.enums.length > 0)
@@ -49,14 +46,15 @@ class PythonContext {
4946
if (name in this.types) {
5047
const type = this.types[name];
5148
const schema = type!.schema;
52-
return `${formatForPyClassName(schema)}${formatForPyClassName(name)}`;
49+
return `${formatForPyClassName(schema)}${formatForPyClassName(type.name)}`;
5350
}
54-
throw new TypeError(`Unknown row type: ${name}`);
51+
console.log(`Unknown recognized row type ${name}`);
52+
return 'Any';
5553
}
5654

5755
parsePgType(pg_type: string) : PythonType {
58-
if (pg_type.endsWith('[]')) {
59-
const inner_str = pg_type.slice(0, -2);
56+
if (pg_type.startsWith('_')) {
57+
const inner_str = pg_type.slice(1);
6058
const inner = this.parsePgType(inner_str);
6159
return new PythonListType(inner);
6260
} else {
@@ -65,15 +63,6 @@ class PythonContext {
6563
}
6664
}
6765

68-
tableToClass(table: PostgresTable) : PythonClass {
69-
const attributes: PythonClassAttribute[] = (this.columns[table.id] ?? [])
70-
.map((col) => {
71-
const type = new PythonConcreteType(this, col.format, col.is_nullable);
72-
return new PythonClassAttribute(col.name, type);
73-
});
74-
return new PythonClass(table.name, this.schemas[table.schema], attributes)
75-
}
76-
7766
typeToClass(type: PostgresType) : PythonClass {
7867
const types = Object.values(this.types);
7968
const attributes = type.attributes.map((attribute) => {
@@ -85,28 +74,38 @@ class PythonContext {
8574
});
8675
const attributeEntries: PythonClassAttribute[] = attributes
8776
.map((attribute) => {
88-
const type = new PythonConcreteType(this, attribute.type!.format, false);
89-
return new PythonClassAttribute(attribute.name, type);
77+
const type = this.parsePgType(attribute.type!.name);
78+
return new PythonClassAttribute(attribute.name, type, false, false, false, false);
9079
});
9180
const schema = this.schemas[type.schema];
9281
return new PythonClass(type.name, schema, attributeEntries);
9382
}
9483

84+
columnsToClassAttrs(table_id: number) : PythonClassAttribute[] {
85+
const attrs = this.columns[table_id] ?? [];
86+
return attrs.map((col) => {
87+
const type = this.parsePgType(col.format);
88+
return new PythonClassAttribute(col.name, type,
89+
col.is_nullable,
90+
col.is_updatable,
91+
col.is_generated || !!col.default_value,
92+
col.is_identity);
93+
});
94+
}
95+
96+
tableToClass(table: PostgresTable) : PythonClass {
97+
const attributes = this.columnsToClassAttrs(table.id);
98+
return new PythonClass(table.name, this.schemas[table.schema], attributes)
99+
}
100+
101+
95102
viewToClass(view: PostgresView) : PythonClass {
96-
const attributes: PythonClassAttribute[] = (this.columns[view.id] ?? [])
97-
.map((col) => {
98-
const type = new PythonConcreteType(this, col.format, col.is_nullable);
99-
return new PythonClassAttribute(col.name, type);
100-
});
103+
const attributes = this.columnsToClassAttrs(view.id);
101104
return new PythonClass(view.name, this.schemas[view.schema], attributes)
102105
}
103106

104107
matViewToClass(matview: PostgresMaterializedView) : PythonClass {
105-
const attributes: PythonClassAttribute[] = (this.columns[matview.id] ?? [])
106-
.map((col) => {
107-
const type = new PythonConcreteType(this, col.format, col.is_nullable);
108-
return new PythonClassAttribute(col.name, type);
109-
});
108+
const attributes = this.columnsToClassAttrs(matview.id);
110109
return new PythonClass(matview.name, this.schemas[matview.schema], attributes)
111110
}
112111
}
@@ -116,7 +115,7 @@ class PythonEnum implements Serializable {
116115
name: string;
117116
variants: string[];
118117
constructor(type: PostgresType) {
119-
this.name = formatForPyClassName(type.name);
118+
this.name = `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}`;
120119
this.variants = type.enums.map(formatForPyAttributeName);
121120
}
122121
serialize(): string {
@@ -147,58 +146,71 @@ class PythonListType implements Serializable {
147146
}
148147
}
149148

150-
class PythonConcreteType implements Serializable {
151-
py_type: PythonType;
149+
class PythonClassAttribute implements Serializable {
150+
name: string;
152151
pg_name: string;
152+
py_type: PythonType;
153153
nullable: boolean;
154-
default_value: string | null;
155-
constructor(ctx: PythonContext, pg_name: string, nullable: boolean) {
156-
const py_type = ctx.parsePgType(pg_name);
154+
mutable: boolean;
155+
has_default: boolean;
156+
is_identity: boolean;
157+
157158

159+
constructor(name: string, py_type: PythonType, nullable: boolean, mutable: boolean, has_default: boolean, is_identity: boolean) {
160+
this.name = formatForPyAttributeName(name);
161+
this.pg_name = name;
158162
this.py_type = py_type;
159-
this.pg_name = pg_name;
160163
this.nullable = nullable;
161-
this.default_value = null;
164+
this.mutable = mutable;
165+
this.has_default = has_default;
166+
this.is_identity = is_identity;
162167
}
163-
164-
serialize() : string {
165-
return this.nullable
168+
169+
serialize(): string {
170+
const py_type = this.nullable
166171
? `Optional[${this.py_type.serialize()}]`
167172
: this.py_type.serialize();
173+
return ` ${this.name}: Annotated[${py_type}, Field(alias="${this.pg_name}")]`
168174
}
169-
}
170175

171-
class PythonClassAttribute implements Serializable {
172-
name: string;
173-
pg_name: string;
174-
py_type: PythonConcreteType;
175-
constructor(name: string, py_type: PythonConcreteType) {
176-
this.name = formatForPyAttributeName(name);
177-
this.pg_name = name;
178-
this.py_type = py_type;
179-
}
180-
serialize(): string {
181-
return ` ${this.name}: Annotated[${this.py_type.serialize()}, Field(alias="${this.pg_name}")]`
182-
}
183176
}
184177

185178
class PythonClass implements Serializable {
186179
name: string;
180+
table_name: string;
181+
parent_class: string;
187182
schema: PostgresSchema;
188183
class_attributes: PythonClassAttribute[];
189184

190-
191-
constructor(name: string, schema: PostgresSchema, class_attributes: PythonClassAttribute[]) {
185+
constructor(name: string, schema: PostgresSchema, class_attributes: PythonClassAttribute[], parent_class: string="BaseModel") {
192186
this.schema = schema;
193187
this.class_attributes = class_attributes;
188+
this.table_name = name;
194189
this.name = `${formatForPyClassName(schema.name)}${formatForPyClassName(name)}`;
190+
this.parent_class = parent_class;
195191
}
196192
serialize(): string {
197193
const attributes = this.class_attributes.length > 0
198194
? this.class_attributes.map((attr) => attr.serialize()).join('\n')
199195
: " pass";
200-
return `class ${this.name}(BaseModel):\n${attributes}`.trim();
196+
return `class ${this.name}(${this.parent_class}):\n${attributes}`;
201197
}
198+
199+
update() : PythonClass {
200+
// Converts all attributes to nullable
201+
const attrs = this.class_attributes
202+
.filter((attr) => attr.mutable || attr.is_identity)
203+
.map((attr) => new PythonClassAttribute(attr.name, attr.py_type, true, attr.mutable, attr.has_default, attr.is_identity))
204+
return new PythonClass(`${this.table_name}_update`, this.schema, attrs, "TypedDict")
205+
}
206+
207+
insert() : PythonClass {
208+
// Converts all attributes that have a default to nullable.
209+
const attrs = this.class_attributes
210+
.map((attr) => new PythonClassAttribute(attr.name, attr.py_type, attr.has_default || attr.nullable, attr.mutable, attr.has_default, attr.is_identity));
211+
return new PythonClass(`${this.table_name}_insert`, this.schema, attrs, "TypedDict")
212+
}
213+
202214
}
203215

204216
function concatLines(items: Serializable[]): string {
@@ -266,29 +278,36 @@ export const apply = ({
266278
const ctx = new PythonContext(types, columns, schemas);
267279
const py_tables = tables
268280
.filter((table) => schemas.some((schema) => schema.name === table.schema))
269-
.map((table) => ctx.tableToClass(table));
281+
.flatMap((table) => {
282+
const py_class = ctx.tableToClass(table);
283+
return [py_class, py_class.insert(), py_class.update()];
284+
});
285+
286+
const composite_types = types
287+
.filter((type) => type.attributes.length > 0)
288+
.map((type) => ctx.typeToClass(type));
270289

271-
const composite_types = types.filter((type) => type.attributes.length > 0).map((type) => ctx.typeToClass(type));
272-
console.log(views);
273290
const py_views = views.map((view) => ctx.viewToClass(view));
274291
const py_matviews = materializedViews.map((matview) => ctx.matViewToClass(matview));
275292

276293
let output = `
294+
from __future__ import annotations
295+
277296
import datetime
278-
from typing import Annotated, Any, List, Literal, Optional, TypeAlias
297+
from typing import Annotated, Any, List, Literal, Optional, TypeAlias, TypedDict
279298
280299
from pydantic import BaseModel, Field, Json
281300
282301
${concatLines(Object.values(ctx.user_enums))}
283302
284-
${concatLines(composite_types)}
285-
286303
${concatLines(py_tables)}
287304
288305
${concatLines(py_views)}
289306
290307
${concatLines(py_matviews)}
291308
309+
${concatLines(composite_types)}
310+
292311
`.trim()
293312

294313
return output
@@ -306,7 +325,6 @@ ${concatLines(py_matviews)}
306325
* ```
307326
*/
308327
function formatForPyClassName(name: string): string {
309-
console.log(name)
310328
return name
311329
.split(/[^a-zA-Z0-9]/)
312330
.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`)

0 commit comments

Comments
 (0)