Skip to content

Commit 0885a10

Browse files
committed
Add more tests and handle more features.
1 parent be2af63 commit 0885a10

File tree

6 files changed

+429
-26
lines changed

6 files changed

+429
-26
lines changed

edgedb/gelalchemy/models.py

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import edgedb
22
import json
3+
import re
34

45

56
DSN = 'edgedb://edgedb@localhost:5656/edgedb?tls_security=insecure'
@@ -65,10 +66,34 @@
6566
not re_test('^(std|cfg|sys|schema)::', .name);
6667
'''
6768

69+
CLEAN_RE = re.compile(r'[^A-Za-z0-9]+')
6870

69-
def get_object_type_name(name):
70-
# for now just assume stuff is in "defualt" module
71-
return name.replace('default::', '')
71+
72+
def get_clean_name(name):
73+
# Just remove default module name
74+
if name.startswith('default::'):
75+
name = name[9:]
76+
elif '::' in name:
77+
raise RuntimeError(f'Only the default modile is supported: {name}')
78+
79+
# After this replace all non-alphanumeric chartacters with '_'
80+
name = CLEAN_RE.sub('_', name)
81+
82+
# Prefix with '_' if name starts with a digit
83+
if name[0].isnumeric():
84+
name = '_' + name
85+
86+
return name
87+
88+
89+
def get_sql_name(name):
90+
# Just remove default module name
91+
if name.startswith('default::'):
92+
name = name[9:]
93+
elif '::' in name:
94+
raise RuntimeError(f'Only the default modile is supported: {name}')
95+
96+
return name
7297

7398

7499
def get_schema_json(client):
@@ -88,31 +113,36 @@ def _process_links(types):
88113
link_tables = []
89114
type_map = {}
90115
for spec in types:
91-
type_map[get_object_type_name(spec['name'])] = spec
116+
type_map[get_clean_name(spec['name'])] = spec
92117

93118
for spec in types:
94119
for link in spec['links']:
95120
if link['name'] != '__type__':
96-
target = get_object_type_name(link['target']['name'])
121+
target = get_clean_name(link['target']['name'])
122+
sql_target = get_sql_name(link['target']['name'])
97123
cardinality = link['cardinality']
98124
exclusive = link['exclusive']
125+
name = get_clean_name(link['name'])
126+
sql_name = get_sql_name(link['name'])
99127

100128
objtype = type_map[target]
101129
objtype['backlinks'].append({
102-
'name': f'backlink_via_{link["name"]}',
130+
'name': f'backlink_via_{name}',
103131
'cardinality': 'One' if exclusive else 'Many',
104132
'exclusive': cardinality == 'One',
105133
'target': {'name': spec['name']},
106134
})
107135

108136
# Add a link table for One-to-Many and Many-to-Many
109137
if cardinality == 'Many':
110-
source = get_object_type_name(spec["name"])
138+
source = get_clean_name(spec["name"])
139+
sql_source = get_sql_name(spec["name"])
111140
link_tables.append({
112-
'name': f'{source}_{link["name"]}_table',
113-
'table': f'{source}.{link["name"]}',
114-
'source': f'{source}.id',
115-
'target': f'{target}.id',
141+
'name': f'{source}_{name}_table',
142+
'table': f'{sql_source}.{sql_name}',
143+
'source': f'{sql_source}.id',
144+
'target': f'{sql_target}.id',
145+
'properties': link['properties'],
116146
})
117147

118148
return {
@@ -127,17 +157,24 @@ def render_link_table(spec):
127157
{spec["table"]!r},
128158
Base.metadata,
129159
Column("source", ForeignKey({spec["source"]!r})),
130-
Column("target", ForeignKey({spec["target"]!r})),
131-
)\
132-
''')
160+
Column("target", ForeignKey({spec["target"]!r})),''')
161+
162+
for prop in spec['properties']:
163+
sql_name = get_sql_name(prop['name'])
164+
_, sqlatype = GEL_SCALAR_MAP[prop['target']['name']]
165+
166+
print(f'{INDENT}Column({sql_name!r}, {sqlatype}()),')
167+
168+
print(')')
133169

134170

135171
def render_type(spec):
136172
# assume nice names for now
137-
name = get_object_type_name(spec['name'])
173+
name = get_clean_name(spec['name'])
174+
sql_name = get_sql_name(spec['name'])
138175

139176
print(f'class {name}(Base):')
140-
print(f'{INDENT}__tablename__ = {name!r}')
177+
print(f'{INDENT}__tablename__ = {sql_name!r}')
141178

142179
# Add two fields that all objects have
143180
print(
@@ -169,30 +206,40 @@ def render_type(spec):
169206

170207

171208
def render_prop(spec):
172-
name = spec['name']
209+
name = get_clean_name(spec['name'])
210+
sql_name = get_sql_name(spec['name'])
173211
nullable = not spec['required']
174212

175213
pytype, sqlatype = GEL_SCALAR_MAP[spec['target']['name']]
176214

215+
maybe_sql_name = ''
216+
if name != sql_name:
217+
maybe_sql_name = f'{sql_name!r}, '
218+
177219
print(
178220
f'\
179221
{name}: Mapped[{pytype}] = '
180-
f'mapped_column({sqlatype}(), nullable={nullable})'
222+
f'mapped_column({maybe_sql_name}{sqlatype}(), nullable={nullable})'
181223
)
182224

183225

184226
def render_link(spec, parent):
185-
name = spec['name']
227+
name = get_clean_name(spec['name'])
228+
sql_name = get_sql_name(spec['name'])
186229
nullable = not spec['required']
187-
target = get_object_type_name(spec['target']['name'])
230+
target = get_clean_name(spec['target']['name'])
188231
cardinality = spec['cardinality']
189232
bklink = f'backlink_via_{name}'
190233

234+
maybe_sql_name = ''
235+
if name != sql_name:
236+
maybe_sql_name = f'{sql_name + "_id"!r}, '
237+
191238
if cardinality == 'One':
192239
print(
193240
f'{INDENT}{name}_id: Mapped[uuid.UUID] = '
194-
f'mapped_column(Uuid(), ForeignKey("{target}.id"), '
195-
f'nullable={nullable})'
241+
f'mapped_column({maybe_sql_name}Uuid(), '
242+
f'ForeignKey("{target}.id"), nullable={nullable})'
196243
)
197244
print(
198245
f'{INDENT}{name}: Mapped[{target!r}] = '
@@ -210,7 +257,7 @@ def render_link(spec, parent):
210257

211258
def render_backlink(spec):
212259
name = spec['name']
213-
target = get_object_type_name(spec['target']['name'])
260+
target = get_clean_name(spec['target']['name'])
214261
cardinality = spec['cardinality']
215262
exclusive = spec['exclusive']
216263
bklink = name.replace('backlink_via_', '', 1)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
'flake8-bugbear~=24.4.26',
5151
'flake8~=7.0.0',
5252
'uvloop>=0.15.1; platform_system != "Windows"',
53+
'SQLAlchemy>=2.0.0',
5354
]
5455

5556
# Dependencies required to build documentation.

tests/dbsetup/features.edgeql

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
insert `Whitespace Name` {name := 'space'};
2+
insert `~~~Not-alpha=numeric*]name` {name := 'alnum'};;
3+
insert `3.14!=0` {name := 'digital'};;
4+
insert Child {num := 0};
5+
insert Child {num := 1};
6+
7+
insert OddProps {
8+
`has space`:= 0,
9+
`ascii/\/\/\/art:)`:= 1,
10+
`123`:= 2,
11+
};
12+
with child := (select Child filter .num = 0)
13+
insert OddLink {
14+
`a child` := child,
15+
};
16+
17+
insert HasLinkPropsA;
18+
update HasLinkPropsA
19+
set {
20+
children += (select Child{@a := 'hello'} filter .num = 0)
21+
};
22+
update HasLinkPropsA
23+
set {
24+
children += (select Child{@a := 'world'} filter .num = 1)
25+
};

tests/dbsetup/features.esdl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
abstract type Named {
2+
required name: str;
3+
}
4+
5+
type `Whitespace Name` extending Named;
6+
type `~~~Not-alpha=numeric*]name` extending Named;
7+
type `3.14!=0` extending Named;
8+
9+
type OddProps {
10+
property `has space`: int64;
11+
property `ascii/\/\/\/art:)`: int64;
12+
property `123`: int64;
13+
};
14+
15+
type Child {
16+
required property num: int64 {
17+
constraint exclusive;
18+
}
19+
};
20+
21+
type OddLink {
22+
link `a child`: Child;
23+
# multi links with weird names not implemented yet
24+
};
25+
26+
type HasLinkPropsA {
27+
multi link children: Child {
28+
property a: str;
29+
}
30+
};

0 commit comments

Comments
 (0)