Skip to content

Commit 58d6da4

Browse files
adamnschFlorentinD
andcommitted
Address some review comments
Co-Authored-By: Florentin Dörre <florentin.dorre@neotechnology.com>
1 parent 81b2788 commit 58d6da4

File tree

1 file changed

+86
-80
lines changed

1 file changed

+86
-80
lines changed

python-wrapper/src/neo4j_viz/gql_create.py

Lines changed: 86 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def _parse_value(value_str: str) -> Any:
1010
if not value_str:
1111
return None
1212

13-
# Parse object
13+
# Parse map
1414
if value_str.startswith("{") and value_str.endswith("}"):
1515
inner = value_str[1:-1].strip()
1616
result = {}
@@ -43,6 +43,7 @@ def _parse_value(value_str: str) -> Any:
4343
k, v = segment.split(":", 1)
4444
k = k.strip().strip("'\"")
4545
result[k] = _parse_value(v)
46+
4647
return result
4748

4849
# Parse list
@@ -68,6 +69,7 @@ def _parse_value(value_str: str) -> Any:
6869
in_string = None
6970
if inner[start_idx:]:
7071
items.append(_parse_value(inner[start_idx:]))
72+
7173
return items
7274

7375
# Parse boolean, float, int, or string
@@ -81,12 +83,91 @@ def _parse_value(value_str: str) -> Any:
8183
return False
8284
if value_str.lower() == "null":
8385
return None
86+
8487
return value_str.strip("'\"")
8588

8689

90+
def _parse_prop_str(
91+
query: str, prop_str: str, prop_start: int, top_level_keys: set[str]
92+
) -> tuple[dict[str, Any], dict[str, Any]]:
93+
top_level: dict[str, Any] = {}
94+
props: dict[str, Any] = {}
95+
depth = 0
96+
in_string = None
97+
start_idx = 0
98+
for i, ch in enumerate(prop_str):
99+
if in_string is None:
100+
if ch in ["'", '"']:
101+
in_string = ch
102+
elif ch in ["{", "["]:
103+
depth += 1
104+
elif ch in ["}", "]"]:
105+
depth -= 1
106+
elif ch == "," and depth == 0:
107+
pair = prop_str[start_idx:i].strip()
108+
if ":" not in pair:
109+
snippet = _get_snippet(query, prop_start + start_idx)
110+
raise ValueError(f"Property syntax error near: `{snippet}`.")
111+
k, v = pair.split(":", 1)
112+
k = k.strip().strip("'\"")
113+
114+
if k in top_level_keys:
115+
top_level[k] = _parse_value(v)
116+
else:
117+
props[k] = _parse_value(v)
118+
119+
start_idx = i + 1
120+
else:
121+
if ch == in_string:
122+
in_string = None
123+
124+
if prop_str[start_idx:]:
125+
pair = prop_str[start_idx:].strip()
126+
if ":" not in pair:
127+
snippet = _get_snippet(query, prop_start + start_idx)
128+
raise ValueError(f"Property syntax error near: `{snippet}`.")
129+
k, v = pair.split(":", 1)
130+
k = k.strip().strip("'\"")
131+
132+
if k in top_level_keys:
133+
top_level[k] = _parse_value(v)
134+
else:
135+
props[k] = _parse_value(v)
136+
137+
return top_level, props
138+
139+
140+
def _parse_labels_and_props(
141+
query: str, s: str, top_level_keys: set[str]
142+
) -> tuple[Optional[str], dict[str, Any], dict[str, Any]]:
143+
prop_match = re.search(r"\{(.*)\}", s)
144+
prop_str = ""
145+
if prop_match:
146+
prop_str = prop_match.group(1)
147+
prop_start = query.index(prop_str, query.index(s))
148+
s = s[: prop_match.start()].strip()
149+
alias_labels = re.split(r"[:&]", s)
150+
raw_alias = alias_labels[0].strip()
151+
final_alias = raw_alias if raw_alias else None
152+
153+
if prop_str:
154+
top_level, props = _parse_prop_str(query, prop_str, prop_start, top_level_keys)
155+
else:
156+
top_level = {}
157+
props = {}
158+
159+
label_list = [lbl.strip() for lbl in alias_labels[1:]]
160+
if "labels" in props:
161+
props["__labels"] = props["labels"]
162+
props["labels"] = sorted(label_list)
163+
164+
return final_alias, top_level, props
165+
166+
87167
def _get_snippet(q: str, idx: int, context: int = 15) -> str:
88168
start = max(0, idx - context)
89169
end = min(len(q), idx + context)
170+
90171
return q[start:end].replace("\n", " ")
91172

92173

