Codegen: improve implementation of generated parent/child relationship

This improves the implementation of the generated parent/child
relationship by adding a new `all_children` field to `ql.Class` which
lists all children (both direct and inherited) of a class, carefully
avoiding duplicating children in case of diamond inheritance. This:
* simplifies the generated code,
* avoid children ambiguities in case of diamond inheritance.

This only comes with some changes in the order of children in the
generated tests (we were previously sorting bases alphabetically there).
For the rest this should be a non-functional change.
This commit is contained in:
Paolo Tranquilli
2025-06-24 17:26:24 +02:00
parent c4a385fa6a
commit 1dcd60527c
19 changed files with 2120 additions and 5238 deletions

View File

@@ -30,6 +30,7 @@ import subprocess
import typing
import itertools
import os
import dataclasses
import inflection
@@ -113,117 +114,201 @@ def _get_doc(cls: schema.Class, prop: schema.Property, plural=None):
return f"{prop_name} of this {class_name}"
def _type_is_hideable(t: str, lookup: typing.Dict[str, schema.ClassBase]) -> bool:
if t in lookup:
match lookup[t]:
@dataclasses.dataclass
class Resolver:
lookup: typing.Dict[str, schema.ClassBase]
_property_cache: typing.Dict[tuple[int, int], ql.Property] = dataclasses.field(
default_factory=dict, init=False
)
_class_cache: typing.Dict[int, ql.Class] = dataclasses.field(
default_factory=dict, init=False
)
def _type_is_hideable(self, t: str) -> bool:
match self.lookup.get(t):
case schema.Class() as cls:
return "ql_hideable" in cls.pragmas
return False
case _:
return False
def get_ql_property(
self,
cls: schema.Class,
prop: schema.Property,
) -> ql.Property:
cache_key = (id(cls), id(prop))
if cache_key not in self._property_cache:
args = dict(
type=prop.type if not prop.is_predicate else "predicate",
qltest_skip="qltest_skip" in prop.pragmas,
is_optional=prop.is_optional,
is_predicate=prop.is_predicate,
is_unordered=prop.is_unordered,
description=prop.description,
synth=bool(cls.synth) or prop.synth,
type_is_hideable=self._type_is_hideable(prop.type),
type_is_codegen_class=prop.type in self.lookup
and not self.lookup[prop.type].imported,
internal="ql_internal" in prop.pragmas,
cfg=prop.is_child
and prop.type in self.lookup
and self.lookup[prop.type].cfg,
is_child=prop.is_child,
)
ql_name = prop.pragmas.get("ql_name", prop.name)
db_table_name = prop.pragmas.get("ql_db_table_name")
if db_table_name and prop.is_single:
raise Error(
f"`db_table_name` pragma is not supported for single properties, but {cls.name}.{prop.name} has it"
)
if prop.is_single:
args.update(
singular=inflection.camelize(ql_name),
tablename=inflection.tableize(cls.name),
tableparams=["this"]
+ [
"result" if p is prop else "_"
for p in cls.properties
if p.is_single
],
doc=_get_doc(cls, prop),
)
elif prop.is_repeated:
args.update(
singular=inflection.singularize(inflection.camelize(ql_name)),
plural=inflection.pluralize(inflection.camelize(ql_name)),
tablename=db_table_name
or inflection.tableize(f"{cls.name}_{prop.name}"),
tableparams=(
["this", "index", "result"]
if not prop.is_unordered
else ["this", "result"]
),
doc=_get_doc(cls, prop, plural=False),
doc_plural=_get_doc(cls, prop, plural=True),
)
elif prop.is_optional:
args.update(
singular=inflection.camelize(ql_name),
tablename=db_table_name
or inflection.tableize(f"{cls.name}_{prop.name}"),
tableparams=["this", "result"],
doc=_get_doc(cls, prop),
)
elif prop.is_predicate:
args.update(
singular=inflection.camelize(ql_name, uppercase_first_letter=False),
tablename=db_table_name
or inflection.underscore(f"{cls.name}_{prop.name}"),
tableparams=["this"],
doc=_get_doc(cls, prop),
)
else:
raise ValueError(
f"unknown property kind for {prop.name} from {cls.name}"
)
self._property_cache[cache_key] = ql.Property(**args)
return self._property_cache[cache_key]
def get_ql_property(
cls: schema.Class,
prop: schema.Property,
lookup: typing.Dict[str, schema.ClassBase],
prev_child: str = "",
) -> ql.Property:
def get_ql_class(self, cls: schema.Class) -> ql.Class:
cache_key = id(cls)
if cache_key not in self._class_cache:
if "ql_name" in cls.pragmas:
raise Error(
"ql_name is not supported yet for classes, only for properties"
)
properties = [self.get_ql_property(cls, p) for p in cls.properties]
all_children = [
ql.Child(self.get_ql_property(c, p))
for c, p in self._get_all_properties(cls)
if p.is_child
]
for prev, child in zip([""] + all_children, all_children):
child.prev = prev and prev.property.singular
self._class_cache[cache_key] = ql.Class(
name=cls.name,
bases=cls.bases,
bases_impl=[base + "Impl::" + base for base in cls.bases],
final=not cls.derived,
properties=properties,
all_children=all_children,
dir=pathlib.Path(cls.group or ""),
doc=cls.doc,
hideable="ql_hideable" in cls.pragmas,
internal="ql_internal" in cls.pragmas,
cfg=cls.cfg,
)
return self._class_cache[cache_key]
args = dict(
type=prop.type if not prop.is_predicate else "predicate",
qltest_skip="qltest_skip" in prop.pragmas,
prev_child=prev_child if prop.is_child else None,
is_optional=prop.is_optional,
is_predicate=prop.is_predicate,
is_unordered=prop.is_unordered,
description=prop.description,
synth=bool(cls.synth) or prop.synth,
type_is_hideable=_type_is_hideable(prop.type, lookup),
type_is_codegen_class=prop.type in lookup and not lookup[prop.type].imported,
internal="ql_internal" in prop.pragmas,
)
ql_name = prop.pragmas.get("ql_name", prop.name)
db_table_name = prop.pragmas.get("ql_db_table_name")
if db_table_name and prop.is_single:
raise Error(
f"`db_table_name` pragma is not supported for single properties, but {cls.name}.{prop.name} has it"
def get_ql_cfg_class(
self,
cls: schema.Class,
) -> ql.CfgClass:
return ql.CfgClass(
name=cls.name,
bases=[base for base in cls.bases if self.lookup[base.base].cfg],
properties=cls.properties,
doc=cls.doc,
)
if prop.is_single:
args.update(
singular=inflection.camelize(ql_name),
tablename=inflection.tableize(cls.name),
tableparams=["this"]
+ ["result" if p is prop else "_" for p in cls.properties if p.is_single],
doc=_get_doc(cls, prop),
)
elif prop.is_repeated:
args.update(
singular=inflection.singularize(inflection.camelize(ql_name)),
plural=inflection.pluralize(inflection.camelize(ql_name)),
tablename=db_table_name or inflection.tableize(f"{cls.name}_{prop.name}"),
tableparams=(
["this", "index", "result"]
if not prop.is_unordered
else ["this", "result"]
),
doc=_get_doc(cls, prop, plural=False),
doc_plural=_get_doc(cls, prop, plural=True),
)
elif prop.is_optional:
args.update(
singular=inflection.camelize(ql_name),
tablename=db_table_name or inflection.tableize(f"{cls.name}_{prop.name}"),
tableparams=["this", "result"],
doc=_get_doc(cls, prop),
)
elif prop.is_predicate:
args.update(
singular=inflection.camelize(ql_name, uppercase_first_letter=False),
tablename=db_table_name or inflection.underscore(f"{cls.name}_{prop.name}"),
tableparams=["this"],
doc=_get_doc(cls, prop),
)
else:
raise ValueError(f"unknown property kind for {prop.name} from {cls.name}")
return ql.Property(**args)
def _get_all_properties(
self,
cls: schema.Class,
already_seen: typing.Optional[typing.Set[int]] = None,
) -> typing.Iterable[typing.Tuple[schema.Class, schema.Property]]:
# deduplicate using ids
if already_seen is None:
already_seen = set()
for b in cls.bases:
base = self.lookup[b]
for item in self._get_all_properties(base, already_seen):
yield item
for p in cls.properties:
if id(p) not in already_seen:
already_seen.add(id(p))
yield cls, p
def get_ql_class(
cls: schema.Class, lookup: typing.Dict[str, schema.ClassBase]
) -> ql.Class:
if "ql_name" in cls.pragmas:
raise Error("ql_name is not supported yet for classes, only for properties")
prev_child = ""
properties = []
for p in cls.properties:
prop = get_ql_property(cls, p, lookup, prev_child)
if prop.is_child:
prev_child = prop.singular
if prop.type in lookup and lookup[prop.type].cfg:
prop.cfg = True
properties.append(prop)
return ql.Class(
name=cls.name,
bases=cls.bases,
bases_impl=[base + "Impl::" + base for base in cls.bases],
final=not cls.derived,
properties=properties,
dir=pathlib.Path(cls.group or ""),
doc=cls.doc,
hideable="ql_hideable" in cls.pragmas,
internal="ql_internal" in cls.pragmas,
cfg=cls.cfg,
)
def get_all_properties_to_be_tested(
self,
cls: schema.Class,
) -> typing.Iterable[ql.PropertyForTest]:
for c, p in self._get_all_properties(cls):
if not ("qltest_skip" in c.pragmas or "qltest_skip" in p.pragmas):
p = self.get_ql_property(c, p)
yield ql.PropertyForTest(
p.getter,
is_total=p.is_single or p.is_predicate,
type=p.type if not p.is_predicate else None,
is_indexed=p.is_indexed,
)
if p.is_repeated and not p.is_optional:
yield ql.PropertyForTest(f"getNumberOf{p.plural}", type="int")
elif p.is_optional and not p.is_repeated:
yield ql.PropertyForTest(f"has{p.singular}")
def _is_in_qltest_collapsed_hierarchy(
self,
cls: schema.Class,
) -> bool:
return (
"qltest_collapse_hierarchy" in cls.pragmas
or self._is_under_qltest_collapsed_hierarchy(cls)
)
def get_ql_cfg_class(
cls: schema.Class, lookup: typing.Dict[str, ql.Class]
) -> ql.CfgClass:
return ql.CfgClass(
name=cls.name,
bases=[base for base in cls.bases if lookup[base.base].cfg],
properties=cls.properties,
doc=cls.doc,
)
def _is_under_qltest_collapsed_hierarchy(
self,
cls: schema.Class,
) -> bool:
return "qltest_uncollapse_hierarchy" not in cls.pragmas and any(
self._is_in_qltest_collapsed_hierarchy(self.lookup[b]) for b in cls.bases
)
def should_skip_qltest(self, cls: schema.Class) -> bool:
return (
"qltest_skip" in cls.pragmas
or not (cls.final or "qltest_collapse_hierarchy" in cls.pragmas)
or self._is_under_qltest_collapsed_hierarchy(cls)
)
def _to_db_type(x: str) -> str:
@@ -330,43 +415,6 @@ def _get_path_public(cls: schema.Class) -> pathlib.Path:
).with_suffix(".qll")
def _get_all_properties(
cls: schema.Class,
lookup: typing.Dict[str, schema.Class],
already_seen: typing.Optional[typing.Set[int]] = None,
) -> typing.Iterable[typing.Tuple[schema.Class, schema.Property]]:
# deduplicate using ids
if already_seen is None:
already_seen = set()
for b in sorted(cls.bases):
base = lookup[b]
for item in _get_all_properties(base, lookup, already_seen):
yield item
for p in cls.properties:
if id(p) not in already_seen:
already_seen.add(id(p))
yield cls, p
def _get_all_properties_to_be_tested(
cls: schema.Class, lookup: typing.Dict[str, schema.Class]
) -> typing.Iterable[ql.PropertyForTest]:
for c, p in _get_all_properties(cls, lookup):
if not ("qltest_skip" in c.pragmas or "qltest_skip" in p.pragmas):
# TODO here operations are duplicated, but should be better if we split ql and qltest generation
p = get_ql_property(c, p, lookup)
yield ql.PropertyForTest(
p.getter,
is_total=p.is_single or p.is_predicate,
type=p.type if not p.is_predicate else None,
is_indexed=p.is_indexed,
)
if p.is_repeated and not p.is_optional:
yield ql.PropertyForTest(f"getNumberOf{p.plural}", type="int")
elif p.is_optional and not p.is_repeated:
yield ql.PropertyForTest(f"has{p.singular}")
def _partition_iter(x, pred):
x1, x2 = itertools.tee(x)
return filter(pred, x1), itertools.filterfalse(pred, x2)
@@ -377,31 +425,6 @@ def _partition(l, pred):
return map(list, _partition_iter(l, pred))
def _is_in_qltest_collapsed_hierarchy(
cls: schema.Class, lookup: typing.Dict[str, schema.Class]
):
return (
"qltest_collapse_hierarchy" in cls.pragmas
or _is_under_qltest_collapsed_hierarchy(cls, lookup)
)
def _is_under_qltest_collapsed_hierarchy(
cls: schema.Class, lookup: typing.Dict[str, schema.Class]
):
return "qltest_uncollapse_hierarchy" not in cls.pragmas and any(
_is_in_qltest_collapsed_hierarchy(lookup[b], lookup) for b in cls.bases
)
def should_skip_qltest(cls: schema.Class, lookup: typing.Dict[str, schema.Class]):
return (
"qltest_skip" in cls.pragmas
or not (cls.final or "qltest_collapse_hierarchy" in cls.pragmas)
or _is_under_qltest_collapsed_hierarchy(cls, lookup)
)
def _get_stub(
cls: schema.Class, base_import: str, generated_import_prefix: str
) -> ql.Stub:
@@ -487,8 +510,10 @@ def generate(opts, renderer):
data = schemaloader.load_file(input)
resolver = Resolver(data.classes)
classes = {
name: get_ql_class(cls, data.classes)
name: resolver.get_ql_class(cls)
for name, cls in data.classes.items()
if not cls.imported
}
@@ -538,7 +563,7 @@ def generate(opts, renderer):
)
imports_impl[c.name + "Impl"] = path_impl + "Impl"
if c.cfg:
cfg_classes.append(get_ql_cfg_class(c, classes))
cfg_classes.append(resolver.get_ql_cfg_class(c))
for c in classes.values():
qll = out / c.path.with_suffix(".qll")
@@ -609,7 +634,7 @@ def generate(opts, renderer):
for c in data.classes.values():
if c.imported:
continue
if should_skip_qltest(c, data.classes):
if resolver.should_skip_qltest(c):
continue
test_with_name = c.pragmas.get("qltest_test_with")
test_with = data.classes[test_with_name] if test_with_name else c
@@ -626,7 +651,7 @@ def generate(opts, renderer):
)
continue
total_props, partial_props = _partition(
_get_all_properties_to_be_tested(c, data.classes),
resolver.get_all_properties_to_be_tested(c),
lambda p: p.is_total,
)
renderer.render(

View File

@@ -59,13 +59,12 @@ def generate(opts, renderer):
registry=opts.ql_test_output / ".generated_tests.list",
force=opts.force,
) as renderer:
resolver = qlgen.Resolver(schema.classes)
for cls in schema.classes.values():
if cls.imported:
continue
if (
qlgen.should_skip_qltest(cls, schema.classes)
or "rust_skip_doc_test" in cls.pragmas
):
if resolver.should_skip_qltest(cls) or "rust_skip_doc_test" in cls.pragmas:
continue
code = _get_code(cls.doc)
for p in schema.iter_properties(cls.name):

