Coverage for fastagency/app.py: 69%
46 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-19 12:16 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-19 12:16 +0000
1__all__ = ["FastAgency"] 1afghbicde
3import os 1afghbicde
4from collections.abc import Awaitable, Generator 1afghbicde
5from contextlib import contextmanager 1afghbicde
6from typing import Any, Callable, Optional, Union 1afghbicde
8from .base import ( 1afghbicde
9 ASGIProtocol,
10 ProviderProtocol,
11 Runnable,
12 UIBase,
13 WSGIProtocol,
14)
15from .exceptions import ( 1afghbicde
16 FastAgencyASGINotImplementedError,
17 FastAgencyWSGINotImplementedError,
18)
19from .logging import get_logger 1afghbicde
21logger = get_logger(__name__) 1afghbicde
24class FastAgency: # Runnable 1afghbicde
25 def __init__( 1afghbicde
26 self,
27 provider: ProviderProtocol,
28 ui: UIBase,
29 *,
30 title: Optional[str] = None,
31 description: Optional[str] = None,
32 ) -> None:
33 """Initialize the FastAgency object.
35 Args:
36 provider (ProviderProtocol): The provider object to use
37 ui (UI): The UI object to use
38 title (Optional[str], optional): The title of the FastAgency. If None, the default string will be used. Defaults to None.
39 description (Optional[str], optional): The description of the FastAgency. If None, the default string will be used. Defaults to None.
40 """
41 # check if we need to start coverage
42 logger.info("Checking if coverage is needed.") 1abcde
43 coverage_process_start = os.environ.get("COVERAGE_PROCESS_START") 1abcde
44 if coverage_process_start: 1abcde
45 logger.info("Coverage process start detected")
46 logger.info(f"Coverage configuration file: {coverage_process_start}")
47 logger.info(
48 "To ensure coverage is written out, terminate this program with SIGTERM"
49 )
50 import coverage
52 coverage.process_startup()
53 _self: Runnable = self 1abcde
54 self._title = title or "FastAgency application" 1abcde
55 self._description = description or "FastAgency application" 1abcde
57 logger.info( 1abcde
58 f"Initializing FastAgency {self} with workflows: {provider} and UI: {ui}"
59 )
60 self._provider = provider 1abcde
61 self._ui = ui 1abcde
62 logger.info(f"Initialized FastAgency: {self}") 1abcde
64 @property 1afghbicde
65 def title(self) -> str: 1afghbicde
66 """Return the title of the FastAgency."""
67 return self._title 1j
69 @property 1afghbicde
70 def description(self) -> str: 1afghbicde
71 """Return the description of the FastAgency."""
72 return self._description
74 def __str__(self) -> str: 1afghbicde
75 """Return the string representation of the FastAgency."""
76 return f"<FastAgency title={self._title}>" 1abcdej
78 @property 1afghbicde
79 def provider(self) -> ProviderProtocol: 1afghbicde
80 """Return the provider object."""
81 return self._provider 1abcdej
83 @property 1afghbicde
84 def ui(self) -> UIBase: 1afghbicde
85 """Return the UI object."""
86 return self._ui 1abcdej
88 @contextmanager 1afghbicde
89 def create(self, import_string: str) -> Generator[None, None, None]: 1afghbicde
90 """Create the FastAgency."""
91 with self._ui.create(app=self, import_string=import_string): 1abcde
92 yield 1abcde
94 def start( 1afghbicde
95 self,
96 *,
97 import_string: str,
98 name: Optional[str] = None,
99 params: dict[str, Any],
100 single_run: bool = False,
101 ) -> None:
102 """Start the FastAgency."""
103 self.ui.start( 1abcde
104 app=self,
105 import_string=import_string,
106 name=name,
107 params=params,
108 single_run=single_run,
109 )
111 def __call__(self, *args: Any) -> Union[Awaitable[None], list[bytes]]: 1afghbicde
112 if len(args) == 2 and callable(args[1]): 112 ↛ 116line 112 didn't jump to line 116 because the condition on line 112 was always true1j
113 # WSGI interface
114 environ, start_response = args 1j
115 return self.handle_wsgi(environ, start_response) 1j
116 elif len(args) == 3 and callable(args[1]) and callable(args[2]):
117 # ASGI interface
118 scope, receive, send = args
119 scope_type = scope.get("type")
120 if scope_type == "http":
121 return self.handle_asgi(scope, receive, send)
122 else:
123 raise NotImplementedError(
124 f"ASGI scope type '{scope_type}' not supported."
125 )
126 else:
127 raise TypeError(f"Invalid arguments for __call__: {args}")
129 def handle_wsgi( 1afghbicde
130 self, environ: dict[str, Any], start_response: Callable[..., Any] 1afghbicde
131 ) -> list[bytes]: 1afghbicde
132 logger.debug(f"Handling WSGI request: {environ}") 1j
133 if isinstance(self.ui, WSGIProtocol): 1j
134 return self.ui.handle_wsgi(self, environ, start_response) 1j
135 else:
136 raise FastAgencyWSGINotImplementedError(
137 f"WSGI interface not supported for UI: {self.ui.__class__.__name__}. Try running with 'fastapi run' or with a ASGI server like 'uvicorn'."
138 )
140 async def handle_asgi( 1afghbicde
141 self,
142 scope: dict[str, Any],
143 receive: Callable[[dict[str, Any]], Awaitable[None]],
144 send: Callable[[dict[str, Any]], Awaitable[None]],
145 ) -> None:
146 if isinstance(self.ui, ASGIProtocol):
147 return await self.ui.handle_asgi(self, scope, receive, send)
148 else:
149 raise FastAgencyASGINotImplementedError(
150 f"ASGI interface not supported for UI: {self.ui.__class__.__name__}. Try running with 'fastapi run' or with a WSGI server like 'gunicorn'/'waitress'."
151 )