flybase-cli 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flybase_cli/__init__.py +4 -0
- flybase_cli/__main__.py +5 -0
- flybase_cli/cli.py +667 -0
- flybase_cli/config.py +266 -0
- flybase_cli/core.py +700 -0
- flybase_cli/loaders.py +539 -0
- flybase_cli/postgres.py +106 -0
- flybase_cli/querying.py +162 -0
- flybase_cli/schema.py +671 -0
- flybase_cli/semantics.py +114 -0
- flybase_cli/syncing.py +254 -0
- flybase_cli/version.py +1 -0
- flybase_cli-0.1.2.dist-info/METADATA +244 -0
- flybase_cli-0.1.2.dist-info/RECORD +18 -0
- flybase_cli-0.1.2.dist-info/WHEEL +5 -0
- flybase_cli-0.1.2.dist-info/entry_points.txt +2 -0
- flybase_cli-0.1.2.dist-info/licenses/LICENSE +21 -0
- flybase_cli-0.1.2.dist-info/top_level.txt +1 -0
flybase_cli/querying.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .config import SEARCH_ID_CANDIDATES
|
|
7
|
+
from .core import open_db
|
|
8
|
+
from .schema import build_query_plan
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
PARAM_PATTERN = re.compile(r":([A-Za-z_][A-Za-z0-9_]*)")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_cli_params(items: list[str] | None) -> dict[str, str]:
|
|
15
|
+
params: dict[str, str] = {}
|
|
16
|
+
for item in items or []:
|
|
17
|
+
if "=" not in item:
|
|
18
|
+
raise ValueError(f"invalid parameter assignment: {item}")
|
|
19
|
+
key, value = item.split("=", 1)
|
|
20
|
+
clean_key = key.strip()
|
|
21
|
+
if not clean_key:
|
|
22
|
+
raise ValueError(f"invalid parameter assignment: {item}")
|
|
23
|
+
params[clean_key] = value
|
|
24
|
+
return params
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def required_parameters(sql: str) -> list[str]:
|
|
28
|
+
seen: list[str] = []
|
|
29
|
+
for match in PARAM_PATTERN.findall(sql):
|
|
30
|
+
if match not in seen:
|
|
31
|
+
seen.append(match)
|
|
32
|
+
return seen
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def row_to_record(columns: list[str], row: tuple[object, ...]) -> dict[str, object]:
|
|
36
|
+
return dict(zip(columns, row, strict=True))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def summarize_records(columns: list[str], records: list[dict[str, object]], truncated: bool) -> dict[str, object]:
|
|
40
|
+
populated_columns = [
|
|
41
|
+
column
|
|
42
|
+
for column in columns
|
|
43
|
+
if any(record.get(column) not in {None, ""} for record in records)
|
|
44
|
+
]
|
|
45
|
+
return {
|
|
46
|
+
"column_count": len(columns),
|
|
47
|
+
"row_count_shown": len(records),
|
|
48
|
+
"truncated": truncated,
|
|
49
|
+
"identifier_columns": [
|
|
50
|
+
column
|
|
51
|
+
for column in columns
|
|
52
|
+
if column in SEARCH_ID_CANDIDATES or column.endswith("_id") or column.endswith("Id")
|
|
53
|
+
],
|
|
54
|
+
"populated_columns": populated_columns,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def execute_sql(
|
|
59
|
+
db_path: Path,
|
|
60
|
+
query: str,
|
|
61
|
+
*,
|
|
62
|
+
params: dict[str, object] | None = None,
|
|
63
|
+
limit: int = 20,
|
|
64
|
+
output_format: str = "records",
|
|
65
|
+
) -> dict[str, object]:
|
|
66
|
+
conn = open_db(db_path)
|
|
67
|
+
try:
|
|
68
|
+
cursor = conn.execute(query, params or {})
|
|
69
|
+
if cursor.description is None:
|
|
70
|
+
conn.commit()
|
|
71
|
+
return {
|
|
72
|
+
"query": query,
|
|
73
|
+
"parameters": params or {},
|
|
74
|
+
"status": "ok",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
columns = [description[0] for description in cursor.description]
|
|
78
|
+
fetched = cursor.fetchmany(limit + 1)
|
|
79
|
+
truncated = len(fetched) > limit
|
|
80
|
+
rows = fetched[:limit]
|
|
81
|
+
records = [row_to_record(columns, row) for row in rows]
|
|
82
|
+
payload: dict[str, object] = {
|
|
83
|
+
"query": query,
|
|
84
|
+
"parameters": params or {},
|
|
85
|
+
"columns": columns,
|
|
86
|
+
"summary": summarize_records(columns, records, truncated),
|
|
87
|
+
}
|
|
88
|
+
if output_format == "rows":
|
|
89
|
+
payload["rows"] = rows
|
|
90
|
+
else:
|
|
91
|
+
payload["records"] = records
|
|
92
|
+
return payload
|
|
93
|
+
finally:
|
|
94
|
+
conn.close()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def select_query_template(
|
|
98
|
+
queries: list[dict[str, object]],
|
|
99
|
+
*,
|
|
100
|
+
template_id: str | None = None,
|
|
101
|
+
template_name: str | None = None,
|
|
102
|
+
kind: str | None = None,
|
|
103
|
+
table_name: str | None = None,
|
|
104
|
+
) -> dict[str, object]:
|
|
105
|
+
matches = queries
|
|
106
|
+
if template_id:
|
|
107
|
+
matches = [query for query in matches if query.get("id") == template_id]
|
|
108
|
+
if template_name:
|
|
109
|
+
matches = [query for query in matches if query.get("name") == template_name]
|
|
110
|
+
if kind:
|
|
111
|
+
matches = [query for query in matches if query.get("kind") == kind]
|
|
112
|
+
if table_name:
|
|
113
|
+
matches = [
|
|
114
|
+
query
|
|
115
|
+
for query in matches
|
|
116
|
+
if table_name in [str(name) for name in query.get("tables", [])]
|
|
117
|
+
]
|
|
118
|
+
if not matches:
|
|
119
|
+
raise ValueError("no query template matched the requested selector")
|
|
120
|
+
return matches[0]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def run_query_template(
|
|
124
|
+
db_path: Path,
|
|
125
|
+
*,
|
|
126
|
+
template_id: str | None = None,
|
|
127
|
+
template_name: str | None = None,
|
|
128
|
+
kind: str | None = None,
|
|
129
|
+
table_name: str | None = None,
|
|
130
|
+
params: dict[str, str] | None = None,
|
|
131
|
+
sample_values: int = 1,
|
|
132
|
+
plan_limit: int = 5,
|
|
133
|
+
result_limit: int = 20,
|
|
134
|
+
output_format: str = "records",
|
|
135
|
+
) -> dict[str, object]:
|
|
136
|
+
plan = build_query_plan(
|
|
137
|
+
db_path,
|
|
138
|
+
sample_values=sample_values,
|
|
139
|
+
limit=plan_limit,
|
|
140
|
+
)
|
|
141
|
+
selected = select_query_template(
|
|
142
|
+
plan["queries"],
|
|
143
|
+
template_id=template_id,
|
|
144
|
+
template_name=template_name,
|
|
145
|
+
kind=kind,
|
|
146
|
+
table_name=table_name,
|
|
147
|
+
)
|
|
148
|
+
provided_params = params or {}
|
|
149
|
+
missing = [name for name in required_parameters(str(selected["sql"])) if name not in provided_params]
|
|
150
|
+
if missing:
|
|
151
|
+
raise ValueError(f"missing template parameters: {', '.join(missing)}")
|
|
152
|
+
result = execute_sql(
|
|
153
|
+
db_path,
|
|
154
|
+
str(selected["sql"]),
|
|
155
|
+
params=provided_params,
|
|
156
|
+
limit=result_limit,
|
|
157
|
+
output_format=output_format,
|
|
158
|
+
)
|
|
159
|
+
return {
|
|
160
|
+
"selected_query": selected,
|
|
161
|
+
"result": result,
|
|
162
|
+
}
|