View File

@@ -37,7 +37,6 @@ class Property:
is_optional: bool = False
is_predicate: bool = False
is_unordered: bool = False
prev_child: Optional[str] = None
qltest_skip: bool = False
description: List[str] = field(default_factory=list)
doc: Optional[str] = None
@@ -48,6 +47,7 @@ class Property:
type_is_self: bool = False
internal: bool = False
cfg: bool = False
is_child: bool = False
def __post_init__(self):
if self.tableparams:
@@ -76,10 +76,6 @@ class Property:
def is_single(self):
return not (self.is_optional or self.is_repeated or self.is_predicate)
@property
def is_child(self):
return self.prev_child is not None
@property
def is_indexed(self) -> bool:
return self.is_repeated and not self.is_unordered
@@ -89,6 +85,12 @@ class Property:
return self.type + "Alias" if self.type_is_self else self.type
@dataclass
class Child:
property: Property
prev: str = ""
@dataclass
class Base:
base: str
@@ -107,6 +109,7 @@ class Class:
bases_impl: List[Base] = field(default_factory=list)
final: bool = False
properties: List[Property] = field(default_factory=list)
all_children: List[Child] = field(default_factory=list)
dir: pathlib.Path = pathlib.Path()
imports: List[str] = field(default_factory=list)
import_prefix: Optional[str] = None
@@ -148,7 +151,7 @@ class Class:
@property
def has_children(self) -> bool:
return any(p.is_child for p in self.properties)
return bool(self.all_children)
@property
def last_base(self) -> str:

