Skip to content

Commit 8511ba5

Browse files
authored
[core][fix] Simplify and fix owner lookup (#2128)
1 parent 91855cd commit 8511ba5

File tree

5 files changed

+56
-40
lines changed

5 files changed

+56
-40
lines changed

fixcore/fixcore/db/arango_query_rewrite.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ def combine_term_if_possible(term: Term, predicates: List[Predicate]) -> Term:
3737
for res in query_model.owners(pred.name):
3838
if res.fqn not in predefined_kinds_by_name:
3939
kinds.add(res.fqn)
40-
kinds.discard("resource") # all resources have this base kind - ignore it
41-
return IsTerm(kinds=sorted(kinds)).and_term(term) if kinds else term
40+
if not kinds or "resource" in kinds: # all resources have this base kind - ignore it
41+
return term
42+
else:
43+
return IsTerm(kinds=sorted(kinds)).and_term(term)
4244

4345
def change_term(term: Term) -> Term:
4446
if isinstance(term, CombinedTerm) and term.op == "or":

fixcore/fixcore/model/model.py

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ def __init__(self, path: Sequence[Optional[str]], str_rep: Optional[str] = None)
181181
def root(self) -> bool:
182182
return not bool(self.path)
183183

184+
@property
185+
def single(self) -> bool:
186+
return len(self.path) == 1
187+
184188
def child(self, part: Optional[str]) -> PropertyPath:
185189
update = list(self.path)
186190
update.append(part)
@@ -189,6 +193,10 @@ def child(self, part: Optional[str]) -> PropertyPath:
189193
def unescaped_parts(self) -> List[str]:
190194
return [p.rstrip("[]").strip("`") for p in self.path if p is not None]
191195

196+
@property
197+
def first_part(self) -> Optional[str]:
198+
return self.path[0] if self.path else None
199+
192200
@property
193201
def last_part(self) -> Optional[str]:
194202
return self.path[-1] if self.path else None
@@ -926,9 +934,8 @@ def __init__(
926934
self.__resolved_props: Dict[str, Tuple[Property, Kind]] = {}
927935
self.__resolved_direct_props: Dict[str, Tuple[Property, Kind]] = {}
928936
self.__resolved_bases: Dict[str, ComplexKind] = {}
929-
self.__owner_lookup: Dict[PropertyPath, ComplexKind] = {}
930937
self.__all_props: List[Property] = list(self.properties)
931-
self.__resolved_hierarchy: Set[str] = {fqn}
938+
self.__resolved_hierarchy: Dict[str, ComplexKind] = {fqn: self}
932939
self.__property_by_path: List[ResolvedPropertyPath] = []
933940
self.__synthetic_props: List[ResolvedPropertyPath] = []
934941

@@ -999,7 +1006,7 @@ def resolve(self, model: Dict[str, Kind]) -> None:
9991006
self.__resolved_hierarchy.update(base.__resolved_hierarchy)
10001007

10011008
# property path -> kind
1002-
self.__property_by_path, self.__owner_lookup = ComplexKind.resolve_properties(self, model)
1009+
self.__property_by_path = ComplexKind.resolve_properties(self, model)
10031010
self.__synthetic_props = [p for p in self.__property_by_path if p.prop.synthetic]
10041011

10051012
# resolve predecessor kinds
@@ -1054,6 +1061,9 @@ def is_root(self) -> bool:
10541061
return not self.bases or (len(self.bases) == 1 and self.bases[0] == self.fqn)
10551062

10561063
def kind_hierarchy(self) -> Set[str]:
1064+
return set(self.__resolved_hierarchy.keys())
1065+
1066+
def resolved_hierarchy(self) -> Dict[str, ComplexKind]:
10571067
return self.__resolved_hierarchy
10581068

10591069
def is_a(self, kind: str) -> bool:
@@ -1062,9 +1072,6 @@ def is_a(self, kind: str) -> bool:
10621072
def resolved_property_paths(self) -> List[ResolvedPropertyPath]:
10631073
return self.__property_by_path
10641074

1065-
def owned_paths(self) -> Dict[PropertyPath, ComplexKind]:
1066-
return self.__owner_lookup
1067-
10681075
def resolved_bases(self) -> Dict[str, ComplexKind]:
10691076
return self.__resolved_bases
10701077

@@ -1257,14 +1264,10 @@ def walk_element(
12571264
return walk_element(elem, self, initial_level, overrides=overrides)
12581265

12591266
@staticmethod
1260-
def resolve_properties(
1261-
complex_kind: ComplexKind, model: Dict[str, Kind]
1262-
) -> Tuple[List[ResolvedPropertyPath], Dict[PropertyPath, ComplexKind]]:
1267+
def resolve_properties(complex_kind: ComplexKind, model: Dict[str, Kind]) -> List[ResolvedPropertyPath]:
12631268
result: List[ResolvedPropertyPath] = []
1264-
owner_lookup: Dict[PropertyPath, ComplexKind] = {}
12651269

12661270
def path_for(
1267-
owner: ComplexKind,
12681271
prop: Property,
12691272
kind: Kind,
12701273
path: PropertyPath,
@@ -1284,30 +1287,24 @@ def path_for(
12841287
kind.resolve(model)
12851288
if isinstance(kind, SimpleKind):
12861289
result.append(ResolvedPropertyPath(relative, prop, kind))
1287-
owner_lookup[relative] = owner
12881290
elif isinstance(kind, ArrayKind):
12891291
if name := relative.last_part:
12901292
result.append(ResolvedPropertyPath(relative, Property(name, kind.fqn), kind))
1291-
owner_lookup[relative] = owner
1292-
path_for(owner, prop, kind.inner, path, visited_kinds, True)
1293+
path_for(prop, kind.inner, path, visited_kinds, True)
12931294
elif isinstance(kind, DictionaryKind):
12941295
child = relative.child(None)
12951296
if name := relative.last_part:
12961297
result.append(ResolvedPropertyPath(relative, Property(name, kind.fqn), kind))
1297-
owner_lookup[relative] = owner
12981298
# Any child path accessing this dictionary will get a property of value kind.
12991299
value = kind.value_kind
13001300
result.append(ResolvedPropertyPath(child, Property("any", value.fqn), value))
1301-
owner_lookup[child] = owner
1302-
path_for(owner, prop, kind.value_kind, child, visited_kinds, add_prop_to_path=False)
1301+
path_for(prop, kind.value_kind, child, visited_kinds, add_prop_to_path=False)
13031302
elif isinstance(kind, ComplexKind):
13041303
if name := relative.last_part:
13051304
result.append(ResolvedPropertyPath(relative, Property(name, kind.fqn), kind))
1306-
owner_lookup[relative] = owner
1307-
for_complex_kind(owner, kind, relative, visited_kinds)
1305+
for_complex_kind(kind, relative, visited_kinds)
13081306

13091307
def for_complex_kind(
1310-
owner: ComplexKind,
13111308
current: ComplexKind,
13121309
relative: PropertyPath,
13131310
visited_kinds: Optional[Dict[str, Set[str]]] = None,
@@ -1318,12 +1315,10 @@ def for_complex_kind(
13181315
if isinstance(cpx := model.get(cpx_fqn), ComplexKind):
13191316
cpx.resolve(model)
13201317
for prop in cpx.properties:
1321-
path_for(
1322-
owner, prop, cpx.__resolved_props[prop.name][1], relative, visited_kinds or defaultdict(set)
1323-
)
1318+
path_for(prop, cpx.__resolved_props[prop.name][1], relative, visited_kinds or defaultdict(set))
13241319

1325-
for_complex_kind(complex_kind, complex_kind, PropertyPath([], ""))
1326-
return result, owner_lookup
1320+
for_complex_kind(complex_kind, PropertyPath([], ""))
1321+
return result
13271322

13281323

13291324
string_kind = StringKind("string")
@@ -1411,26 +1406,30 @@ def from_kinds(kinds: List[Kind]) -> Model:
14111406
prop_kinds_by_path = {}
14121407
# lookup map to get the aggregate root that defined a specific property path
14131408
# Example: instance_cores
1414-
complex_by_path_distinct: Dict[PropertyPath, Dict[str, ComplexKind]] = defaultdict(dict)
1409+
complex_by_prop_distinct: Dict[str, Dict[str, ComplexKind]] = defaultdict(dict)
14151410
for c in all_kinds:
14161411
if isinstance(c, ComplexKind) and c.aggregate_root:
14171412
for r in c.resolved_property_paths():
14181413
prop_kinds_by_path[r.path] = r
1419-
for path, cpl in c.owned_paths().items():
1420-
complex_by_path_distinct[path][cpl.fqn] = cpl
1414+
for cpl in c.resolved_hierarchy().values():
1415+
for prop in cpl.properties:
1416+
complex_by_prop_distinct[prop.name][cpl.fqn] = cpl
14211417

1422-
complex_by_path = {k: list(v.values()) for k, v in complex_by_path_distinct.items()}
1423-
return Model(kind_dict, list(prop_kinds_by_path.values()), complex_by_path)
1418+
complex_by_prop = {k: list(v.values()) for k, v in complex_by_prop_distinct.items()}
1419+
return Model(kind_dict, list(prop_kinds_by_path.values()), complex_by_prop)
14241420

14251421
def __init__(
14261422
self,
14271423
kinds: Dict[str, Kind],
14281424
property_kind_by_path: List[ResolvedPropertyPath],
1429-
complex_kinds_by_path: Dict[PropertyPath, List[ComplexKind]],
1425+
complex_kinds_by_root_prop: Dict[str, List[ComplexKind]],
14301426
):
1427+
# all kinds of this model
14311428
self.kinds = kinds
1429+
# all possible paths in the model to efficiently look up the kind of property
14321430
self.__property_kind_by_path: List[ResolvedPropertyPath] = property_kind_by_path
1433-
self.__complex_kinds_by_path: Dict[PropertyPath, List[ComplexKind]] = complex_kinds_by_path
1431+
# only the root paths point to the owners: foo.bla[*].bar -> lookup foo
1432+
self.__complex_kinds_by_root_prop: Dict[str, List[ComplexKind]] = complex_kinds_by_root_prop
14341433

14351434
def __contains__(self, name_or_object: Union[str, Json]) -> bool:
14361435
if isinstance(name_or_object, str):
@@ -1481,8 +1480,8 @@ def kind_by_path(self, path: Union[str, List[str]]) -> Kind:
14811480
return self.property_by_path(path).kind
14821481

14831482
def owners_by_path(self, path_: Union[str, List[str]]) -> List[ComplexKind]:
1484-
path = PropertyPath.from_string(path_) if isinstance(path_, str) else PropertyPath.from_list(path_)
1485-
return self.__complex_kinds_by_path.get(path, [])
1483+
prop_path = PropertyPath.from_string(path_) if isinstance(path_, str) else PropertyPath.from_list(path_)
1484+
return self.__complex_kinds_by_root_prop.get(p, []) if (p := prop_path.first_part) else []
14861485

14871486
def coerce(self, js: Json) -> Json:
14881487
try:
@@ -1669,7 +1668,7 @@ def all_predecessor_kinds(kind: ComplexKind) -> Dict[EdgeType, List[str]]:
16691668
)
16701669
else:
16711670
result[kind.fqn] = kind
1672-
return Model(result, self.__property_kind_by_path, self.__complex_kinds_by_path)
1671+
return Model(result, self.__property_kind_by_path, self.__complex_kinds_by_root_prop)
16731672

16741673
def filter_complex(
16751674
self, filter_fn: Callable[[ComplexKind], bool], with_bases: bool = True, with_prop_types: bool = True
@@ -1699,7 +1698,7 @@ def add_kind(cpl: ComplexKind) -> None:
16991698
if isinstance(kind, ComplexKind) and filter_fn(kind):
17001699
add_kind(kind)
17011700

1702-
return Model(kinds, self.__property_kind_by_path, self.__complex_kinds_by_path)
1701+
return Model(kinds, self.__property_kind_by_path, self.__complex_kinds_by_root_prop)
17031702

17041703
def complete_path(
17051704
self,

fixcore/tests/fixcore/db/model_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ def test_graph_update() -> None:
1616
gu2 = GraphUpdate(6, 5, 4, 3, 2, 1)
1717
assert gu1.all_changes() == gu2.all_changes() == 21
1818
assert gu1 + gu2 == GraphUpdate(7, 7, 7, 7, 7, 7)
19+
20+
21+
def test_owner(person_model: Model) -> None:
22+
model = QueryModel(Query.by("test"), person_model)
23+
assert {a.fqn for a in model.owners("mtime")} == {"Base"}
24+
assert {a.fqn for a in model.owners("tags")} == {"Base", "graph_root"}
25+
assert {a.fqn for a in model.owners("other_addresses.test.city")} == {"Person"}

fixcore/tests/fixcore/model/model_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,15 @@ def test_property_path_on_model(person_model: Model) -> None:
337337
assert person_model.kind_by_path("tags.owner") == person_model["string"]
338338

339339

340+
def test_owned_path(person_model: Model) -> None:
341+
assert {a.fqn for a in person_model.owners_by_path("name")} == {"Person", "graph_root"}
342+
assert {a.fqn for a in person_model.owners_by_path("city")} == {"Address"}
343+
assert {a.fqn for a in person_model.owners_by_path("list")} == {"Base"}
344+
assert {a.fqn for a in person_model.owners_by_path("mtime")} == {"Base"}
345+
assert {a.fqn for a in person_model.owners_by_path("test")} == {"any_foo"}
346+
assert len(person_model.owners_by_path("does_not_exist")) == 0
347+
348+
340349
def test_metadata_on_update(person_model: Model) -> None:
341350
person = cast(ComplexKind, person_model["Person"])
342351
person_23 = deepcopy(person)

plugins/azure/fix_plugin_azure/resource/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,12 @@ class AzureUsageName:
354354
class AzureBaseUsage:
355355
kind: ClassVar[str] = "azure_usage"
356356
mapping: ClassVar[Dict[str, Bender]] = {
357-
"name": S("name", "value"),
357+
"name": S("name", "value"), # inherited by BaseResource - name already defined there
358358
"usage_name": S("name") >> Bend(AzureUsageName.mapping),
359359
"current_value": S("currentValue"),
360360
"limit": S("limit"),
361361
"unit": S("unit"),
362362
}
363-
name: Optional[str] = field(default=None, metadata={"description": "The name of the resource"})
364363
usage_name: Optional[AzureUsageName] = field(default=None, metadata={"description": "The name of the type of usage."}) # fmt: skip
365364
current_value: Optional[int] = field(default=None, metadata={"description": "The current value of the usage."})
366365
limit: Optional[int] = field(default=None, metadata={"description": "The limit of usage."})

0 commit comments

Comments
 (0)