/ src / python / txtai / api / application.py
application.py
  1  """
  2  FastAPI application module
  3  """
  4  
  5  import inspect
  6  import os
  7  import sys
  8  
  9  from fastapi import APIRouter, Depends, FastAPI
 10  from fastapi_mcp import FastApiMCP
 11  from httpx import AsyncClient, ASGITransport
 12  
 13  from .authorization import Authorization
 14  from .base import API
 15  from .factory import APIFactory
 16  
 17  from ..app import Application
 18  
 19  
 20  def get():
 21      """
 22      Returns global API instance.
 23  
 24      Returns:
 25          API instance
 26      """
 27  
 28      return INSTANCE
 29  
 30  
 31  def create():
 32      """
 33      Creates a FastAPI instance.
 34      """
 35  
 36      # Application dependencies
 37      dependencies = []
 38  
 39      # Default implementation of token authorization
 40      token = os.environ.get("TOKEN")
 41      if token:
 42          dependencies.append(Depends(Authorization(token)))
 43  
 44      # Add custom dependencies
 45      deps = os.environ.get("DEPENDENCIES")
 46      if deps:
 47          for dep in deps.split(","):
 48              # Create and add dependency
 49              dep = APIFactory.get(dep.strip())()
 50              dependencies.append(Depends(dep))
 51  
 52      # Create FastAPI application
 53      return FastAPI(lifespan=lifespan, dependencies=dependencies if dependencies else None)
 54  
 55  
 56  def apirouters():
 57      """
 58      Lists available APIRouters.
 59  
 60      Returns:
 61          {router name: router}
 62      """
 63  
 64      # Get handle to api module
 65      api = sys.modules[".".join(__name__.split(".")[:-1])]
 66  
 67      available = {}
 68      for name, rclass in inspect.getmembers(api, inspect.ismodule):
 69          if hasattr(rclass, "router") and isinstance(rclass.router, APIRouter):
 70              available[name.lower()] = rclass.router
 71  
 72      return available
 73  
 74  
 75  def lifespan(application):
 76      """
 77      FastAPI lifespan event handler.
 78  
 79      Args:
 80          application: FastAPI application to initialize
 81      """
 82  
 83      # pylint: disable=W0603
 84      global INSTANCE
 85  
 86      # Load YAML settings
 87      config = Application.read(os.environ.get("CONFIG"))
 88  
 89      # Instantiate API instance
 90      api = os.environ.get("API_CLASS")
 91      INSTANCE = APIFactory.create(config, api) if api else API(config)
 92  
 93      # Get all known routers
 94      routers = apirouters()
 95  
 96      # Conditionally add routes based on configuration
 97      for name, router in routers.items():
 98          if name in config:
 99              application.include_router(router)
100  
101      # Special case for embeddings clusters
102      if "cluster" in config and "embeddings" not in config:
103          application.include_router(routers["embeddings"])
104  
105      # Special case to add similarity instance for embeddings
106      if "embeddings" in config and "similarity" not in config:
107          application.include_router(routers["similarity"])
108  
109      # Execute extensions if present
110      extensions = os.environ.get("EXTENSIONS")
111      if extensions:
112          for extension in extensions.split(","):
113              # Create instance and execute extension
114              extension = APIFactory.get(extension.strip())()
115              extension(application)
116  
117      # Add Model Context Protocol (MCP) service, if applicable
118      createmcp(application, config)
119  
120      yield
121  
122  
123  def createmcp(application, config):
124      """
125      Create a MCP service if necessary.
126  
127      Args:
128          application: FastAPI Application
129          config: configuration
130      """
131  
132      mcp = config.get("mcp")
133      if mcp:
134          # HTTP Client arguments
135          defaults = {"base_url": "http://apiserver", "timeout": 100}
136          clientargs = mcp.get("clientargs", {}) if isinstance(mcp, dict) else {}
137          clientargs = {**defaults, **clientargs}
138  
139          # Create HTTP client with custom options
140          client = AsyncClient(transport=ASGITransport(app=application, raise_app_exceptions=False), **clientargs)
141  
142          # MCP service arguments
143          mcpargs = mcp.get("mcpargs", {}) if isinstance(mcp, dict) else {}
144  
145          mcp = FastApiMCP(application, http_client=client, **mcpargs)
146          mcp.mount()
147  
148  
149  def start():
150      """
151      Runs application lifespan handler.
152      """
153  
154      list(lifespan(app))
155  
156  
157  # FastAPI instance txtai API instances
158  app, INSTANCE = create(), None