View File

@@ -9,51 +9,39 @@ import {{.}}
private module Impl {
{{#classes}}
private Element getImmediateChildOf{{name}}({{name}} e, int index, string partialPredicateCall) {
{{! avoid unused argument warnings on root element, assuming the root element has no children }}
{{#root}}none(){{/root}}
{{^root}}
{{! b is the base offset 0, for ease of generation }}
{{! b<base> is constructed to be strictly greater than the indexes required for children coming from <base> }}
{{! n is the base offset for direct children, equal to the last base offset from above }}
{{! n<child> is constructed to be strictly greater than the indexes for <child> children }}
exists(int b{{#bases}}, int b{{.}}{{/bases}}, int n{{#properties}}{{#is_child}}, int n{{singular}}{{/is_child}}{{/properties}} |
b = 0
{{#bases}}
and
b{{.}} = b{{prev}} + 1 + max(int i | i = -1 or exists(getImmediateChildOf{{.}}(e, i, _)) | i)
{{/bases}}
and
n = b{{last_base}}
{{#properties}}
{{#is_child}}
{{! n<child> is defined on top of the previous definition }}
{{! for single and optional properties it adds 1 (regardless of whether the optional property exists) }}
{{! for repeated it adds 1 + the maximum index (which works for repeated optional as well) }}
and
n{{singular}} = n{{prev_child}} + 1{{#is_repeated}}+ max(int i | i = -1 or exists(e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(i)) | i){{/is_repeated}}
{{/is_child}}
{{/properties}} and (
none()
{{#bases}}
or
result = getImmediateChildOf{{.}}(e, index - b{{prev}}, partialPredicateCall)
{{/bases}}
{{#properties}}
{{#is_child}}
or
{{#is_repeated}}
result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(index - n{{prev_child}}) and partialPredicateCall = "{{singular}}(" + (index - n{{prev_child}}).toString() + ")"
{{/is_repeated}}
{{^is_repeated}}
index = n{{prev_child}} and result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}() and partialPredicateCall = "{{singular}}()"
{{/is_repeated}}
{{/is_child}}
{{/properties}}
))
{{/root}}
}
{{#final}}
private Element getImmediateChildOf{{name}}({{name}} e, int index, string partialPredicateCall) {
{{^has_children}}none(){{/has_children}}
{{#has_children}}
{{! n is the base offset 0, for ease of generation }}
{{! n<child> is constructed to be strictly greater than the indexes for <child> children }}
exists(int n{{#all_children}}, int n{{property.singular}}{{/all_children}} |
n = 0
{{#all_children}}
{{#property}}
{{! n<child> is defined on top of the previous definition }}
{{! for single and optional properties it adds 1 (regardless of whether the optional property exists) }}
{{! for repeated it adds 1 + the maximum index (which works for repeated optional as well) }}
and
n{{singular}} = n{{prev}} + 1{{#is_repeated}}+ max(int i | i = -1 or exists(e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(i)) | i){{/is_repeated}}
{{/property}}
{{/all_children}} and (
none()
{{#all_children}}
{{#property}}
or
{{#is_repeated}}
result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}(index - n{{prev}}) and partialPredicateCall = "{{singular}}(" + (index - n{{prev}}).toString() + ")"
{{/is_repeated}}
{{^is_repeated}}
index = n{{prev}} and result = e.get{{#type_is_hideable}}Immediate{{/type_is_hideable}}{{singular}}() and partialPredicateCall = "{{singular}}()"
{{/is_repeated}}
{{/property}}
{{/all_children}}
))
{{/has_children}}
}
{{/final}}
{{/classes}}
cached
Element getImmediateChild(Element e, int index, string partialAccessor) {

View File

@@ -133,22 +133,10 @@ def test_non_root_class():
assert not cls.root
@pytest.mark.parametrize(
"prev_child,is_child", [(None, False), ("", True), ("x", True)]
)
def test_is_child(prev_child, is_child):
p = ql.Property("Foo", "int", prev_child=prev_child)
assert p.is_child is is_child
def test_empty_class_no_children():
cls = ql.Class("Class", properties=[])
assert cls.has_children is False
def test_class_no_children():
cls = ql.Class(
"Class", properties=[ql.Property("Foo", "int"), ql.Property("Bar", "string")]
"Class",
all_children=[],
)
assert cls.has_children is False
@@ -156,11 +144,7 @@ def test_class_no_children():
def test_class_with_children():
cls = ql.Class(
"Class",
properties=[
ql.Property("Foo", "int"),
ql.Property("Child", "x", prev_child=""),
ql.Property("Bar", "string"),
],
all_children=[ql.Child(ql.Property("Foo", "int"))],
)
assert cls.has_children is True

View File

@@ -388,11 +388,101 @@ def test_internal_property(generate_classes):
def test_children(generate_classes):
expected_parent_property = ql.Property(
singular="ParentChild",
type="int",
is_child=True,
tablename="parents",
tableparams=["this", "result"],
doc="parent child of this parent",
)
expected_properties = [
ql.Property(
singular="A",
type="int",
tablename="my_objects",
tableparams=["this", "result", "_"],
doc="a of this my object",
),
ql.Property(
singular="Child1",
type="int",
tablename="my_objects",
tableparams=["this", "_", "result"],
is_child=True,
doc="child 1 of this my object",
),
ql.Property(
singular="B",
plural="Bs",
type="int",
tablename="my_object_bs",
tableparams=["this", "index", "result"],
doc="b of this my object",
doc_plural="bs of this my object",
),
ql.Property(
singular="Child",
plural="Children",
type="int",
tablename="my_object_children",
tableparams=["this", "index", "result"],
is_child=True,
doc="child of this my object",
doc_plural="children of this my object",
),
ql.Property(
singular="C",
type="int",
tablename="my_object_cs",
tableparams=["this", "result"],
is_optional=True,
doc="c of this my object",
),
ql.Property(
singular="Child3",
type="int",
tablename="my_object_child_3s",
tableparams=["this", "result"],
is_optional=True,
is_child=True,
doc="child 3 of this my object",
),
ql.Property(
singular="D",
plural="Ds",
type="int",
tablename="my_object_ds",
tableparams=["this", "index", "result"],
is_optional=True,
doc="d of this my object",
doc_plural="ds of this my object",
),
ql.Property(
singular="Child4",
plural="Child4s",
type="int",
tablename="my_object_child_4s",
tableparams=["this", "index", "result"],
is_optional=True,
is_child=True,
doc="child 4 of this my object",
doc_plural="child 4s of this my object",
),
]
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"Parent",
derived={"MyObject"},
properties=[
schema.SingleProperty("parent_child", "int", is_child=True),
],
),
schema.Class(
"MyObject",
bases=["Parent"],
properties=[
schema.SingleProperty("a", "int"),
schema.SingleProperty("child_1", "int", is_child=True),
@@ -413,87 +503,53 @@ def test_children(generate_classes):
name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"]
),
),
"Parent.qll": (
a_ql_class_public(name="Parent"),
a_ql_stub(name="Parent"),
a_ql_class(
name="Parent",
imports=[stub_import_prefix + "Parent"],
properties=[expected_parent_property],
all_children=[
ql.Child(
expected_parent_property,
),
],
),
),
"MyObject.qll": (
a_ql_class_public(name="MyObject"),
a_ql_class_public(name="MyObject", imports=[stub_import_prefix + "Parent"]),
a_ql_stub(name="MyObject"),
a_ql_class(
name="MyObject",
final=True,
properties=[
ql.Property(
singular="A",
type="int",
tablename="my_objects",
tableparams=["this", "result", "_"],
doc="a of this my object",
bases=["Parent"],
bases_impl=["ParentImpl::Parent"],
properties=expected_properties,
all_children=[
ql.Child(
expected_parent_property,
),
ql.Property(
singular="Child1",
type="int",
tablename="my_objects",
tableparams=["this", "_", "result"],
prev_child="",
doc="child 1 of this my object",
ql.Child(
expected_properties[1],
prev="ParentChild",
),
ql.Property(
singular="B",
plural="Bs",
type="int",
tablename="my_object_bs",
tableparams=["this", "index", "result"],
doc="b of this my object",
doc_plural="bs of this my object",
ql.Child(
expected_properties[3],
prev="Child1",
),
ql.Property(
singular="Child",
plural="Children",
type="int",
tablename="my_object_children",
tableparams=["this", "index", "result"],
prev_child="Child1",
doc="child of this my object",
doc_plural="children of this my object",
ql.Child(
expected_properties[5],
prev="Child",
),
ql.Property(
singular="C",
type="int",
tablename="my_object_cs",
tableparams=["this", "result"],
is_optional=True,
doc="c of this my object",
),
ql.Property(
singular="Child3",
type="int",
tablename="my_object_child_3s",
tableparams=["this", "result"],
is_optional=True,
prev_child="Child",
doc="child 3 of this my object",
),
ql.Property(
singular="D",
plural="Ds",
type="int",
tablename="my_object_ds",
tableparams=["this", "index", "result"],
is_optional=True,
doc="d of this my object",
doc_plural="ds of this my object",
),
ql.Property(
singular="Child4",
plural="Child4s",
type="int",
tablename="my_object_child_4s",
tableparams=["this", "index", "result"],
is_optional=True,
prev_child="Child3",
doc="child 4 of this my object",
doc_plural="child 4s of this my object",
ql.Child(
expected_properties[7],
prev="Child3",
),
],
imports=[stub_import_prefix + "MyObject"],
imports=[
stub_import_prefix + "internal.ParentImpl::Impl as ParentImpl"
],
),
),
}
@@ -547,14 +603,13 @@ def test_single_properties(generate_classes):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_optional_property(generate_classes, is_child, prev_child):
def test_optional_property(generate_classes):
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"MyObject",
properties=[schema.OptionalProperty("foo", "bar", is_child=is_child)],
properties=[schema.OptionalProperty("foo", "bar")],
),
]
) == {
@@ -578,7 +633,6 @@ def test_optional_property(generate_classes, is_child, prev_child):
tablename="my_object_foos",
tableparams=["this", "result"],
is_optional=True,
prev_child=prev_child,
doc="foo of this my object",
),
],
@@ -588,14 +642,13 @@ def test_optional_property(generate_classes, is_child, prev_child):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_repeated_property(generate_classes, is_child, prev_child):
def test_repeated_property(generate_classes):
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"MyObject",
properties=[schema.RepeatedProperty("foo", "bar", is_child=is_child)],
properties=[schema.RepeatedProperty("foo", "bar")],
),
]
) == {
@@ -619,7 +672,6 @@ def test_repeated_property(generate_classes, is_child, prev_child):
type="bar",
tablename="my_object_foos",
tableparams=["this", "index", "result"],
prev_child=prev_child,
doc="foo of this my object",
doc_plural="foos of this my object",
),
@@ -670,16 +722,13 @@ def test_repeated_unordered_property(generate_classes):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_repeated_optional_property(generate_classes, is_child, prev_child):
def test_repeated_optional_property(generate_classes):
assert generate_classes(
[
schema.Class("FakeRoot"),
schema.Class(
"MyObject",
properties=[
schema.RepeatedOptionalProperty("foo", "bar", is_child=is_child)
],
properties=[schema.RepeatedOptionalProperty("foo", "bar")],
),
]
) == {
@@ -704,7 +753,6 @@ def test_repeated_optional_property(generate_classes, is_child, prev_child):
tablename="my_object_foos",
tableparams=["this", "index", "result"],
is_optional=True,
prev_child=prev_child,
doc="foo of this my object",
doc_plural="foos of this my object",
),
@@ -743,14 +791,13 @@ def test_predicate_property(generate_classes):
}
@pytest.mark.parametrize("is_child,prev_child", [(False, None), (True, "")])
def test_single_class_property(generate_classes, is_child, prev_child):
def test_single_class_property(generate_classes):
assert generate_classes(
[
schema.Class("Bar"),
schema.Class(
"MyObject",
properties=[schema.SingleProperty("foo", "Bar", is_child=is_child)],
properties=[schema.SingleProperty("foo", "Bar")],
),
]
) == {
@@ -767,7 +814,6 @@ def test_single_class_property(generate_classes, is_child, prev_child):
type="Bar",
tablename="my_objects",
tableparams=["this", "result"],
prev_child=prev_child,
doc="foo of this my object",
type_is_codegen_class=True,
),