Skip to content

Commit 898df81

Browse files
committed
fix: enforce Phase 2 request contracts
1 parent 15367da commit 898df81

9 files changed

Lines changed: 10560 additions & 1500 deletions

File tree

platform-api/src/openmetadata_demo_api/catalog.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@
8282
"profile",
8383
"status",
8484
)
85+
DEPENDENT_RECORD_TERMS = (
86+
"extension",
87+
"follower",
88+
"lineage",
89+
"profile",
90+
"relationship",
91+
"sample",
92+
"status",
93+
"tag",
94+
"testresult",
95+
"usage",
96+
"vote",
97+
)
8598
BULK_ASSET_PREREQUISITES = CORE_SERVICE_PREREQUISITES | {
8699
"createAPICollection",
87100
"createAPIEndpoint",
@@ -172,6 +185,7 @@ def _is_bulk_drive_operation(operation: Mapping[str, Any]) -> bool:
172185
return (
173186
operation_id in BULK_ASSET_PREREQUISITES
174187
or source_file.startswith(("drive/", "drives/", "csv/"))
188+
or "bulk" in haystack
175189
or (
176190
source_file.startswith(CORE_PREFIXES)
177191
and (
@@ -190,6 +204,17 @@ def _phase(operation: Mapping[str, Any]) -> str:
190204
source_file = operation["source"]["file"]
191205
if source_file.startswith("services/"):
192206
return "service-management"
207+
operation_text = " ".join(
208+
(
209+
str(operation["operation_id"]),
210+
str(operation["java_method"]),
211+
str(operation["path"]),
212+
)
213+
).lower()
214+
if source_file.startswith(CORE_PREFIXES) and any(
215+
term in operation_text for term in DEPENDENT_RECORD_TERMS
216+
):
217+
return "enrichment"
193218
if source_file in CORE_PARENT_FILES:
194219
return "parent-assets"
195220
if source_file in CORE_CHILD_FILES:

platform-api/src/openmetadata_demo_api/inventory.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
CAPITALIZED_TYPE = re.compile(r"\b(?:Create|Add|Update|Restore|Bulk|Search|Move|Set|Vote)\w+\b")
4040
IMPORT = re.compile(r"(?m)^import\s+(?!static\s)([\w.]+);")
4141
TYPE_TOKEN = re.compile(r"\b[A-Z][A-Za-z0-9_]+\b")
42+
JAVA_IDENTIFIER = re.compile(r"\b[A-Za-z_$][A-Za-z0-9_$]*\b")
4243
PARAMETER_LOCATION = re.compile(
4344
r"@(PathParam|QueryParam|HeaderParam|CookieParam|FormParam|FormDataParam)"
4445
r"\(\s*\"([^\"]+)\"\s*\)"
@@ -91,6 +92,7 @@ class ParameterContract:
9192
name: str
9293
location: str
9394
java_type: str
95+
cardinality: str
9496
required: bool
9597
default: str | None
9698
model_hints: tuple[str, ...]
@@ -179,6 +181,46 @@ def _split_java_parameters(signature: str) -> list[str]:
179181
return chunks
180182

181183

184+
def _without_java_annotations(text: str) -> str:
185+
"""Remove annotations, including nested arguments, from a parameter declaration."""
186+
187+
output: list[str] = []
188+
offset = 0
189+
while offset < len(text):
190+
if text[offset] != "@":
191+
output.append(text[offset])
192+
offset += 1
193+
continue
194+
offset += 1
195+
while offset < len(text) and (text[offset].isalnum() or text[offset] in "_.$"):
196+
offset += 1
197+
while offset < len(text) and text[offset].isspace():
198+
offset += 1
199+
if offset < len(text) and text[offset] == "(":
200+
depth = 1
201+
offset += 1
202+
in_string = False
203+
escaped = False
204+
while offset < len(text) and depth:
205+
character = text[offset]
206+
if in_string:
207+
if escaped:
208+
escaped = False
209+
elif character == "\\":
210+
escaped = True
211+
elif character == '"':
212+
in_string = False
213+
elif character == '"':
214+
in_string = True
215+
elif character == "(":
216+
depth += 1
217+
elif character == ")":
218+
depth -= 1
219+
offset += 1
220+
output.append(" ")
221+
return "".join(output)
222+
223+
182224
def _parameter_contracts(signature: str, imports: dict[str, str]) -> tuple[ParameterContract, ...]:
183225
contracts: list[ParameterContract] = []
184226
locations = {
@@ -200,14 +242,23 @@ def _parameter_contracts(signature: str, imports: dict[str, str]) -> tuple[Param
200242
name = annotation.group(2) if annotation else variable.group(1)
201243
default_match = DEFAULT_VALUE.search(parameter)
202244
default = default_match.group(1) if default_match else None
203-
type_tokens = TYPE_TOKEN.findall(parameter[: variable.start()])
245+
declaration = _without_java_annotations(parameter[: variable.start()])
246+
type_tokens = [
247+
token for token in JAVA_IDENTIFIER.findall(declaration) if token not in {"final"}
248+
]
204249
java_type = type_tokens[-1] if type_tokens else "unknown"
250+
cardinality = (
251+
"array"
252+
if re.search(r"\b(?:Collection|List|Set)\s*<|\[\]", parameter[: variable.start()])
253+
else "single"
254+
)
205255
model_hints = tuple(sorted(_schema_type_hints(parameter, imports)))
206256
contracts.append(
207257
ParameterContract(
208258
name=name,
209259
location=location,
210260
java_type=java_type,
261+
cardinality=cardinality,
211262
required=location in {"path", "body"} or "@NotNull" in parameter,
212263
default=default,
213264
model_hints=model_hints,
@@ -381,6 +432,7 @@ def parse_resource_source(source_file: Path, resource_root: Path) -> list[Source
381432
name="inherited_collection",
382433
location="path",
383434
java_type="String",
435+
cardinality="single",
384436
required=True,
385437
default="entities",
386438
model_hints=(),

0 commit comments

Comments
 (0)