Skip to content

Commit 64e0831

Browse files
committed
Use json5 for api.json and apiignore.json
1 parent 6df4ed5 commit 64e0831

8 files changed

Lines changed: 63 additions & 39 deletions

File tree

.github/workflows/schema-compat.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,15 @@ jobs:
3838
with:
3939
python-version: "3.13"
4040

41+
- name: Set up uv
42+
uses: astral-sh/setup-uv@v7
43+
with:
44+
enable-cache: true
45+
4146
- name: Run Schema Compatibility Check
4247
env:
4348
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44-
run: python scripts/check_schema_compat.py ${{ env.PARADEDB_VERSION }}
49+
run: uv run --no-sync scripts/check_schema_compat.py ${{ env.PARADEDB_VERSION }}
4550

4651
integration-tests:
4752
name: Integration Tests
File renamed without changes.

apiignore.json renamed to apiignore.json5

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
"pdb.alias_out_safe",
2020
"pdb.alias_recv",
2121
"pdb.alias_send",
22+
23+
// These functions are implementation details of tokenizers that the ORM
24+
// doesn't need to interact with
2225
"pdb.chinese_compatible_in",
2326
"pdb.chinese_compatible_out",
2427
"pdb.chinese_compatible_recv",

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ test = [
5252
"pytest-cov>=6.0",
5353
"psycopg[binary]>=3.1.18",
5454
"alembic>=1.13.0",
55+
"json5>=0.9",
5556
]
5657
dev = [
5758
"ruff>=0.15.0",
5859
"mypy>=1.19.0",
5960
"pre-commit>=4.0.0",
61+
"json5>=0.9",
6062
]
6163

6264
[tool.pytest.ini_options]

scripts/check_api_coverage.py

100755100644
Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
#!/usr/bin/env python3
2-
"""Validate that api.json matches the SQLAlchemy wrapper surface."""
1+
"""Validate that api.json5 matches the SQLAlchemy wrapper surface."""
32

43
from __future__ import annotations
54

65
import ast
7-
import json
86
import re
97
import sys
108
from pathlib import Path
119

10+
import json5
11+
1212
ROOT = Path(__file__).resolve().parents[1]
13-
API_JSON = ROOT / "api.json"
14-
APIIGNORE_JSON = ROOT / "apiignore.json"
13+
API_JSON = ROOT / "api.json5"
14+
APIIGNORE_JSON = ROOT / "apiignore.json5"
1515
PDB_SYMBOL_RE = re.compile(r"\bpdb\.[A-Za-z_][A-Za-z0-9_]*\b")
1616

1717

1818
def load_json(path: Path) -> object:
1919
try:
20-
return json.loads(path.read_text(encoding="utf-8"))
20+
return json5.loads(path.read_text(encoding="utf-8"))
2121
except FileNotFoundError as exc:
2222
raise FileNotFoundError(f"{path} not found") from exc
23-
except json.JSONDecodeError as exc:
24-
raise ValueError(f"invalid JSON in {path}: {exc}") from exc
23+
except ValueError as exc:
24+
raise ValueError(f"invalid JSON5 in {path}: {exc}") from exc
2525

2626

2727
def flatten_ignore(section: object, *, kind: str) -> set[str]:
@@ -141,23 +141,23 @@ def main() -> int:
141141
return 1
142142

143143
if not isinstance(api, dict):
144-
print("❌ api.json must contain a JSON object.", file=sys.stderr)
144+
print("❌ api.json5 must contain a JSON object.", file=sys.stderr)
145145
return 1
146146
if not isinstance(apiignore, dict):
147-
print("❌ apiignore.json must contain a JSON object.", file=sys.stderr)
147+
print("❌ apiignore.json5 must contain a JSON object.", file=sys.stderr)
148148
return 1
149149

150150
try:
151151
operators = api["operators"]
152152
functions = api["functions"]
153153
types = api["types"]
154154
except KeyError as exc:
155-
print(f"❌ api.json missing required section: {exc}", file=sys.stderr)
155+
print(f"❌ api.json5 missing required section: {exc}", file=sys.stderr)
156156
return 1
157157

