@@ -7,9 +7,6 @@ import type {
77 PostgresView ,
88} from '../../lib/index.js'
99import type { GeneratorMetadata } from '../../lib/generators.js'
10- import { console } from 'inspector/promises' ;
11-
12- type Operation = 'Select' | 'Insert' | 'Update'
1310
1411interface Serializable {
1512 serialize ( ) : string
@@ -18,7 +15,7 @@ interface Serializable {
1815class 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
185178class 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
204216function 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+
277296import datetime
278- from typing import Annotated, Any, List, Literal, Optional, TypeAlias
297+ from typing import Annotated, Any, List, Literal, Optional, TypeAlias, TypedDict
279298
280299from 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 */
308327function formatForPyClassName ( name : string ) : string {
309- console . log ( name )
310328 return name
311329 . split ( / [ ^ a - z A - Z 0 - 9 ] / )
312330 . map ( ( word ) => `${ word [ 0 ] . toUpperCase ( ) } ${ word . slice ( 1 ) } ` )
0 commit comments