lean-explore 0.3.0__py3-none-any.whl → 1.0.0__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.
- lean_explore/__init__.py +14 -1
- lean_explore/api/__init__.py +12 -1
- lean_explore/api/client.py +64 -176
- lean_explore/cli/__init__.py +10 -1
- lean_explore/cli/data_commands.py +157 -479
- lean_explore/cli/display.py +171 -0
- lean_explore/cli/main.py +51 -608
- lean_explore/config.py +244 -0
- lean_explore/extract/__init__.py +5 -0
- lean_explore/extract/__main__.py +368 -0
- lean_explore/extract/doc_gen4.py +200 -0
- lean_explore/extract/doc_parser.py +499 -0
- lean_explore/extract/embeddings.py +371 -0
- lean_explore/extract/github.py +110 -0
- lean_explore/extract/index.py +317 -0
- lean_explore/extract/informalize.py +653 -0
- lean_explore/extract/package_config.py +59 -0
- lean_explore/extract/package_registry.py +45 -0
- lean_explore/extract/package_utils.py +105 -0
- lean_explore/extract/types.py +25 -0
- lean_explore/mcp/__init__.py +11 -1
- lean_explore/mcp/app.py +14 -46
- lean_explore/mcp/server.py +20 -35
- lean_explore/mcp/tools.py +70 -205
- lean_explore/models/__init__.py +9 -0
- lean_explore/models/search_db.py +76 -0
- lean_explore/models/search_types.py +53 -0
- lean_explore/search/__init__.py +32 -0
- lean_explore/search/engine.py +655 -0
- lean_explore/search/scoring.py +156 -0
- lean_explore/search/service.py +68 -0
- lean_explore/search/tokenization.py +71 -0
- lean_explore/util/__init__.py +28 -0
- lean_explore/util/embedding_client.py +92 -0
- lean_explore/util/logging.py +22 -0
- lean_explore/util/openrouter_client.py +63 -0
- lean_explore/util/reranker_client.py +189 -0
- {lean_explore-0.3.0.dist-info → lean_explore-1.0.0.dist-info}/METADATA +32 -9
- lean_explore-1.0.0.dist-info/RECORD +43 -0
- {lean_explore-0.3.0.dist-info → lean_explore-1.0.0.dist-info}/WHEEL +1 -1
- lean_explore-1.0.0.dist-info/entry_points.txt +2 -0
- lean_explore/cli/agent.py +0 -788
- lean_explore/cli/config_utils.py +0 -481
- lean_explore/defaults.py +0 -114
- lean_explore/local/__init__.py +0 -1
- lean_explore/local/search.py +0 -1050
- lean_explore/local/service.py +0 -479
- lean_explore/shared/__init__.py +0 -1
- lean_explore/shared/models/__init__.py +0 -1
- lean_explore/shared/models/api.py +0 -117
- lean_explore/shared/models/db.py +0 -396
- lean_explore-0.3.0.dist-info/RECORD +0 -26
- lean_explore-0.3.0.dist-info/entry_points.txt +0 -2
- {lean_explore-0.3.0.dist-info → lean_explore-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {lean_explore-0.3.0.dist-info → lean_explore-1.0.0.dist-info}/top_level.txt +0 -0
lean_explore/local/service.py
DELETED
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
# src/lean_explore/local/service.py
|
|
2
|
-
|
|
3
|
-
"""Provides a service class for local Lean data exploration.
|
|
4
|
-
|
|
5
|
-
This module defines the Service class, which offers methods to search,
|
|
6
|
-
retrieve by ID, and get dependencies for statement groups using local
|
|
7
|
-
data assets (SQLite database, FAISS index, and embedding models).
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
import time
|
|
12
|
-
from typing import List, Optional, Union, overload
|
|
13
|
-
|
|
14
|
-
import faiss # For type hinting if needed
|
|
15
|
-
from sentence_transformers import SentenceTransformer # For type hinting if needed
|
|
16
|
-
from sqlalchemy import create_engine
|
|
17
|
-
from sqlalchemy.exc import OperationalError, SQLAlchemyError
|
|
18
|
-
from sqlalchemy.orm import Session as SQLAlchemySessionType
|
|
19
|
-
from sqlalchemy.orm import joinedload, sessionmaker
|
|
20
|
-
|
|
21
|
-
from lean_explore import defaults
|
|
22
|
-
from lean_explore.shared.models.api import (
|
|
23
|
-
APICitationsResponse,
|
|
24
|
-
APIPrimaryDeclarationInfo,
|
|
25
|
-
APISearchResponse,
|
|
26
|
-
APISearchResultItem,
|
|
27
|
-
)
|
|
28
|
-
from lean_explore.shared.models.db import (
|
|
29
|
-
StatementGroup,
|
|
30
|
-
StatementGroupDependency,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
from .search import load_embedding_model, load_faiss_assets, perform_search
|
|
34
|
-
|
|
35
|
-
logger = logging.getLogger(__name__)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class Service:
|
|
39
|
-
"""A service for interacting with local Lean explore data.
|
|
40
|
-
|
|
41
|
-
This service loads necessary data assets (embedding model, FAISS index,
|
|
42
|
-
database connection) upon initialization using default paths and parameters
|
|
43
|
-
derived from the active toolchain. It provides methods for searching
|
|
44
|
-
statement groups, retrieving them by ID, and fetching dependencies (citations).
|
|
45
|
-
|
|
46
|
-
Attributes:
|
|
47
|
-
embedding_model: The loaded sentence embedding model.
|
|
48
|
-
faiss_index: The loaded FAISS index.
|
|
49
|
-
text_chunk_id_map: A list mapping FAISS indices to text chunk IDs.
|
|
50
|
-
engine: The SQLAlchemy engine for database connections.
|
|
51
|
-
SessionLocal: The SQLAlchemy sessionmaker for creating sessions.
|
|
52
|
-
default_faiss_k (int): Default number of FAISS neighbors to retrieve.
|
|
53
|
-
default_pagerank_weight (float): Default weight for PageRank.
|
|
54
|
-
default_text_relevance_weight (float): Default weight for text relevance.
|
|
55
|
-
default_name_match_weight (float): Default weight for name matching (BM25).
|
|
56
|
-
default_semantic_similarity_threshold (float): Default similarity threshold.
|
|
57
|
-
default_results_limit (int): Default limit for search results.
|
|
58
|
-
default_faiss_nprobe (int): Default nprobe for FAISS IVF indexes.
|
|
59
|
-
default_faiss_oversampling_factor (int): Default oversampling factor for
|
|
60
|
-
FAISS when package filters are active.
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
def __init__(self):
|
|
64
|
-
"""Initializes the Service by loading data assets and configurations.
|
|
65
|
-
|
|
66
|
-
Checks for essential local data files first, then loads the
|
|
67
|
-
embedding model, FAISS index, and sets up the database engine.
|
|
68
|
-
Paths for data assets are sourced from `lean_explore.defaults`.
|
|
69
|
-
|
|
70
|
-
Raises:
|
|
71
|
-
FileNotFoundError: If essential data files (DB, FAISS index, map)
|
|
72
|
-
are not found at their expected locations.
|
|
73
|
-
RuntimeError: If the embedding model fails to load or if other
|
|
74
|
-
critical initialization steps (like database connection
|
|
75
|
-
after file checks) fail.
|
|
76
|
-
"""
|
|
77
|
-
logger.info("Initializing local Service...")
|
|
78
|
-
try:
|
|
79
|
-
defaults.LEAN_EXPLORE_TOOLCHAINS_BASE_DIR.mkdir(parents=True, exist_ok=True)
|
|
80
|
-
logger.info(
|
|
81
|
-
"User toolchains base directory ensured: "
|
|
82
|
-
f"{defaults.LEAN_EXPLORE_TOOLCHAINS_BASE_DIR}"
|
|
83
|
-
)
|
|
84
|
-
except OSError as e:
|
|
85
|
-
logger.error(
|
|
86
|
-
f"Could not create user toolchains base directory "
|
|
87
|
-
f"{defaults.LEAN_EXPLORE_TOOLCHAINS_BASE_DIR}: {e}"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
db_path = defaults.DEFAULT_DB_PATH
|
|
91
|
-
db_url = defaults.DEFAULT_DB_URL
|
|
92
|
-
is_file_db = db_url.startswith("sqlite:///")
|
|
93
|
-
|
|
94
|
-
if is_file_db and not db_path.exists():
|
|
95
|
-
error_message = (
|
|
96
|
-
f"Database file not found at the expected location: {db_path}\n"
|
|
97
|
-
"Please run 'leanexplore data fetch' to download the data toolchain."
|
|
98
|
-
)
|
|
99
|
-
logger.error(error_message)
|
|
100
|
-
raise FileNotFoundError(error_message)
|
|
101
|
-
|
|
102
|
-
logger.info(f"Loading embedding model: {defaults.DEFAULT_EMBEDDING_MODEL_NAME}")
|
|
103
|
-
self.embedding_model: Optional[SentenceTransformer] = load_embedding_model(
|
|
104
|
-
defaults.DEFAULT_EMBEDDING_MODEL_NAME
|
|
105
|
-
)
|
|
106
|
-
if self.embedding_model is None:
|
|
107
|
-
raise RuntimeError(
|
|
108
|
-
f"Failed to load embedding model: "
|
|
109
|
-
f"{defaults.DEFAULT_EMBEDDING_MODEL_NAME}. "
|
|
110
|
-
"Check model name and network connection if downloaded on the fly."
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
faiss_index_path = defaults.DEFAULT_FAISS_INDEX_PATH
|
|
114
|
-
faiss_map_path = defaults.DEFAULT_FAISS_MAP_PATH
|
|
115
|
-
logger.info(
|
|
116
|
-
f"Attempting to load FAISS assets: Index='{faiss_index_path}', "
|
|
117
|
-
f"Map='{faiss_map_path}'"
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
faiss_assets = load_faiss_assets(str(faiss_index_path), str(faiss_map_path))
|
|
121
|
-
if faiss_assets[0] is None or faiss_assets[1] is None:
|
|
122
|
-
error_message = (
|
|
123
|
-
"Failed to load critical FAISS assets (index or ID map).\n"
|
|
124
|
-
"Expected at:\n"
|
|
125
|
-
f" Index path: {faiss_index_path}\n"
|
|
126
|
-
f" ID map path: {faiss_map_path}\n"
|
|
127
|
-
"Please run 'leanexplore data fetch' to download or update the data "
|
|
128
|
-
"toolchain."
|
|
129
|
-
)
|
|
130
|
-
logger.error(error_message)
|
|
131
|
-
raise FileNotFoundError(error_message)
|
|
132
|
-
self.faiss_index: faiss.Index = faiss_assets[0]
|
|
133
|
-
self.text_chunk_id_map: List[str] = faiss_assets[1]
|
|
134
|
-
logger.info("FAISS assets loaded successfully.")
|
|
135
|
-
|
|
136
|
-
logger.info(f"Initializing database engine. Expected DB path: {db_path}")
|
|
137
|
-
try:
|
|
138
|
-
self.engine = create_engine(db_url)
|
|
139
|
-
# Test connection
|
|
140
|
-
with self.engine.connect(): # type: ignore[attr-defined] # sqlalchemy stubs might be incomplete
|
|
141
|
-
logger.info("Database connection successful.")
|
|
142
|
-
self.SessionLocal: sessionmaker[SQLAlchemySessionType] = sessionmaker(
|
|
143
|
-
autocommit=False, autoflush=False, bind=self.engine
|
|
144
|
-
)
|
|
145
|
-
except OperationalError as oe:
|
|
146
|
-
guidance = (
|
|
147
|
-
"Please check your database configuration or connection parameters."
|
|
148
|
-
)
|
|
149
|
-
if is_file_db:
|
|
150
|
-
guidance = (
|
|
151
|
-
f"The database file at '{db_path}' might be corrupted, "
|
|
152
|
-
"inaccessible, or not a valid SQLite file. "
|
|
153
|
-
"Consider running 'leanexplore data fetch' to get a fresh copy."
|
|
154
|
-
)
|
|
155
|
-
logger.error(
|
|
156
|
-
f"Failed to initialize database engine or connection to {db_url}: "
|
|
157
|
-
f"{oe}\n{guidance}"
|
|
158
|
-
)
|
|
159
|
-
raise RuntimeError(
|
|
160
|
-
f"Database initialization failed: {oe}. {guidance}"
|
|
161
|
-
) from oe
|
|
162
|
-
except Exception as e:
|
|
163
|
-
logger.error(
|
|
164
|
-
f"Unexpected error during database engine initialization: {e}",
|
|
165
|
-
exc_info=True,
|
|
166
|
-
)
|
|
167
|
-
raise RuntimeError(
|
|
168
|
-
f"Database initialization failed unexpectedly: {e}"
|
|
169
|
-
) from e
|
|
170
|
-
|
|
171
|
-
self.default_faiss_k: int = defaults.DEFAULT_FAISS_K
|
|
172
|
-
self.default_pagerank_weight: float = defaults.DEFAULT_PAGERANK_WEIGHT
|
|
173
|
-
self.default_text_relevance_weight: float = (
|
|
174
|
-
defaults.DEFAULT_TEXT_RELEVANCE_WEIGHT
|
|
175
|
-
)
|
|
176
|
-
self.default_name_match_weight: float = defaults.DEFAULT_NAME_MATCH_WEIGHT
|
|
177
|
-
self.default_semantic_similarity_threshold: float = (
|
|
178
|
-
defaults.DEFAULT_SEM_SIM_THRESHOLD
|
|
179
|
-
)
|
|
180
|
-
self.default_results_limit: int = defaults.DEFAULT_RESULTS_LIMIT
|
|
181
|
-
self.default_faiss_nprobe: int = defaults.DEFAULT_FAISS_NPROBE
|
|
182
|
-
self.default_faiss_oversampling_factor: int = (
|
|
183
|
-
defaults.DEFAULT_FAISS_OVERSAMPLING_FACTOR
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
logger.info("Local Service initialized successfully.")
|
|
187
|
-
|
|
188
|
-
def _serialize_sg_to_api_item(self, sg_orm: StatementGroup) -> APISearchResultItem:
|
|
189
|
-
"""Converts a StatementGroup ORM obj to APISearchResultItem Pydantic model.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
sg_orm: The SQLAlchemy StatementGroup object.
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
An APISearchResultItem Pydantic model instance.
|
|
196
|
-
"""
|
|
197
|
-
primary_decl_info = APIPrimaryDeclarationInfo(
|
|
198
|
-
lean_name=sg_orm.primary_declaration.lean_name
|
|
199
|
-
if sg_orm.primary_declaration
|
|
200
|
-
else None
|
|
201
|
-
)
|
|
202
|
-
return APISearchResultItem(
|
|
203
|
-
id=sg_orm.id,
|
|
204
|
-
primary_declaration=primary_decl_info,
|
|
205
|
-
source_file=sg_orm.source_file,
|
|
206
|
-
range_start_line=sg_orm.range_start_line,
|
|
207
|
-
display_statement_text=sg_orm.display_statement_text,
|
|
208
|
-
statement_text=sg_orm.statement_text,
|
|
209
|
-
docstring=sg_orm.docstring,
|
|
210
|
-
informal_description=sg_orm.informal_description,
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
def _perform_one_search(
|
|
214
|
-
self,
|
|
215
|
-
query: str,
|
|
216
|
-
package_filters: Optional[List[str]],
|
|
217
|
-
limit: Optional[int],
|
|
218
|
-
) -> APISearchResponse:
|
|
219
|
-
"""Helper to perform and package a single local search.
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
query: The search query string.
|
|
223
|
-
package_filters: An optional list of package names to filter results by.
|
|
224
|
-
limit: An optional limit on the number of results to return.
|
|
225
|
-
|
|
226
|
-
Returns:
|
|
227
|
-
An APISearchResponse for the given query.
|
|
228
|
-
"""
|
|
229
|
-
start_time = time.time()
|
|
230
|
-
actual_limit = limit if limit is not None else self.default_results_limit
|
|
231
|
-
|
|
232
|
-
with self.SessionLocal() as session:
|
|
233
|
-
try:
|
|
234
|
-
ranked_results_orm = perform_search(
|
|
235
|
-
session=session,
|
|
236
|
-
query_string=query,
|
|
237
|
-
model=self.embedding_model,
|
|
238
|
-
faiss_index=self.faiss_index,
|
|
239
|
-
text_chunk_id_map=self.text_chunk_id_map,
|
|
240
|
-
faiss_k=self.default_faiss_k,
|
|
241
|
-
pagerank_weight=self.default_pagerank_weight,
|
|
242
|
-
text_relevance_weight=self.default_text_relevance_weight,
|
|
243
|
-
name_match_weight=self.default_name_match_weight,
|
|
244
|
-
log_searches=True,
|
|
245
|
-
selected_packages=package_filters,
|
|
246
|
-
semantic_similarity_threshold=(
|
|
247
|
-
self.default_semantic_similarity_threshold
|
|
248
|
-
),
|
|
249
|
-
faiss_nprobe=self.default_faiss_nprobe,
|
|
250
|
-
faiss_oversampling_factor=self.default_faiss_oversampling_factor,
|
|
251
|
-
)
|
|
252
|
-
except Exception as e:
|
|
253
|
-
logger.error(
|
|
254
|
-
f"Error during perform_search execution: {e}", exc_info=True
|
|
255
|
-
)
|
|
256
|
-
raise
|
|
257
|
-
|
|
258
|
-
api_results = [
|
|
259
|
-
self._serialize_sg_to_api_item(sg_obj)
|
|
260
|
-
for sg_obj, _scores in ranked_results_orm
|
|
261
|
-
]
|
|
262
|
-
|
|
263
|
-
final_results = api_results[:actual_limit]
|
|
264
|
-
end_time = time.time()
|
|
265
|
-
processing_time_ms = int((end_time - start_time) * 1000)
|
|
266
|
-
|
|
267
|
-
return APISearchResponse(
|
|
268
|
-
query=query,
|
|
269
|
-
packages_applied=package_filters,
|
|
270
|
-
results=final_results,
|
|
271
|
-
count=len(final_results),
|
|
272
|
-
total_candidates_considered=len(api_results),
|
|
273
|
-
processing_time_ms=processing_time_ms,
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
@overload
|
|
277
|
-
def search(
|
|
278
|
-
self,
|
|
279
|
-
query: str,
|
|
280
|
-
package_filters: Optional[List[str]] = None,
|
|
281
|
-
limit: Optional[int] = None,
|
|
282
|
-
) -> APISearchResponse: ...
|
|
283
|
-
|
|
284
|
-
@overload
|
|
285
|
-
def search(
|
|
286
|
-
self,
|
|
287
|
-
query: List[str],
|
|
288
|
-
package_filters: Optional[List[str]] = None,
|
|
289
|
-
limit: Optional[int] = None,
|
|
290
|
-
) -> List[APISearchResponse]: ...
|
|
291
|
-
|
|
292
|
-
def search(
|
|
293
|
-
self,
|
|
294
|
-
query: Union[str, List[str]],
|
|
295
|
-
package_filters: Optional[List[str]] = None,
|
|
296
|
-
limit: Optional[int] = None,
|
|
297
|
-
) -> Union[APISearchResponse, List[APISearchResponse]]:
|
|
298
|
-
"""Performs a local search for statement groups.
|
|
299
|
-
|
|
300
|
-
This method can handle a single query string or a list of query strings.
|
|
301
|
-
When a list is provided, searches are performed serially.
|
|
302
|
-
|
|
303
|
-
Args:
|
|
304
|
-
query: The search query string or a list of query strings.
|
|
305
|
-
package_filters: An optional list of package names to filter results by.
|
|
306
|
-
limit: An optional limit on the number of results to return.
|
|
307
|
-
If None, defaults.DEFAULT_RESULTS_LIMIT is used.
|
|
308
|
-
|
|
309
|
-
Returns:
|
|
310
|
-
An APISearchResponse object if a single query was provided, or a
|
|
311
|
-
list of APISearchResponse objects if a list of queries was provided.
|
|
312
|
-
|
|
313
|
-
Raises:
|
|
314
|
-
RuntimeError: If service not properly initialized (e.g., assets missing).
|
|
315
|
-
Exception: Propagates exceptions from `perform_search`.
|
|
316
|
-
"""
|
|
317
|
-
if (
|
|
318
|
-
self.embedding_model is None
|
|
319
|
-
or self.faiss_index is None
|
|
320
|
-
or self.text_chunk_id_map is None
|
|
321
|
-
):
|
|
322
|
-
logger.error(
|
|
323
|
-
"Search service assets not loaded. Service may not have initialized "
|
|
324
|
-
"correctly."
|
|
325
|
-
)
|
|
326
|
-
raise RuntimeError(
|
|
327
|
-
"Search service assets not loaded. Please ensure data has been fetched."
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
was_single_query = isinstance(query, str)
|
|
331
|
-
queries = [query] if was_single_query else query
|
|
332
|
-
results = []
|
|
333
|
-
|
|
334
|
-
for q in queries:
|
|
335
|
-
response = self._perform_one_search(q, package_filters, limit)
|
|
336
|
-
results.append(response)
|
|
337
|
-
|
|
338
|
-
if was_single_query:
|
|
339
|
-
return results[0]
|
|
340
|
-
return results
|
|
341
|
-
|
|
342
|
-
@overload
|
|
343
|
-
def get_by_id(self, group_id: int) -> Optional[APISearchResultItem]: ...
|
|
344
|
-
|
|
345
|
-
@overload
|
|
346
|
-
def get_by_id(self, group_id: List[int]) -> List[Optional[APISearchResultItem]]: ...
|
|
347
|
-
|
|
348
|
-
def get_by_id(
|
|
349
|
-
self, group_id: Union[int, List[int]]
|
|
350
|
-
) -> Union[Optional[APISearchResultItem], List[Optional[APISearchResultItem]]]:
|
|
351
|
-
"""Retrieves a specific statement group by its ID from local data.
|
|
352
|
-
|
|
353
|
-
Args:
|
|
354
|
-
group_id: The unique identifier of the statement group, or a list of IDs.
|
|
355
|
-
|
|
356
|
-
Returns:
|
|
357
|
-
An APISearchResultItem if a single ID was found, None if not found.
|
|
358
|
-
A list of Optional[APISearchResultItem] if a list of IDs was provided.
|
|
359
|
-
"""
|
|
360
|
-
was_single_id = isinstance(group_id, int)
|
|
361
|
-
group_ids = [group_id] if was_single_id else group_id
|
|
362
|
-
results = []
|
|
363
|
-
|
|
364
|
-
with self.SessionLocal() as session:
|
|
365
|
-
for g_id in group_ids:
|
|
366
|
-
try:
|
|
367
|
-
stmt_group_orm = (
|
|
368
|
-
session.query(StatementGroup)
|
|
369
|
-
.options(joinedload(StatementGroup.primary_declaration))
|
|
370
|
-
.filter(StatementGroup.id == g_id)
|
|
371
|
-
.first()
|
|
372
|
-
)
|
|
373
|
-
if stmt_group_orm:
|
|
374
|
-
results.append(self._serialize_sg_to_api_item(stmt_group_orm))
|
|
375
|
-
else:
|
|
376
|
-
results.append(None)
|
|
377
|
-
except SQLAlchemyError as e:
|
|
378
|
-
logger.error(
|
|
379
|
-
f"Database error in get_by_id for group_id {g_id}: {e}",
|
|
380
|
-
exc_info=True,
|
|
381
|
-
)
|
|
382
|
-
results.append(None)
|
|
383
|
-
except Exception as e:
|
|
384
|
-
logger.error(
|
|
385
|
-
f"Unexpected error in get_by_id for group_id {g_id}: {e}",
|
|
386
|
-
exc_info=True,
|
|
387
|
-
)
|
|
388
|
-
results.append(None)
|
|
389
|
-
|
|
390
|
-
if was_single_id:
|
|
391
|
-
return results[0]
|
|
392
|
-
return results
|
|
393
|
-
|
|
394
|
-
@overload
|
|
395
|
-
def get_dependencies(self, group_id: int) -> Optional[APICitationsResponse]: ...
|
|
396
|
-
|
|
397
|
-
@overload
|
|
398
|
-
def get_dependencies(
|
|
399
|
-
self, group_id: List[int]
|
|
400
|
-
) -> List[Optional[APICitationsResponse]]: ...
|
|
401
|
-
|
|
402
|
-
def get_dependencies(
|
|
403
|
-
self, group_id: Union[int, List[int]]
|
|
404
|
-
) -> Union[Optional[APICitationsResponse], List[Optional[APICitationsResponse]]]:
|
|
405
|
-
"""Retrieves citations for a specific statement group from local data.
|
|
406
|
-
|
|
407
|
-
Citations are the statement groups that the specified group_id depends on.
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
group_id: The unique ID of the source group, or a list of IDs.
|
|
411
|
-
|
|
412
|
-
Returns:
|
|
413
|
-
An APICitationsResponse if a single ID was provided, or a list of
|
|
414
|
-
Optional[APICitationsResponse] if a list of IDs was given. Returns
|
|
415
|
-
None for IDs that are not found or cause an error.
|
|
416
|
-
"""
|
|
417
|
-
was_single_id = isinstance(group_id, int)
|
|
418
|
-
group_ids = [group_id] if was_single_id else group_id
|
|
419
|
-
results = []
|
|
420
|
-
|
|
421
|
-
with self.SessionLocal() as session:
|
|
422
|
-
for g_id in group_ids:
|
|
423
|
-
try:
|
|
424
|
-
source_group_exists = (
|
|
425
|
-
session.query(StatementGroup.id)
|
|
426
|
-
.filter(StatementGroup.id == g_id)
|
|
427
|
-
.first()
|
|
428
|
-
)
|
|
429
|
-
if not source_group_exists:
|
|
430
|
-
logger.warning(
|
|
431
|
-
f"Source statement group ID {g_id} not found for "
|
|
432
|
-
"dependency lookup."
|
|
433
|
-
)
|
|
434
|
-
results.append(None)
|
|
435
|
-
continue
|
|
436
|
-
|
|
437
|
-
cited_target_groups_orm = (
|
|
438
|
-
session.query(StatementGroup)
|
|
439
|
-
.join(
|
|
440
|
-
StatementGroupDependency,
|
|
441
|
-
StatementGroup.id
|
|
442
|
-
== StatementGroupDependency.target_statement_group_id,
|
|
443
|
-
)
|
|
444
|
-
.filter(
|
|
445
|
-
StatementGroupDependency.source_statement_group_id == g_id
|
|
446
|
-
)
|
|
447
|
-
.options(joinedload(StatementGroup.primary_declaration))
|
|
448
|
-
.all()
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
citations_api_items = [
|
|
452
|
-
self._serialize_sg_to_api_item(sg_orm)
|
|
453
|
-
for sg_orm in cited_target_groups_orm
|
|
454
|
-
]
|
|
455
|
-
|
|
456
|
-
results.append(
|
|
457
|
-
APICitationsResponse(
|
|
458
|
-
source_group_id=g_id,
|
|
459
|
-
citations=citations_api_items,
|
|
460
|
-
count=len(citations_api_items),
|
|
461
|
-
)
|
|
462
|
-
)
|
|
463
|
-
except SQLAlchemyError as e:
|
|
464
|
-
logger.error(
|
|
465
|
-
f"Database error in get_dependencies for group_id {g_id}: {e}",
|
|
466
|
-
exc_info=True,
|
|
467
|
-
)
|
|
468
|
-
results.append(None)
|
|
469
|
-
except Exception as e:
|
|
470
|
-
logger.error(
|
|
471
|
-
f"Unexpected error in get_dependencies for "
|
|
472
|
-
f"group_id {g_id}: {e}",
|
|
473
|
-
exc_info=True,
|
|
474
|
-
)
|
|
475
|
-
results.append(None)
|
|
476
|
-
|
|
477
|
-
if was_single_id:
|
|
478
|
-
return results[0]
|
|
479
|
-
return results
|
lean_explore/shared/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Local package for lean explore."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Local package for lean explore."""
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# src/lean_explore/shared/models/api.py
|
|
2
|
-
|
|
3
|
-
"""Pydantic models for API data interchange.
|
|
4
|
-
|
|
5
|
-
This module defines the Pydantic models that represent the structure of
|
|
6
|
-
request and response bodies for the remote Lean Explore API. These models
|
|
7
|
-
are used by the API client for data validation and serialization.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from typing import List, Optional
|
|
11
|
-
|
|
12
|
-
from pydantic import BaseModel, Field
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class APIPrimaryDeclarationInfo(BaseModel):
|
|
16
|
-
"""Minimal information about a primary declaration within an API response.
|
|
17
|
-
|
|
18
|
-
Attributes:
|
|
19
|
-
lean_name: The Lean name of the primary declaration, if available.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
lean_name: Optional[str] = Field(
|
|
23
|
-
None, description="The Lean name of the primary declaration."
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class APISearchResultItem(BaseModel):
|
|
28
|
-
"""Represents a single statement group item as returned by API endpoints.
|
|
29
|
-
|
|
30
|
-
This model is used for items in search results and for the direct
|
|
31
|
-
retrieval of a statement group by its ID.
|
|
32
|
-
|
|
33
|
-
Attributes:
|
|
34
|
-
id: The unique identifier of the statement group.
|
|
35
|
-
primary_declaration: Information about the primary declaration.
|
|
36
|
-
source_file: The source file where the statement group is located.
|
|
37
|
-
range_start_line: Start line of statement group in source file.
|
|
38
|
-
display_statement_text: Display-friendly statement text, if available.
|
|
39
|
-
statement_text: The full canonical statement text.
|
|
40
|
-
docstring: The docstring associated with the statement group, if available.
|
|
41
|
-
informal_description: Informal description of the statement group, if available.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
id: int = Field(..., description="Unique identifier for the statement group.")
|
|
45
|
-
primary_declaration: APIPrimaryDeclarationInfo = Field(
|
|
46
|
-
...,
|
|
47
|
-
description="Information about the primary declaration of the statement group.",
|
|
48
|
-
)
|
|
49
|
-
source_file: str = Field(
|
|
50
|
-
..., description="The source file path for the statement group."
|
|
51
|
-
)
|
|
52
|
-
range_start_line: int = Field(
|
|
53
|
-
...,
|
|
54
|
-
description="Line number of statement group in its source file.",
|
|
55
|
-
)
|
|
56
|
-
display_statement_text: Optional[str] = Field(
|
|
57
|
-
None, description="A display-optimized version of the statement text."
|
|
58
|
-
)
|
|
59
|
-
statement_text: str = Field(
|
|
60
|
-
..., description="The complete canonical text of the statement group."
|
|
61
|
-
)
|
|
62
|
-
docstring: Optional[str] = Field(
|
|
63
|
-
None, description="The docstring associated with the statement group."
|
|
64
|
-
)
|
|
65
|
-
informal_description: Optional[str] = Field(
|
|
66
|
-
None,
|
|
67
|
-
description="An informal, human-readable description of the statement group.",
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class APISearchResponse(BaseModel):
|
|
72
|
-
"""Represents the complete response structure for a search API call.
|
|
73
|
-
|
|
74
|
-
Attributes:
|
|
75
|
-
query: The original search query string submitted by the user.
|
|
76
|
-
packages_applied: List of package filters applied to the search, if any.
|
|
77
|
-
results: A list of search result items.
|
|
78
|
-
count: The number of results returned in the current response.
|
|
79
|
-
total_candidates_considered: The total number of potential candidates
|
|
80
|
-
considered by the search algorithm before limiting results.
|
|
81
|
-
processing_time_ms: Server processing time for search request, in milliseconds.
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
query: str = Field(..., description="The search query that was executed.")
|
|
85
|
-
packages_applied: Optional[List[str]] = Field(
|
|
86
|
-
None, description="List of package filters applied to the search."
|
|
87
|
-
)
|
|
88
|
-
results: List[APISearchResultItem] = Field(
|
|
89
|
-
..., description="A list of search results."
|
|
90
|
-
)
|
|
91
|
-
count: int = Field(
|
|
92
|
-
..., description="The number of results provided in this response."
|
|
93
|
-
)
|
|
94
|
-
total_candidates_considered: int = Field(
|
|
95
|
-
..., description="Total number of candidate results before truncation."
|
|
96
|
-
)
|
|
97
|
-
processing_time_ms: int = Field(
|
|
98
|
-
..., description="Server-side processing time for the search in milliseconds."
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class APICitationsResponse(BaseModel):
|
|
103
|
-
"""Represents the response structure for a dependencies (citations) API call.
|
|
104
|
-
|
|
105
|
-
Attributes:
|
|
106
|
-
source_group_id: ID of the statement group for which citations were requested.
|
|
107
|
-
citations: A list of statement groups that are cited by the source group.
|
|
108
|
-
count: The number of citations found and returned.
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
source_group_id: int = Field(
|
|
112
|
-
..., description="The ID of the statement group whose citations are listed."
|
|
113
|
-
)
|
|
114
|
-
citations: List[APISearchResultItem] = Field(
|
|
115
|
-
..., description="A list of statement groups cited by the source group."
|
|
116
|
-
)
|
|
117
|
-
count: int = Field(..., description="The number of citations provided.")
|