158158
if not all(isinstance(section, dict) for section in (operators, functions, types)):
159159
print(
160-
"❌ api.json sections operators/functions/types must all be objects.",
160+
"❌ api.json5 sections operators/functions/types must all be objects.",
161161
file=sys.stderr,
162162
)
163163
return 1
@@ -201,14 +201,16 @@ def main() -> int:
201201

202202
issues: list[str] = []
203203
if missing_pdb_symbols:
204-
issues.append("api.json pdb.* symbols not referenced by SQLAlchemy wrappers: " + ", ".join(missing_pdb_symbols))
204+
issues.append(
205+
"api.json5 pdb.* symbols not referenced by SQLAlchemy wrappers: " + ", ".join(missing_pdb_symbols)
206+
)
205207
if missing_operator_symbols:
206208
issues.append(
207-
"api.json operators not referenced by SQLAlchemy wrappers: " + ", ".join(missing_operator_symbols)
209+
"api.json5 operators not referenced by SQLAlchemy wrappers: " + ", ".join(missing_operator_symbols)
208210
)
209211
if untracked_symbols:
210212
issues.append(
211-
"pdb.* symbols used in package source but missing from api.json/apiignore.json: "
213+
"pdb.* symbols used in package source but missing from api.json5/apiignore.json5: "
212214
+ ", ".join(untracked_symbols)
213215
)
214216

@@ -217,7 +219,7 @@ def main() -> int:
217219
for issue in issues:
218220
print(f" - {issue}", file=sys.stderr)
219221
print(
220-
"\nUpdate api.json, apiignore.json, or the SQLAlchemy wrappers so they stay in sync.",
222+
"\nUpdate api.json5, apiignore.json5, or the SQLAlchemy wrappers so they stay in sync.",
221223
file=sys.stderr,
222224
)
223225
return 1

scripts/check_schema_compat.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
1-
#!/usr/bin/env python3
21
"""
3-
Check compatibility between sqlalchemy-paradedb's api.json and a released pg_search schema.
2+
Check compatibility between sqlalchemy-paradedb's api.json5 and a released pg_search schema.
43
54
The schema is downloaded from the corresponding ParadeDB GitHub release and
65
checked in both directions:
76
8-
Forward: every symbol in api.json is present in the schema (detects removals/renames).
9-
Reverse: every pdb.* symbol in the schema is either in api.json or in apiignore.json
7+
Forward: every symbol in api.json5 is present in the schema (detects removals/renames).
8+
Reverse: every pdb.* symbol in the schema is either in api.json5 or in apiignore.json5
109
(surfaces new paradedb APIs that haven't been wrapped yet).
1110
1211
Example:
1312
python scripts/check_schema_compat.py 0.22.0
1413
15-
The ignore list is read automatically from apiignore.json (repo root) if it exists.
14+
The ignore list is read automatically from apiignore.json5 (repo root) if it exists.
1615
"""
1716

1817
import argparse
19-
import json
2018
import re
2119
import shutil
2220
import subprocess
2321
import sys
2422
import tempfile
2523
from pathlib import Path
2624

25+
import json5
26+
2727
_ROOT_DIR = Path(__file__).resolve().parent.parent
28-
_API_FILE = _ROOT_DIR / "api.json"
29-
_IGNORE_FILE = _ROOT_DIR / "apiignore.json"
28+
_API_FILE = _ROOT_DIR / "api.json5"
29+
_IGNORE_FILE = _ROOT_DIR / "apiignore.json5"
3030
_SCHEMA_FILE_NAME = "pg_search.schema.sql"
3131

3232

@@ -36,8 +36,8 @@ def normalize(sql: str) -> str:
3636

3737

