yangdx commited on
Commit
a80bf0f
·
1 Parent(s): fba7aaa

Fit linting

Browse files
.gitignore CHANGED
@@ -62,4 +62,4 @@ lightrag-dev/
62
  gui/
63
 
64
  # unit-test files
65
- test_*
 
62
  gui/
63
 
64
  # unit-test files
65
+ test_*
lightrag/api/lightrag_server.py CHANGED
@@ -4,7 +4,6 @@ LightRAG FastAPI Server
4
 
5
  from fastapi import (
6
  FastAPI,
7
- HTTPException,
8
  Depends,
9
  )
10
  from fastapi.responses import FileResponse
@@ -14,7 +13,7 @@ import os
14
  from fastapi.staticfiles import StaticFiles
15
  import logging
16
  import argparse
17
- from typing import Optional, Dict
18
  from pathlib import Path
19
  import configparser
20
  from ascii_colors import ASCIIColors
@@ -73,6 +72,7 @@ scan_progress: Dict = {
73
  # Lock for thread-safe operations
74
  progress_lock = threading.Lock()
75
 
 
76
  def get_default_host(binding_type: str) -> str:
77
  default_hosts = {
78
  "ollama": os.getenv("LLM_BINDING_HOST", "http://localhost:11434"),
@@ -624,7 +624,9 @@ def create_app(args):
624
  scan_progress["indexed_count"] = 0
625
  scan_progress["progress"] = 0
626
  # Create background task
627
- task = asyncio.create_task(run_scanning_process(rag, doc_manager))
 
 
628
  app.state.background_tasks.add(task)
629
  task.add_done_callback(app.state.background_tasks.discard)
630
  ASCIIColors.info(
@@ -876,8 +878,12 @@ def create_app(args):
876
  # Webui mount webui/index.html
877
  static_dir = Path(__file__).parent / "webui"
878
  static_dir.mkdir(exist_ok=True)
879
- app.mount("/webui", StaticFiles(directory=static_dir, html=True, check_dir=True), name="webui")
880
-
 
 
 
 
881
  @app.get("/webui/")
882
  async def webui_root():
883
  return FileResponse(static_dir / "index.html")
 
4
 
5
  from fastapi import (
6
  FastAPI,
 
7
  Depends,
8
  )
9
  from fastapi.responses import FileResponse
 
13
  from fastapi.staticfiles import StaticFiles
14
  import logging
15
  import argparse
16
+ from typing import Dict
17
  from pathlib import Path
18
  import configparser
19
  from ascii_colors import ASCIIColors
 
72
  # Lock for thread-safe operations
73
  progress_lock = threading.Lock()
74
 
75
+
76
  def get_default_host(binding_type: str) -> str:
77
  default_hosts = {
78
  "ollama": os.getenv("LLM_BINDING_HOST", "http://localhost:11434"),
 
624
  scan_progress["indexed_count"] = 0
625
  scan_progress["progress"] = 0
626
  # Create background task
627
+ task = asyncio.create_task(
628
+ run_scanning_process(rag, doc_manager)
629
+ )
630
  app.state.background_tasks.add(task)
631
  task.add_done_callback(app.state.background_tasks.discard)
632
  ASCIIColors.info(
 
878
  # Webui mount webui/index.html
879
  static_dir = Path(__file__).parent / "webui"
880
  static_dir.mkdir(exist_ok=True)
881
+ app.mount(
882
+ "/webui",
883
+ StaticFiles(directory=static_dir, html=True, check_dir=True),
884
+ name="webui",
885
+ )
886
+
887
  @app.get("/webui/")
888
  async def webui_root():
889
  return FileResponse(static_dir / "index.html")
lightrag/api/routers/document_routes.py CHANGED
@@ -14,9 +14,7 @@ from pathlib import Path
14
  from typing import Dict, List, Optional, Any
15
 
16
  from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile
17
- from fastapi.security import APIKeyHeader
18
  from pydantic import BaseModel, Field, field_validator
19
- from starlette.status import HTTP_403_FORBIDDEN
20
 
21
  from lightrag.base import DocProcessingStatus, DocStatus
22
  from ..utils_api import get_api_key_dependency
@@ -39,6 +37,7 @@ progress_lock = asyncio.Lock()
39
  # Temporary file prefix
40
  temp_prefix = "__tmp__"
41
 
 
42
  class InsertTextRequest(BaseModel):
43
  text: str = Field(
44
  min_length=1,
@@ -50,6 +49,7 @@ class InsertTextRequest(BaseModel):
50
  def strip_after(cls, text: str) -> str:
51
  return text.strip()
52
 
 
53
  class InsertTextsRequest(BaseModel):
54
  texts: list[str] = Field(
55
  min_length=1,
@@ -61,10 +61,12 @@ class InsertTextsRequest(BaseModel):
61
  def strip_after(cls, texts: list[str]) -> list[str]:
62
  return [text.strip() for text in texts]
63
 
 
64
  class InsertResponse(BaseModel):
65
  status: str = Field(description="Status of the operation")
66
  message: str = Field(description="Message describing the operation result")
67
 
 
68
  class DocStatusResponse(BaseModel):
69
  @staticmethod
70
  def format_datetime(dt: Any) -> Optional[str]:
@@ -84,9 +86,11 @@ class DocStatusResponse(BaseModel):
84
  error: Optional[str] = None
85
  metadata: Optional[dict[str, Any]] = None
86
 
 
87
  class DocsStatusesResponse(BaseModel):
88
  statuses: Dict[DocStatus, List[DocStatusResponse]] = {}
89
 
 
90
  class DocumentManager:
91
  def __init__(
92
  self,
@@ -129,6 +133,7 @@ class DocumentManager:
129
  def is_supported_file(self, filename: str) -> bool:
130
  return any(filename.lower().endswith(ext) for ext in self.supported_extensions)
131
 
 
132
  async def pipeline_enqueue_file(rag, file_path: Path) -> bool:
133
  try:
134
  content = ""
@@ -145,7 +150,7 @@ async def pipeline_enqueue_file(rag, file_path: Path) -> bool:
145
  case ".pdf":
146
  if not pm.is_installed("pypdf2"):
147
  pm.install("pypdf2")
148
- from PyPDF2 import PdfReader # type: ignore
149
  from io import BytesIO
150
 
151
  pdf_file = BytesIO(file)
@@ -184,10 +189,17 @@ async def pipeline_enqueue_file(rag, file_path: Path) -> bool:
184
  for sheet in wb:
185
  content += f"Sheet: {sheet.title}\n"
186
  for row in sheet.iter_rows(values_only=True):
187
- content += "\t".join(str(cell) if cell is not None else "" for cell in row) + "\n"
 
 
 
 
 
188
  content += "\n"
189
  case _:
190
- logging.error(f"Unsupported file type: {file_path.name} (extension {ext})")
 
 
191
  return False
192
 
193
  # Insert into the RAG queue
@@ -209,6 +221,7 @@ async def pipeline_enqueue_file(rag, file_path: Path) -> bool:
209
  logging.error(f"Error deleting file {file_path}: {str(e)}")
210
  return False
211
 
 
212
  async def pipeline_index_file(rag, file_path: Path):
213
  """Index a file
214
 
@@ -231,7 +244,7 @@ async def pipeline_index_file(rag, file_path: Path):
231
  case ".pdf":
232
  if not pm.is_installed("pypdf2"):
233
  pm.install("pypdf2")
234
- from PyPDF2 import PdfReader # type: ignore
235
  from io import BytesIO
236
 
237
  pdf_file = BytesIO(file)
@@ -270,10 +283,17 @@ async def pipeline_index_file(rag, file_path: Path):
270
  for sheet in wb:
271
  content += f"Sheet: {sheet.title}\n"
272
  for row in sheet.iter_rows(values_only=True):
273
- content += "\t".join(str(cell) if cell is not None else "" for cell in row) + "\n"
 
 
 
 
 
274
  content += "\n"
275
  case _:
276
- logging.error(f"Unsupported file type: {file_path.name} (extension {ext})")
 
 
277
  return
278
 
279
  # Insert into the RAG queue
@@ -288,6 +308,7 @@ async def pipeline_index_file(rag, file_path: Path):
288
  logging.error(f"Error indexing file {file_path.name}: {str(e)}")
289
  logging.error(traceback.format_exc())
290
 
 
291
  async def pipeline_index_files(rag, file_paths: List[Path]):
292
  if not file_paths:
293
  return
@@ -305,12 +326,14 @@ async def pipeline_index_files(rag, file_paths: List[Path]):
305
  logging.error(f"Error indexing files: {str(e)}")
306
  logging.error(traceback.format_exc())
307
 
 
308
  async def pipeline_index_texts(rag, texts: List[str]):
309
  if not texts:
310
  return
311
  await rag.apipeline_enqueue_documents(texts)
312
  await rag.apipeline_process_enqueue_documents()
313
 
 
314
  async def save_temp_file(input_dir: Path, file: UploadFile = File(...)) -> Path:
315
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
316
  unique_filename = f"{temp_prefix}{timestamp}_{file.filename}"
@@ -320,6 +343,7 @@ async def save_temp_file(input_dir: Path, file: UploadFile = File(...)) -> Path:
320
  shutil.copyfileobj(file.file, buffer)
321
  return temp_path
322
 
 
323
  async def run_scanning_process(rag, doc_manager: DocumentManager):
324
  """Background task to scan and index documents"""
325
  try:
@@ -349,7 +373,10 @@ async def run_scanning_process(rag, doc_manager: DocumentManager):
349
  async with progress_lock:
350
  scan_progress["is_scanning"] = False
351
 
352
- def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[str] = None):
 
 
 
353
  optional_api_key = get_api_key_dependency(api_key)
354
 
355
  @router.post("/scan", dependencies=[Depends(optional_api_key)])
@@ -437,8 +464,12 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
437
  logging.error(traceback.format_exc())
438
  raise HTTPException(status_code=500, detail=str(e))
439
 
440
- @router.post("/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
441
- async def insert_text(request: InsertTextRequest, background_tasks: BackgroundTasks):
 
 
 
 
442
  """
443
  Insert text into the RAG system.
444
 
@@ -466,8 +497,14 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
466
  logging.error(traceback.format_exc())
467
  raise HTTPException(status_code=500, detail=str(e))
468
 
469
- @router.post("/texts", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
470
- async def insert_texts(request: InsertTextsRequest, background_tasks: BackgroundTasks):
 
 
 
 
 
 
471
  """
472
  Insert multiple texts into the RAG system.
473
 
@@ -495,8 +532,12 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
495
  logging.error(traceback.format_exc())
496
  raise HTTPException(status_code=500, detail=str(e))
497
 
498
- @router.post("/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
499
- async def insert_file(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
 
 
 
 
500
  """
501
  Insert a file directly into the RAG system.
502
 
@@ -532,8 +573,14 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
532
  logging.error(traceback.format_exc())
533
  raise HTTPException(status_code=500, detail=str(e))
534
 
535
- @router.post("/file_batch", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
536
- async def insert_batch(background_tasks: BackgroundTasks, files: List[UploadFile] = File(...)):
 
 
 
 
 
 
537
  """
538
  Process multiple files in batch mode.
539
 
@@ -587,7 +634,9 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
587
  logging.error(traceback.format_exc())
588
  raise HTTPException(status_code=500, detail=str(e))
589
 
590
- @router.delete("", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
 
 
591
  async def clear_documents():
592
  """
593
  Clear all documents from the RAG system.
@@ -605,7 +654,9 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
605
  rag.text_chunks = []
606
  rag.entities_vdb = None
607
  rag.relationships_vdb = None
608
- return InsertResponse(status="success", message="All documents cleared successfully")
 
 
609
  except Exception as e:
610
  logging.error(f"Error DELETE /documents: {str(e)}")
611
  logging.error(traceback.format_exc())
@@ -651,8 +702,12 @@ def create_document_routes(rag, doc_manager: DocumentManager, api_key: Optional[
651
  content_summary=doc_status.content_summary,
652
  content_length=doc_status.content_length,
653
  status=doc_status.status,
654
- created_at=DocStatusResponse.format_datetime(doc_status.created_at),
655
- updated_at=DocStatusResponse.format_datetime(doc_status.updated_at),
 
 
 
 
656
  chunks_count=doc_status.chunks_count,
657
  error=doc_status.error,
658
  metadata=doc_status.metadata,
 
14
  from typing import Dict, List, Optional, Any
15
 
16
  from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile
 
17
  from pydantic import BaseModel, Field, field_validator
 
18
 
19
  from lightrag.base import DocProcessingStatus, DocStatus
20
  from ..utils_api import get_api_key_dependency
 
37
  # Temporary file prefix
38
  temp_prefix = "__tmp__"
39
 
40
+
41
  class InsertTextRequest(BaseModel):
42
  text: str = Field(
43
  min_length=1,
 
49
  def strip_after(cls, text: str) -> str:
50
  return text.strip()
51
 
52
+
53
  class InsertTextsRequest(BaseModel):
54
  texts: list[str] = Field(
55
  min_length=1,
 
61
  def strip_after(cls, texts: list[str]) -> list[str]:
62
  return [text.strip() for text in texts]
63
 
64
+
65
  class InsertResponse(BaseModel):
66
  status: str = Field(description="Status of the operation")
67
  message: str = Field(description="Message describing the operation result")
68
 
69
+
70
  class DocStatusResponse(BaseModel):
71
  @staticmethod
72
  def format_datetime(dt: Any) -> Optional[str]:
 
86
  error: Optional[str] = None
87
  metadata: Optional[dict[str, Any]] = None
88
 
89
+
90
  class DocsStatusesResponse(BaseModel):
91
  statuses: Dict[DocStatus, List[DocStatusResponse]] = {}
92
 
93
+
94
  class DocumentManager:
95
  def __init__(
96
  self,
 
133
  def is_supported_file(self, filename: str) -> bool:
134
  return any(filename.lower().endswith(ext) for ext in self.supported_extensions)
135
 
136
+
137
  async def pipeline_enqueue_file(rag, file_path: Path) -> bool:
138
  try:
139
  content = ""
 
150
  case ".pdf":
151
  if not pm.is_installed("pypdf2"):
152
  pm.install("pypdf2")
153
+ from PyPDF2 import PdfReader # type: ignore
154
  from io import BytesIO
155
 
156
  pdf_file = BytesIO(file)
 
189
  for sheet in wb:
190
  content += f"Sheet: {sheet.title}\n"
191
  for row in sheet.iter_rows(values_only=True):
192
+ content += (
193
+ "\t".join(
194
+ str(cell) if cell is not None else "" for cell in row
195
+ )
196
+ + "\n"
197
+ )
198
  content += "\n"
199
  case _:
200
+ logging.error(
201
+ f"Unsupported file type: {file_path.name} (extension {ext})"
202
+ )
203
  return False
204
 
205
  # Insert into the RAG queue
 
221
  logging.error(f"Error deleting file {file_path}: {str(e)}")
222
  return False
223
 
224
+
225
  async def pipeline_index_file(rag, file_path: Path):
226
  """Index a file
227
 
 
244
  case ".pdf":
245
  if not pm.is_installed("pypdf2"):
246
  pm.install("pypdf2")
247
+ from PyPDF2 import PdfReader # type: ignore
248
  from io import BytesIO
249
 
250
  pdf_file = BytesIO(file)
 
283
  for sheet in wb:
284
  content += f"Sheet: {sheet.title}\n"
285
  for row in sheet.iter_rows(values_only=True):
286
+ content += (
287
+ "\t".join(
288
+ str(cell) if cell is not None else "" for cell in row
289
+ )
290
+ + "\n"
291
+ )
292
  content += "\n"
293
  case _:
294
+ logging.error(
295
+ f"Unsupported file type: {file_path.name} (extension {ext})"
296
+ )
297
  return
298
 
299
  # Insert into the RAG queue
 
308
  logging.error(f"Error indexing file {file_path.name}: {str(e)}")
309
  logging.error(traceback.format_exc())
310
 
311
+
312
  async def pipeline_index_files(rag, file_paths: List[Path]):
313
  if not file_paths:
314
  return
 
326
  logging.error(f"Error indexing files: {str(e)}")
327
  logging.error(traceback.format_exc())
328
 
329
+
330
  async def pipeline_index_texts(rag, texts: List[str]):
331
  if not texts:
332
  return
333
  await rag.apipeline_enqueue_documents(texts)
334
  await rag.apipeline_process_enqueue_documents()
335
 
336
+
337
  async def save_temp_file(input_dir: Path, file: UploadFile = File(...)) -> Path:
338
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
339
  unique_filename = f"{temp_prefix}{timestamp}_{file.filename}"
 
343
  shutil.copyfileobj(file.file, buffer)
344
  return temp_path
345
 
346
+
347
  async def run_scanning_process(rag, doc_manager: DocumentManager):
348
  """Background task to scan and index documents"""
349
  try:
 
373
  async with progress_lock:
374
  scan_progress["is_scanning"] = False
375
 
376
+
377
+ def create_document_routes(
378
+ rag, doc_manager: DocumentManager, api_key: Optional[str] = None
379
+ ):
380
  optional_api_key = get_api_key_dependency(api_key)
381
 
382
  @router.post("/scan", dependencies=[Depends(optional_api_key)])
 
464
  logging.error(traceback.format_exc())
465
  raise HTTPException(status_code=500, detail=str(e))
466
 
467
+ @router.post(
468
+ "/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)]
469
+ )
470
+ async def insert_text(
471
+ request: InsertTextRequest, background_tasks: BackgroundTasks
472
+ ):
473
  """
474
  Insert text into the RAG system.
475
 
 
497
  logging.error(traceback.format_exc())
498
  raise HTTPException(status_code=500, detail=str(e))
499
 
500
+ @router.post(
501
+ "/texts",
502
+ response_model=InsertResponse,
503
+ dependencies=[Depends(optional_api_key)],
504
+ )
505
+ async def insert_texts(
506
+ request: InsertTextsRequest, background_tasks: BackgroundTasks
507
+ ):
508
  """
509
  Insert multiple texts into the RAG system.
510
 
 
532
  logging.error(traceback.format_exc())
533
  raise HTTPException(status_code=500, detail=str(e))
534
 
535
+ @router.post(
536
+ "/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)]
537
+ )
538
+ async def insert_file(
539
+ background_tasks: BackgroundTasks, file: UploadFile = File(...)
540
+ ):
541
  """
542
  Insert a file directly into the RAG system.
543
 
 
573
  logging.error(traceback.format_exc())
574
  raise HTTPException(status_code=500, detail=str(e))
575
 
576
+ @router.post(
577
+ "/file_batch",
578
+ response_model=InsertResponse,
579
+ dependencies=[Depends(optional_api_key)],
580
+ )
581
+ async def insert_batch(
582
+ background_tasks: BackgroundTasks, files: List[UploadFile] = File(...)
583
+ ):
584
  """
585
  Process multiple files in batch mode.
586
 
 
634
  logging.error(traceback.format_exc())
635
  raise HTTPException(status_code=500, detail=str(e))
636
 
637
+ @router.delete(
638
+ "", response_model=InsertResponse, dependencies=[Depends(optional_api_key)]
639
+ )
640
  async def clear_documents():
641
  """
642
  Clear all documents from the RAG system.
 
654
  rag.text_chunks = []
655
  rag.entities_vdb = None
656
  rag.relationships_vdb = None
657
+ return InsertResponse(
658
+ status="success", message="All documents cleared successfully"
659
+ )
660
  except Exception as e:
661
  logging.error(f"Error DELETE /documents: {str(e)}")
662
  logging.error(traceback.format_exc())
 
702
  content_summary=doc_status.content_summary,
703
  content_length=doc_status.content_length,
704
  status=doc_status.status,
705
+ created_at=DocStatusResponse.format_datetime(
706
+ doc_status.created_at
707
+ ),
708
+ updated_at=DocStatusResponse.format_datetime(
709
+ doc_status.updated_at
710
+ ),
711
  chunks_count=doc_status.chunks_count,
712
  error=doc_status.error,
713
  metadata=doc_status.metadata,
lightrag/api/routers/graph_routes.py CHANGED
@@ -4,12 +4,13 @@ This module contains all graph-related routes for the LightRAG API.
4
 
5
  from typing import Optional
6
 
7
- from fastapi import APIRouter, Depends, HTTPException
8
 
9
  from ..utils_api import get_api_key_dependency
10
 
11
  router = APIRouter(tags=["graph"])
12
 
 
13
  def create_graph_routes(rag, api_key: Optional[str] = None):
14
  optional_api_key = get_api_key_dependency(api_key)
15
 
 
4
 
5
  from typing import Optional
6
 
7
+ from fastapi import APIRouter, Depends
8
 
9
  from ..utils_api import get_api_key_dependency
10
 
11
  router = APIRouter(tags=["graph"])
12
 
13
+
14
  def create_graph_routes(rag, api_key: Optional[str] = None):
15
  optional_api_key = get_api_key_dependency(api_key)
16
 
lightrag/api/routers/query_routes.py CHANGED
@@ -4,7 +4,6 @@ This module contains all query-related routes for the LightRAG API.
4
 
5
  import json
6
  import logging
7
- import traceback
8
  from typing import Any, Dict, List, Literal, Optional
9
 
10
  from fastapi import APIRouter, Depends, HTTPException
@@ -16,6 +15,7 @@ from ascii_colors import trace_exception
16
 
17
  router = APIRouter(tags=["query"])
18
 
 
19
  class QueryRequest(BaseModel):
20
  query: str = Field(
21
  min_length=1,
@@ -131,15 +131,19 @@ class QueryRequest(BaseModel):
131
  param.stream = is_stream
132
  return param
133
 
 
134
  class QueryResponse(BaseModel):
135
  response: str = Field(
136
  description="The generated response",
137
  )
138
 
 
139
  def create_query_routes(rag, api_key: Optional[str] = None, top_k: int = 60):
140
  optional_api_key = get_api_key_dependency(api_key)
141
 
142
- @router.post("/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)])
 
 
143
  async def query_text(request: QueryRequest):
144
  """
145
  Handle a POST request at the /query endpoint to process user queries using RAG capabilities.
 
4
 
5
  import json
6
  import logging
 
7
  from typing import Any, Dict, List, Literal, Optional
8
 
9
  from fastapi import APIRouter, Depends, HTTPException
 
15
 
16
  router = APIRouter(tags=["query"])
17
 
18
+
19
  class QueryRequest(BaseModel):
20
  query: str = Field(
21
  min_length=1,
 
131
  param.stream = is_stream
132
  return param
133
 
134
+
135
  class QueryResponse(BaseModel):
136
  response: str = Field(
137
  description="The generated response",
138
  )
139
 
140
+
141
  def create_query_routes(rag, api_key: Optional[str] = None, top_k: int = 60):
142
  optional_api_key = get_api_key_dependency(api_key)
143
 
144
+ @router.post(
145
+ "/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)]
146
+ )
147
  async def query_text(request: QueryRequest):
148
  """
149
  Handle a POST request at the /query endpoint to process user queries using RAG capabilities.
lightrag/api/utils_api.py CHANGED
@@ -7,6 +7,7 @@ from fastapi import HTTPException, Security
7
  from fastapi.security import APIKeyHeader
8
  from starlette.status import HTTP_403_FORBIDDEN
9
 
 
10
  def get_api_key_dependency(api_key: Optional[str]):
11
  """
12
  Create an API key dependency for route protection.
 
7
  from fastapi.security import APIKeyHeader
8
  from starlette.status import HTTP_403_FORBIDDEN
9
 
10
+
11
  def get_api_key_dependency(api_key: Optional[str]):
12
  """
13
  Create an API key dependency for route protection.
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- future
2
  aiohttp
3
  configparser
 
4
 
5
  # Basic modules
6
  numpy
 
 
1
  aiohttp
2
  configparser
3
+ future
4
 
5
  # Basic modules
6
  numpy