@@ -129,81 +210,6 @@ def from_gql_create(
129210
if not re.match(r"(?i)^create\b", query):
130211
raise ValueError("Query must begin with 'CREATE' (case insensitive).")
131212

132-
def parse_prop_str(
133-
prop_str: str, prop_start: int, top_level_keys: set[str]
134-
) -> tuple[dict[str, Any], dict[str, Any]]:
135-
top_level: dict[str, Any] = {}
136-
props: dict[str, Any] = {}
137-
depth = 0
138-
in_string = None
139-
start_idx = 0
140-
for i, ch in enumerate(prop_str):
141-
if in_string is None:
142-
if ch in ["'", '"']:
143-
in_string = ch
144-
elif ch in ["{", "["]:
145-
depth += 1
146-
elif ch in ["}", "]"]:
147-
depth -= 1
148-
elif ch == "," and depth == 0:
149-
pair = prop_str[start_idx:i].strip()
150-
if ":" not in pair:
151-
snippet = _get_snippet(query, prop_start + start_idx)
152-
raise ValueError(f"Property syntax error near: `{snippet}`.")
153-
k, v = pair.split(":", 1)
154-
k = k.strip().strip("'\"")
155-
156-
if k in top_level_keys:
157-
top_level[k] = _parse_value(v)
158-
else:
159-
props[k] = _parse_value(v)
160-
161-
start_idx = i + 1
162-
else:
163-
if ch == in_string:
164-
in_string = None
165-
166-
if prop_str[start_idx:]:
167-
pair = prop_str[start_idx:].strip()
168-
if ":" not in pair:
169-
snippet = _get_snippet(query, prop_start + start_idx)
170-
raise ValueError(f"Property syntax error near: `{snippet}`.")
171-
k, v = pair.split(":", 1)
172-
k = k.strip().strip("'\"")
173-
174-
if k in top_level_keys:
175-
top_level[k] = _parse_value(v)
176-
else:
177-
props[k] = _parse_value(v)
178-
179-
return top_level, props
180-
181-
def parse_labels_and_props(
182-
s: str, top_level_keys: set[str]
183-
) -> tuple[Optional[str], dict[str, Any], dict[str, Any]]:
184-
prop_match = re.search(r"\{(.*)\}", s)
185-
prop_str = ""
186-
if prop_match:
187-
prop_str = prop_match.group(1)
188-
prop_start = query.index(prop_str, query.index(s))
189-
s = s[: prop_match.start()].strip()
190-
alias_labels = re.split(r"[:&]", s)
191-
raw_alias = alias_labels[0].strip()
192-
final_alias = raw_alias if raw_alias else None
193-
194-
if prop_str:
195-
top_level, props = parse_prop_str(prop_str, prop_start, top_level_keys)
196-
else:
197-
top_level = {}
198-
props = {}
199-
200-
label_list = [lbl.strip() for lbl in alias_labels[1:]]
201-
if "labels" in props:
202-
props["__labels"] = props["labels"]
203-
props["labels"] = sorted(label_list)
204-
205-
return final_alias, top_level, props
206-
207213
nodes = []
208214
relationships = []
209215
alias_to_id = {}
@@ -259,7 +265,7 @@ def parse_labels_and_props(
259265
node_m = node_pattern.match(part)
260266
if node_m:
261267
alias_labels_props = node_m.group(1).strip()
262-
alias, top_level, props = parse_labels_and_props(alias_labels_props, node_top_level_keys)
268+
alias, top_level, props = _parse_labels_and_props(query, alias_labels_props, node_top_level_keys)
263269
if not alias:
264270
alias = f"_anon_{anonymous_count}"
265271
anonymous_count += 1
@@ -273,12 +279,12 @@ def parse_labels_and_props(
273279
rel_type = rel_m.group(2).replace(":", "").strip()
274280
right_node = rel_m.group(4).strip()
275281

276-
left_alias, _, _ = parse_labels_and_props(left_node, empty_set)
282+
left_alias, _, _ = _parse_labels_and_props(query, left_node, empty_set)
277283
if not left_alias or left_alias not in alias_to_id:
278284
snippet = _get_snippet(query, query.index(left_node))
279285
raise ValueError(f"Relationship references unknown node alias: '{left_alias}' near: `{snippet}`.")
280286

281-
right_alias, _, _ = parse_labels_and_props(right_node, empty_set)
287+
right_alias, _, _ = _parse_labels_and_props(query, right_node, empty_set)
282288
if not right_alias or right_alias not in alias_to_id:
283289
snippet = _get_snippet(query, query.index(right_node))
284290
raise ValueError(f"Relationship references unknown node alias: '{right_alias}' near: `{snippet}`.")
@@ -288,7 +294,7 @@ def parse_labels_and_props(
288294
if rel_props_str:
289295
inner_str = rel_props_str.strip("{}").strip()
290296
prop_start = query.index(inner_str, query.index(inner_str))
291-
top_level, props = parse_prop_str(inner_str, prop_start, rel_top_level_keys)
297+
top_level, props = _parse_prop_str(query, inner_str, prop_start, rel_top_level_keys)
292298
else:
293299
top_level = {}
294300
props = {}

0 commit comments

Comments
 (0)