3838
def extract_from_api(path: Path) -> dict:
39-
"""Read api.json and return {functions: [...], operators: [...], types: [...]}."""
40-
data = json.loads(path.read_text())
39+
"""Read api.json5 and return {functions: [...], operators: [...], types: [...]}."""
40+
data = json5.loads(path.read_text())
4141
return {
4242
"functions": sorted(set(data["functions"].values())),
4343
"operators": sorted(set(data["operators"].values())),
@@ -104,7 +104,7 @@ def normalize_version(version: str) -> str:
104104

105105
def parse_args(argv: list[str]) -> argparse.Namespace:
106106
parser = argparse.ArgumentParser(
107-
description=("Download pg_search.schema.sql for a ParadeDB release and check it against this repo's api.json.")
107+
description=("Download pg_search.schema.sql for a ParadeDB release and check it against this repo's api.json5.")
108108
)
109109
parser.add_argument("version", help="ParadeDB version to check, for example 0.22.0")
110110
return parser.parse_args(argv)
@@ -164,17 +164,17 @@ def run_checks(schema_path: Path, api_path: Path) -> int:
164164
print(f"❌ Schema file not found: {schema_path}", file=sys.stderr)
165165
return 1
166166
if not api_path.exists():
167-
print(f"❌ api.json not found: {api_path}", file=sys.stderr)
167+
print(f"❌ api.json5 not found: {api_path}", file=sys.stderr)
168168
return 1
169169

170170
schema = normalize(schema_path.read_text())
171171
deps = extract_from_api(api_path)
172-
ignored = json.loads(_IGNORE_FILE.read_text()) if _IGNORE_FILE.exists() else {}
172+
ignored = json5.loads(_IGNORE_FILE.read_text()) if _IGNORE_FILE.exists() else {}
173173

174174
rc = 0
175175

176176
# ------------------------------------------------------------------
177-
# Forward check: every symbol in api.json must exist in the schema.
177+
# Forward check: every symbol in api.json5 must exist in the schema.
178178
# ------------------------------------------------------------------
179179
missing: list[tuple[str, str]] = []
180180
for fn in deps.get("functions", []):
@@ -189,20 +189,20 @@ def run_checks(schema_path: Path, api_path: Path) -> int:
189189

190190
total_api = sum(len(v) for v in deps.values() if isinstance(v, list))
191191
if missing:
192-
print(f"❌ Forward check: {len(missing)}/{total_api} api.json symbols missing from schema:")
192+
print(f"❌ Forward check: {len(missing)}/{total_api} api.json5 symbols missing from schema:")
193193
for kind, name in missing:
194194
print(f" {kind}: {name}")
195195
print(
196196
"\nThese symbols were removed or renamed in this version of pg_search.\n"
197-
"Update sqlalchemy-paradedb to handle the API change, then update api.json."
197+
"Update sqlalchemy-paradedb to handle the API change, then update api.json5."
198198
)
199199
rc = 1
200200
else:
201-
print(f"✅ Forward check: all {total_api} api.json symbols present in schema.")
201+
print(f"✅ Forward check: all {total_api} api.json5 symbols present in schema.")
202202

203203
# ------------------------------------------------------------------
204-
# Reverse check: every pdb.* symbol in the schema must be in api.json
205-
# or explicitly ignored in apiignore.json.
204+
# Reverse check: every pdb.* symbol in the schema must be in api.json5
205+
# or explicitly ignored in apiignore.json5.
206206
# ------------------------------------------------------------------
207207
schema_symbols = scan_schema_symbols(schema)
208208
uncovered: list[tuple[str, str]] = []
@@ -215,12 +215,12 @@ def run_checks(schema_path: Path, api_path: Path) -> int:
215215

216216
total_schema = sum(len(v) for v in schema_symbols.values())
217217
if uncovered:
218-
print(f"\n⚠️ Reverse check: {len(uncovered)} schema symbols not covered by api.json:")
218+
print(f"\n⚠️ Reverse check: {len(uncovered)} schema symbols not covered by api.json5:")
219219
for kind, name in uncovered:
220220
print(f" {kind}: {name}")
221221
print(
222222
"\nThese are paradedb APIs not yet wrapped by sqlalchemy-paradedb.\n"
223-
"Either add them to api.json or add them to apiignore.json."
223+
"Either add them to api.json5 or add them to apiignore.json5."
224224
)
225225
rc = 1
226226
else:

tests/integration/test_query_interface_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,5 +185,5 @@ def test_all_tokenizers(session, expected: str, tokenizer: str, tokenizer_params
185185
_sql(stmt)
186186
== f"""SELECT products.id
187187
FROM products
188-
WHERE products.description &&& 'running shoes'::pdb.{expected} ORDER BY products.id"""
188+
WHERE products.description &&& 'running shoes'::{expected} ORDER BY products.id"""
189189
)

uv.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)