Skip to content

Commit

Permalink
Add more tests and handle more features.
Browse files Browse the repository at this point in the history
  • Loading branch information
vpetrovykh committed Nov 15, 2024
1 parent be2af63 commit 0885a10
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 26 deletions.
93 changes: 70 additions & 23 deletions edgedb/gelalchemy/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import edgedb
import json
import re


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

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

def get_object_type_name(name):
# for now just assume stuff is in "defualt" module
return name.replace('default::', '')

def get_clean_name(name):
# Just remove default module name
if name.startswith('default::'):
name = name[9:]
elif '::' in name:
raise RuntimeError(f'Only the default modile is supported: {name}')

# After this replace all non-alphanumeric chartacters with '_'
name = CLEAN_RE.sub('_', name)

# Prefix with '_' if name starts with a digit
if name[0].isnumeric():
name = '_' + name

return name


def get_sql_name(name):
# Just remove default module name
if name.startswith('default::'):
name = name[9:]
elif '::' in name:
raise RuntimeError(f'Only the default modile is supported: {name}')

return name


def get_schema_json(client):
Expand All @@ -88,31 +113,36 @@ def _process_links(types):
link_tables = []
type_map = {}
for spec in types:
type_map[get_object_type_name(spec['name'])] = spec
type_map[get_clean_name(spec['name'])] = spec

for spec in types:
for link in spec['links']:
if link['name'] != '__type__':
target = get_object_type_name(link['target']['name'])
target = get_clean_name(link['target']['name'])
sql_target = get_sql_name(link['target']['name'])
cardinality = link['cardinality']
exclusive = link['exclusive']
name = get_clean_name(link['name'])
sql_name = get_sql_name(link['name'])

objtype = type_map[target]
objtype['backlinks'].append({
'name': f'backlink_via_{link["name"]}',
'name': f'backlink_via_{name}',
'cardinality': 'One' if exclusive else 'Many',
'exclusive': cardinality == 'One',
'target': {'name': spec['name']},
})

# Add a link table for One-to-Many and Many-to-Many
if cardinality == 'Many':
source = get_object_type_name(spec["name"])
source = get_clean_name(spec["name"])
sql_source = get_sql_name(spec["name"])
link_tables.append({
'name': f'{source}_{link["name"]}_table',
'table': f'{source}.{link["name"]}',
'source': f'{source}.id',
'target': f'{target}.id',
'name': f'{source}_{name}_table',
'table': f'{sql_source}.{sql_name}',
'source': f'{sql_source}.id',
'target': f'{sql_target}.id',
'properties': link['properties'],
})

return {
Expand All @@ -127,17 +157,24 @@ def render_link_table(spec):
{spec["table"]!r},
Base.metadata,
Column("source", ForeignKey({spec["source"]!r})),
Column("target", ForeignKey({spec["target"]!r})),
)\
''')
Column("target", ForeignKey({spec["target"]!r})),''')

for prop in spec['properties']:
sql_name = get_sql_name(prop['name'])
_, sqlatype = GEL_SCALAR_MAP[prop['target']['name']]

print(f'{INDENT}Column({sql_name!r}, {sqlatype}()),')

print(')')


def render_type(spec):
# assume nice names for now
name = get_object_type_name(spec['name'])
name = get_clean_name(spec['name'])
sql_name = get_sql_name(spec['name'])

print(f'class {name}(Base):')
print(f'{INDENT}__tablename__ = {name!r}')
print(f'{INDENT}__tablename__ = {sql_name!r}')

# Add two fields that all objects have
print(
Expand Down Expand Up @@ -169,30 +206,40 @@ def render_type(spec):


def render_prop(spec):
name = spec['name']
name = get_clean_name(spec['name'])
sql_name = get_sql_name(spec['name'])
nullable = not spec['required']

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

maybe_sql_name = ''
if name != sql_name:
maybe_sql_name = f'{sql_name!r}, '

print(
f'\
{name}: Mapped[{pytype}] = '
f'mapped_column({sqlatype}(), nullable={nullable})'
f'mapped_column({maybe_sql_name}{sqlatype}(), nullable={nullable})'
)


def render_link(spec, parent):
name = spec['name']
name = get_clean_name(spec['name'])
sql_name = get_sql_name(spec['name'])
nullable = not spec['required']
target = get_object_type_name(spec['target']['name'])
target = get_clean_name(spec['target']['name'])
cardinality = spec['cardinality']
bklink = f'backlink_via_{name}'

maybe_sql_name = ''
if name != sql_name:
maybe_sql_name = f'{sql_name + "_id"!r}, '

if cardinality == 'One':
print(
f'{INDENT}{name}_id: Mapped[uuid.UUID] = '
f'mapped_column(Uuid(), ForeignKey("{target}.id"), '
f'nullable={nullable})'
f'mapped_column({maybe_sql_name}Uuid(), '
f'ForeignKey("{target}.id"), nullable={nullable})'
)
print(
f'{INDENT}{name}: Mapped[{target!r}] = '
Expand All @@ -210,7 +257,7 @@ def render_link(spec, parent):

def render_backlink(spec):
name = spec['name']
target = get_object_type_name(spec['target']['name'])
target = get_clean_name(spec['target']['name'])
cardinality = spec['cardinality']
exclusive = spec['exclusive']
bklink = name.replace('backlink_via_', '', 1)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
'flake8-bugbear~=24.4.26',
'flake8~=7.0.0',
'uvloop>=0.15.1; platform_system != "Windows"',
'SQLAlchemy>=2.0.0',
]

# Dependencies required to build documentation.
Expand Down
25 changes: 25 additions & 0 deletions tests/dbsetup/features.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
insert `Whitespace Name` {name := 'space'};
insert `~~~Not-alpha=numeric*]name` {name := 'alnum'};;
insert `3.14!=0` {name := 'digital'};;
insert Child {num := 0};
insert Child {num := 1};

insert OddProps {
`has space`:= 0,
`ascii/\/\/\/art:)`:= 1,
`123`:= 2,
};
with child := (select Child filter .num = 0)
insert OddLink {
`a child` := child,
};

insert HasLinkPropsA;
update HasLinkPropsA
set {
children += (select Child{@a := 'hello'} filter .num = 0)
};
update HasLinkPropsA
set {
children += (select Child{@a := 'world'} filter .num = 1)
};
30 changes: 30 additions & 0 deletions tests/dbsetup/features.esdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
abstract type Named {
required name: str;
}

type `Whitespace Name` extending Named;
type `~~~Not-alpha=numeric*]name` extending Named;
type `3.14!=0` extending Named;

type OddProps {
property `has space`: int64;
property `ascii/\/\/\/art:)`: int64;
property `123`: int64;
};

type Child {
required property num: int64 {
constraint exclusive;
}
};

type OddLink {
link `a child`: Child;
# multi links with weird names not implemented yet
};

type HasLinkPropsA {
multi link children: Child {
property a: str;
}
};
Loading

0 comments on commit 0885a10

Please sign in to comment.