hammad-python 0.0.29__py3-none-any.whl → 0.0.31__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.
- ham/__init__.py +10 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/METADATA +6 -32
- hammad_python-0.0.31.dist-info/RECORD +6 -0
- hammad/__init__.py +0 -84
- hammad/_internal.py +0 -256
- hammad/_main.py +0 -226
- hammad/cache/__init__.py +0 -40
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -573
- hammad/cli/plugins.py +0 -867
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -634
- hammad/data/__init__.py +0 -90
- hammad/data/collections/__init__.py +0 -49
- hammad/data/collections/collection.py +0 -326
- hammad/data/collections/indexes/__init__.py +0 -37
- hammad/data/collections/indexes/qdrant/__init__.py +0 -1
- hammad/data/collections/indexes/qdrant/index.py +0 -723
- hammad/data/collections/indexes/qdrant/settings.py +0 -94
- hammad/data/collections/indexes/qdrant/utils.py +0 -210
- hammad/data/collections/indexes/tantivy/__init__.py +0 -1
- hammad/data/collections/indexes/tantivy/index.py +0 -426
- hammad/data/collections/indexes/tantivy/settings.py +0 -40
- hammad/data/collections/indexes/tantivy/utils.py +0 -176
- hammad/data/configurations/__init__.py +0 -35
- hammad/data/configurations/configuration.py +0 -564
- hammad/data/models/__init__.py +0 -50
- hammad/data/models/extensions/__init__.py +0 -4
- hammad/data/models/extensions/pydantic/__init__.py +0 -42
- hammad/data/models/extensions/pydantic/converters.py +0 -759
- hammad/data/models/fields.py +0 -546
- hammad/data/models/model.py +0 -1078
- hammad/data/models/utils.py +0 -280
- hammad/data/sql/__init__.py +0 -24
- hammad/data/sql/database.py +0 -576
- hammad/data/sql/types.py +0 -127
- hammad/data/types/__init__.py +0 -75
- hammad/data/types/file.py +0 -431
- hammad/data/types/multimodal/__init__.py +0 -36
- hammad/data/types/multimodal/audio.py +0 -200
- hammad/data/types/multimodal/image.py +0 -182
- hammad/data/types/text.py +0 -1308
- hammad/formatting/__init__.py +0 -33
- hammad/formatting/json/__init__.py +0 -27
- hammad/formatting/json/converters.py +0 -158
- hammad/formatting/text/__init__.py +0 -63
- hammad/formatting/text/converters.py +0 -723
- hammad/formatting/text/markdown.py +0 -131
- hammad/formatting/yaml/__init__.py +0 -26
- hammad/formatting/yaml/converters.py +0 -5
- hammad/genai/__init__.py +0 -217
- hammad/genai/a2a/__init__.py +0 -32
- hammad/genai/a2a/workers.py +0 -552
- hammad/genai/agents/__init__.py +0 -59
- hammad/genai/agents/agent.py +0 -1973
- hammad/genai/agents/run.py +0 -1024
- hammad/genai/agents/types/__init__.py +0 -42
- hammad/genai/agents/types/agent_context.py +0 -13
- hammad/genai/agents/types/agent_event.py +0 -128
- hammad/genai/agents/types/agent_hooks.py +0 -220
- hammad/genai/agents/types/agent_messages.py +0 -31
- hammad/genai/agents/types/agent_response.py +0 -125
- hammad/genai/agents/types/agent_stream.py +0 -327
- hammad/genai/graphs/__init__.py +0 -125
- hammad/genai/graphs/_utils.py +0 -190
- hammad/genai/graphs/base.py +0 -1828
- hammad/genai/graphs/plugins.py +0 -316
- hammad/genai/graphs/types.py +0 -638
- hammad/genai/models/__init__.py +0 -1
- hammad/genai/models/embeddings/__init__.py +0 -43
- hammad/genai/models/embeddings/model.py +0 -226
- hammad/genai/models/embeddings/run.py +0 -163
- hammad/genai/models/embeddings/types/__init__.py +0 -37
- hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
- hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
- hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
- hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
- hammad/genai/models/language/__init__.py +0 -57
- hammad/genai/models/language/model.py +0 -1098
- hammad/genai/models/language/run.py +0 -878
- hammad/genai/models/language/types/__init__.py +0 -40
- hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
- hammad/genai/models/language/types/language_model_messages.py +0 -28
- hammad/genai/models/language/types/language_model_name.py +0 -239
- hammad/genai/models/language/types/language_model_request.py +0 -127
- hammad/genai/models/language/types/language_model_response.py +0 -217
- hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
- hammad/genai/models/language/types/language_model_settings.py +0 -89
- hammad/genai/models/language/types/language_model_stream.py +0 -600
- hammad/genai/models/language/utils/__init__.py +0 -28
- hammad/genai/models/language/utils/requests.py +0 -421
- hammad/genai/models/language/utils/structured_outputs.py +0 -135
- hammad/genai/models/model_provider.py +0 -4
- hammad/genai/models/multimodal.py +0 -47
- hammad/genai/models/reranking.py +0 -26
- hammad/genai/types/__init__.py +0 -1
- hammad/genai/types/base.py +0 -215
- hammad/genai/types/history.py +0 -290
- hammad/genai/types/tools.py +0 -507
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -1018
- hammad/mcp/__init__.py +0 -53
- hammad/mcp/client/__init__.py +0 -35
- hammad/mcp/client/client.py +0 -624
- hammad/mcp/client/client_service.py +0 -400
- hammad/mcp/client/settings.py +0 -178
- hammad/mcp/servers/__init__.py +0 -26
- hammad/mcp/servers/launcher.py +0 -1161
- hammad/runtime/__init__.py +0 -32
- hammad/runtime/decorators.py +0 -142
- hammad/runtime/run.py +0 -299
- hammad/service/__init__.py +0 -49
- hammad/service/create.py +0 -527
- hammad/service/decorators.py +0 -283
- hammad/types.py +0 -288
- hammad/typing/__init__.py +0 -435
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -275
- hammad/web/openapi/__init__.py +0 -1
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -1023
- hammad/web/utils.py +0 -472
- hammad_python-0.0.29.dist-info/RECORD +0 -135
- {hammad → ham}/py.typed +0 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
hammad/service/create.py
DELETED
@@ -1,527 +0,0 @@
|
|
1
|
-
"""hammad.service.create
|
2
|
-
|
3
|
-
Service creation utilities for launching FastAPI servers from various Python objects.
|
4
|
-
"""
|
5
|
-
|
6
|
-
import inspect
|
7
|
-
import signal
|
8
|
-
import atexit
|
9
|
-
from typing import (
|
10
|
-
Any,
|
11
|
-
Callable,
|
12
|
-
Dict,
|
13
|
-
List,
|
14
|
-
Literal,
|
15
|
-
Optional,
|
16
|
-
Type,
|
17
|
-
Union,
|
18
|
-
get_type_hints,
|
19
|
-
)
|
20
|
-
from dataclasses import dataclass, fields, is_dataclass, MISSING
|
21
|
-
from enum import Enum
|
22
|
-
|
23
|
-
try:
|
24
|
-
from fastapi import FastAPI, HTTPException
|
25
|
-
from pydantic import BaseModel, create_model
|
26
|
-
from uvicorn import Config, Server
|
27
|
-
except ImportError as e:
|
28
|
-
raise ImportError(
|
29
|
-
"Service dependencies not installed. Install with: pip install hammad-python[serve]"
|
30
|
-
) from e
|
31
|
-
|
32
|
-
import logging
|
33
|
-
|
34
|
-
logger = logging.getLogger(__name__)
|
35
|
-
|
36
|
-
|
37
|
-
class ServiceStatus(str, Enum):
|
38
|
-
"""Service status enumeration."""
|
39
|
-
|
40
|
-
STARTING = "starting"
|
41
|
-
RUNNING = "running"
|
42
|
-
STOPPING = "stopping"
|
43
|
-
STOPPED = "stopped"
|
44
|
-
ERROR = "error"
|
45
|
-
|
46
|
-
|
47
|
-
@dataclass
|
48
|
-
class ServiceConfig:
|
49
|
-
"""Configuration for service creation."""
|
50
|
-
|
51
|
-
host: str = "0.0.0.0"
|
52
|
-
port: int = 8000
|
53
|
-
log_level: str = "info"
|
54
|
-
reload: bool = False
|
55
|
-
workers: int = 1
|
56
|
-
timeout_keep_alive: int = 5
|
57
|
-
timeout_graceful_shutdown: int = 30
|
58
|
-
access_log: bool = True
|
59
|
-
use_colors: bool = True
|
60
|
-
loop: str = "asyncio"
|
61
|
-
|
62
|
-
|
63
|
-
class ServiceManager:
|
64
|
-
"""Manages service lifecycle including graceful shutdown."""
|
65
|
-
|
66
|
-
def __init__(self):
|
67
|
-
self.servers: List[Server] = []
|
68
|
-
self.status = ServiceStatus.STOPPED
|
69
|
-
self._shutdown_handlers_registered = False
|
70
|
-
|
71
|
-
def register_shutdown_handlers(self):
|
72
|
-
"""Register signal handlers for graceful shutdown."""
|
73
|
-
if self._shutdown_handlers_registered:
|
74
|
-
return
|
75
|
-
|
76
|
-
def signal_handler(signum, _):
|
77
|
-
logger.info(f"Received signal {signum}. Shutting down services...")
|
78
|
-
self.shutdown_all()
|
79
|
-
|
80
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
81
|
-
signal.signal(signal.SIGINT, signal_handler)
|
82
|
-
atexit.register(self.shutdown_all)
|
83
|
-
self._shutdown_handlers_registered = True
|
84
|
-
|
85
|
-
def add_server(self, server: Server):
|
86
|
-
"""Add a server to be managed."""
|
87
|
-
self.servers.append(server)
|
88
|
-
self.register_shutdown_handlers()
|
89
|
-
|
90
|
-
def shutdown_all(self):
|
91
|
-
"""Shutdown all managed servers."""
|
92
|
-
if self.status == ServiceStatus.STOPPING:
|
93
|
-
return
|
94
|
-
|
95
|
-
self.status = ServiceStatus.STOPPING
|
96
|
-
logger.info(f"Shutting down {len(self.servers)} service(s)...")
|
97
|
-
|
98
|
-
for server in self.servers:
|
99
|
-
try:
|
100
|
-
if server.should_exit:
|
101
|
-
continue
|
102
|
-
server.should_exit = True
|
103
|
-
logger.info("Service shutdown initiated")
|
104
|
-
except Exception as e:
|
105
|
-
logger.error(f"Error shutting down server: {e}")
|
106
|
-
|
107
|
-
self.status = ServiceStatus.STOPPED
|
108
|
-
logger.info("All services shut down")
|
109
|
-
|
110
|
-
|
111
|
-
# Global service manager
|
112
|
-
_service_manager = ServiceManager()
|
113
|
-
|
114
|
-
|
115
|
-
def _python_type_to_openapi_type(python_type: Type) -> str:
|
116
|
-
"""Convert Python type to OpenAPI type string."""
|
117
|
-
type_mapping = {
|
118
|
-
str: "string",
|
119
|
-
int: "integer",
|
120
|
-
float: "number",
|
121
|
-
bool: "boolean",
|
122
|
-
list: "array",
|
123
|
-
dict: "object",
|
124
|
-
}
|
125
|
-
|
126
|
-
# Handle Union types (Optional)
|
127
|
-
if hasattr(python_type, "__origin__"):
|
128
|
-
if python_type.__origin__ is Union:
|
129
|
-
# For Optional[T], use the non-None type
|
130
|
-
non_none_types = [t for t in python_type.__args__ if t != type(None)]
|
131
|
-
if non_none_types:
|
132
|
-
return _python_type_to_openapi_type(non_none_types[0])
|
133
|
-
elif python_type.__origin__ is list:
|
134
|
-
return "array"
|
135
|
-
elif python_type.__origin__ is dict:
|
136
|
-
return "object"
|
137
|
-
|
138
|
-
return type_mapping.get(python_type, "string")
|
139
|
-
|
140
|
-
|
141
|
-
def _create_pydantic_model_from_function(func: Callable) -> Type[BaseModel]:
|
142
|
-
"""Create a Pydantic model from function signature."""
|
143
|
-
sig = inspect.signature(func)
|
144
|
-
type_hints = get_type_hints(func)
|
145
|
-
|
146
|
-
fields_dict = {}
|
147
|
-
for param_name, param in sig.parameters.items():
|
148
|
-
if param_name == "self":
|
149
|
-
continue
|
150
|
-
|
151
|
-
param_type = type_hints.get(param_name, str)
|
152
|
-
default_value = (
|
153
|
-
... if param.default == inspect.Parameter.empty else param.default
|
154
|
-
)
|
155
|
-
|
156
|
-
fields_dict[param_name] = (param_type, default_value)
|
157
|
-
|
158
|
-
return create_model(f"{func.__name__}Model", **fields_dict)
|
159
|
-
|
160
|
-
|
161
|
-
def _create_fastapi_from_function(
|
162
|
-
func: Callable,
|
163
|
-
*,
|
164
|
-
name: Optional[str] = None,
|
165
|
-
method: Literal[
|
166
|
-
"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"
|
167
|
-
] = "POST",
|
168
|
-
path: str = "/",
|
169
|
-
include_in_schema: bool = True,
|
170
|
-
dependencies: Optional[List[Callable[..., Any]]] = None,
|
171
|
-
tags: Optional[List[str]] = None,
|
172
|
-
description: Optional[str] = None,
|
173
|
-
) -> FastAPI:
|
174
|
-
"""Create a FastAPI app from a function."""
|
175
|
-
app_name = name or func.__name__
|
176
|
-
app = FastAPI(
|
177
|
-
title=app_name,
|
178
|
-
description=description or f"Auto-generated API for {func.__name__}",
|
179
|
-
)
|
180
|
-
|
181
|
-
# Create request model for POST/PUT/PATCH methods
|
182
|
-
if method in ["POST", "PUT", "PATCH"]:
|
183
|
-
request_model = _create_pydantic_model_from_function(func)
|
184
|
-
|
185
|
-
async def endpoint(request: request_model): # type: ignore
|
186
|
-
try:
|
187
|
-
# Convert request to dict and call function
|
188
|
-
kwargs = request.model_dump()
|
189
|
-
result = func(**kwargs)
|
190
|
-
return {"result": result}
|
191
|
-
except Exception as e:
|
192
|
-
logger.error(f"Error in {func.__name__}: {e}")
|
193
|
-
raise HTTPException(status_code=500, detail=str(e))
|
194
|
-
|
195
|
-
app.add_api_route(
|
196
|
-
path,
|
197
|
-
endpoint,
|
198
|
-
methods=[method],
|
199
|
-
include_in_schema=include_in_schema,
|
200
|
-
dependencies=dependencies,
|
201
|
-
tags=tags,
|
202
|
-
)
|
203
|
-
else:
|
204
|
-
# For GET and other methods, use query parameters
|
205
|
-
sig = inspect.signature(func)
|
206
|
-
|
207
|
-
async def endpoint(**kwargs):
|
208
|
-
try:
|
209
|
-
# Filter kwargs to only include function parameters
|
210
|
-
func_kwargs = {
|
211
|
-
key: value for key, value in kwargs.items() if key in sig.parameters
|
212
|
-
}
|
213
|
-
result = func(**func_kwargs)
|
214
|
-
return {"result": result}
|
215
|
-
except Exception as e:
|
216
|
-
logger.error(f"Error in {func.__name__}: {e}")
|
217
|
-
raise HTTPException(status_code=500, detail=str(e))
|
218
|
-
|
219
|
-
# Dynamically set the endpoint signature to match the function
|
220
|
-
endpoint.__signature__ = sig
|
221
|
-
|
222
|
-
app.add_api_route(
|
223
|
-
path,
|
224
|
-
endpoint,
|
225
|
-
methods=[method],
|
226
|
-
include_in_schema=include_in_schema,
|
227
|
-
dependencies=dependencies,
|
228
|
-
tags=tags,
|
229
|
-
)
|
230
|
-
|
231
|
-
return app
|
232
|
-
|
233
|
-
|
234
|
-
def _create_fastapi_from_model(
|
235
|
-
model: Union[Type[BaseModel], Type, Any],
|
236
|
-
*,
|
237
|
-
name: Optional[str] = None,
|
238
|
-
methods: List[Literal["GET", "POST", "PUT", "DELETE"]] = None,
|
239
|
-
path: str = "/",
|
240
|
-
include_in_schema: bool = True,
|
241
|
-
dependencies: Optional[List[Callable[..., Any]]] = None,
|
242
|
-
tags: Optional[List[str]] = None,
|
243
|
-
description: Optional[str] = None,
|
244
|
-
) -> FastAPI:
|
245
|
-
"""Create a FastAPI app from a model (Pydantic, dataclass, etc.)."""
|
246
|
-
if methods is None:
|
247
|
-
methods = ["GET", "POST"]
|
248
|
-
|
249
|
-
app_name = name or getattr(model, "__name__", "ModelService")
|
250
|
-
app = FastAPI(
|
251
|
-
title=app_name,
|
252
|
-
description=description or f"Auto-generated API for {app_name}",
|
253
|
-
)
|
254
|
-
|
255
|
-
# Convert model to Pydantic if needed
|
256
|
-
if is_dataclass(model) and not issubclass(model, BaseModel):
|
257
|
-
# Convert dataclass to Pydantic model
|
258
|
-
field_definitions = {}
|
259
|
-
for field in fields(model):
|
260
|
-
field_definitions[field.name] = (
|
261
|
-
field.type,
|
262
|
-
field.default if field.default != MISSING else ...,
|
263
|
-
)
|
264
|
-
pydantic_model = create_model(f"{model.__name__}Model", **field_definitions)
|
265
|
-
elif inspect.isclass(model) and issubclass(model, BaseModel):
|
266
|
-
pydantic_model = model
|
267
|
-
else:
|
268
|
-
# For other types, create a simple wrapper
|
269
|
-
pydantic_model = create_model(f"{app_name}Model", value=(str, ...))
|
270
|
-
|
271
|
-
# Store for the service
|
272
|
-
items: Dict[str, Any] = {}
|
273
|
-
|
274
|
-
if "GET" in methods:
|
275
|
-
|
276
|
-
@app.get(path, response_model=Dict[str, Any])
|
277
|
-
async def get_items():
|
278
|
-
return {"items": list(items.values())}
|
279
|
-
|
280
|
-
@app.get(f"{path}/{{item_id}}", response_model=pydantic_model)
|
281
|
-
async def get_item(item_id: str):
|
282
|
-
if item_id not in items:
|
283
|
-
raise HTTPException(status_code=404, detail="Item not found")
|
284
|
-
return items[item_id]
|
285
|
-
|
286
|
-
if "POST" in methods:
|
287
|
-
|
288
|
-
@app.post(path, response_model=Dict[str, Any])
|
289
|
-
async def create_item(item: pydantic_model): # type: ignore
|
290
|
-
item_id = str(len(items))
|
291
|
-
items[item_id] = item
|
292
|
-
return {"id": item_id, "item": item}
|
293
|
-
|
294
|
-
if "PUT" in methods:
|
295
|
-
|
296
|
-
@app.put(f"{path}/{{item_id}}", response_model=pydantic_model)
|
297
|
-
async def update_item(item_id: str, item: pydantic_model): # type: ignore
|
298
|
-
if item_id not in items:
|
299
|
-
raise HTTPException(status_code=404, detail="Item not found")
|
300
|
-
items[item_id] = item
|
301
|
-
return item
|
302
|
-
|
303
|
-
if "DELETE" in methods:
|
304
|
-
|
305
|
-
@app.delete(f"{path}/{{item_id}}")
|
306
|
-
async def delete_item(item_id: str):
|
307
|
-
if item_id not in items:
|
308
|
-
raise HTTPException(status_code=404, detail="Item not found")
|
309
|
-
del items[item_id]
|
310
|
-
return {"message": "Item deleted"}
|
311
|
-
|
312
|
-
return app
|
313
|
-
|
314
|
-
|
315
|
-
def create_service(
|
316
|
-
target: Union[Callable, Type[BaseModel], Type, Any],
|
317
|
-
*,
|
318
|
-
# Service configuration
|
319
|
-
config: Optional[ServiceConfig] = None,
|
320
|
-
host: str = "0.0.0.0",
|
321
|
-
port: int = 8000,
|
322
|
-
# Function-specific parameters
|
323
|
-
name: Optional[str] = None,
|
324
|
-
method: Literal[
|
325
|
-
"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"
|
326
|
-
] = "POST",
|
327
|
-
path: str = "/",
|
328
|
-
# Model-specific parameters
|
329
|
-
methods: List[Literal["GET", "POST", "PUT", "DELETE"]] = None,
|
330
|
-
# FastAPI parameters
|
331
|
-
include_in_schema: bool = True,
|
332
|
-
dependencies: Optional[List[Callable[..., Any]]] = None,
|
333
|
-
tags: Optional[List[str]] = None,
|
334
|
-
description: Optional[str] = None,
|
335
|
-
# Server parameters
|
336
|
-
log_level: str = "info",
|
337
|
-
reload: bool = False,
|
338
|
-
workers: int = 1,
|
339
|
-
timeout_keep_alive: int = 5,
|
340
|
-
access_log: bool = True,
|
341
|
-
use_colors: bool = True,
|
342
|
-
auto_start: bool = True,
|
343
|
-
) -> Union[FastAPI, Server]:
|
344
|
-
"""
|
345
|
-
Create a service from a function, Pydantic model, dataclass, or other object.
|
346
|
-
|
347
|
-
Args:
|
348
|
-
target: The function or model to create a service from
|
349
|
-
config: ServiceConfig object (overrides individual parameters)
|
350
|
-
host: Host to bind to
|
351
|
-
port: Port to bind to
|
352
|
-
name: Service name (defaults to function/class name)
|
353
|
-
method: HTTP method for functions (GET, POST, etc.)
|
354
|
-
path: API path
|
355
|
-
methods: HTTP methods for models (list of methods)
|
356
|
-
include_in_schema: Include in OpenAPI schema
|
357
|
-
dependencies: FastAPI dependencies
|
358
|
-
tags: API tags
|
359
|
-
description: API description
|
360
|
-
log_level: Uvicorn log level
|
361
|
-
reload: Enable auto-reload
|
362
|
-
workers: Number of worker processes
|
363
|
-
timeout_keep_alive: Keep-alive timeout
|
364
|
-
access_log: Enable access logging
|
365
|
-
use_colors: Use colored logs
|
366
|
-
auto_start: Automatically start the server
|
367
|
-
|
368
|
-
Returns:
|
369
|
-
FastAPI app if auto_start=False, Server instance if auto_start=True
|
370
|
-
"""
|
371
|
-
# Use config if provided, otherwise use individual parameters
|
372
|
-
if config:
|
373
|
-
host = config.host
|
374
|
-
port = config.port
|
375
|
-
log_level = config.log_level
|
376
|
-
reload = config.reload
|
377
|
-
workers = config.workers
|
378
|
-
timeout_keep_alive = config.timeout_keep_alive
|
379
|
-
access_log = config.access_log
|
380
|
-
use_colors = config.use_colors
|
381
|
-
|
382
|
-
# Determine if target is a function or model-like object
|
383
|
-
if callable(target) and not inspect.isclass(target):
|
384
|
-
# It's a function
|
385
|
-
app = _create_fastapi_from_function(
|
386
|
-
target,
|
387
|
-
name=name,
|
388
|
-
method=method,
|
389
|
-
path=path,
|
390
|
-
include_in_schema=include_in_schema,
|
391
|
-
dependencies=dependencies,
|
392
|
-
tags=tags,
|
393
|
-
description=description,
|
394
|
-
)
|
395
|
-
else:
|
396
|
-
# It's a model-like object (class, Pydantic model, dataclass, etc.)
|
397
|
-
app = _create_fastapi_from_model(
|
398
|
-
target,
|
399
|
-
name=name,
|
400
|
-
methods=methods,
|
401
|
-
path=path,
|
402
|
-
include_in_schema=include_in_schema,
|
403
|
-
dependencies=dependencies,
|
404
|
-
tags=tags,
|
405
|
-
description=description,
|
406
|
-
)
|
407
|
-
|
408
|
-
if not auto_start:
|
409
|
-
return app
|
410
|
-
|
411
|
-
# Create and configure server
|
412
|
-
config_obj = Config(
|
413
|
-
app=app,
|
414
|
-
host=host,
|
415
|
-
port=port,
|
416
|
-
log_level=log_level,
|
417
|
-
reload=reload,
|
418
|
-
workers=workers,
|
419
|
-
timeout_keep_alive=timeout_keep_alive,
|
420
|
-
access_log=access_log,
|
421
|
-
use_colors=use_colors,
|
422
|
-
loop="asyncio",
|
423
|
-
)
|
424
|
-
|
425
|
-
server = Server(config_obj)
|
426
|
-
_service_manager.add_server(server)
|
427
|
-
|
428
|
-
logger.info(f"Starting service on {host}:{port}")
|
429
|
-
_service_manager.status = ServiceStatus.STARTING
|
430
|
-
|
431
|
-
try:
|
432
|
-
server.run()
|
433
|
-
_service_manager.status = ServiceStatus.RUNNING
|
434
|
-
except Exception as e:
|
435
|
-
_service_manager.status = ServiceStatus.ERROR
|
436
|
-
logger.error(f"Service failed to start: {e}")
|
437
|
-
raise
|
438
|
-
|
439
|
-
return server
|
440
|
-
|
441
|
-
|
442
|
-
async def async_create_service(
|
443
|
-
target: Union[Callable, Type[BaseModel], Type, Any],
|
444
|
-
*,
|
445
|
-
config: Optional[ServiceConfig] = None,
|
446
|
-
**kwargs,
|
447
|
-
) -> Union[FastAPI, Server]:
|
448
|
-
"""
|
449
|
-
Async version of create_service.
|
450
|
-
|
451
|
-
Args:
|
452
|
-
target: The function or model to create a service from
|
453
|
-
config: ServiceConfig object
|
454
|
-
**kwargs: Same as create_service
|
455
|
-
|
456
|
-
Returns:
|
457
|
-
FastAPI app if auto_start=False, Server instance if auto_start=True
|
458
|
-
"""
|
459
|
-
# Check if auto_start is provided and respect it
|
460
|
-
auto_start = kwargs.get("auto_start", True)
|
461
|
-
|
462
|
-
# Force auto_start=False to get the app first
|
463
|
-
kwargs["auto_start"] = False
|
464
|
-
app = create_service(target, config=config, **kwargs)
|
465
|
-
|
466
|
-
# If auto_start was False, just return the app
|
467
|
-
if not auto_start:
|
468
|
-
return app
|
469
|
-
|
470
|
-
# Use config if provided
|
471
|
-
if config:
|
472
|
-
host = config.host
|
473
|
-
port = config.port
|
474
|
-
log_level = config.log_level
|
475
|
-
reload = config.reload
|
476
|
-
workers = config.workers
|
477
|
-
timeout_keep_alive = config.timeout_keep_alive
|
478
|
-
access_log = config.access_log
|
479
|
-
use_colors = config.use_colors
|
480
|
-
else:
|
481
|
-
host = kwargs.get("host", "0.0.0.0")
|
482
|
-
port = kwargs.get("port", 8000)
|
483
|
-
log_level = kwargs.get("log_level", "info")
|
484
|
-
reload = kwargs.get("reload", False)
|
485
|
-
workers = kwargs.get("workers", 1)
|
486
|
-
timeout_keep_alive = kwargs.get("timeout_keep_alive", 5)
|
487
|
-
access_log = kwargs.get("access_log", True)
|
488
|
-
use_colors = kwargs.get("use_colors", True)
|
489
|
-
|
490
|
-
config_obj = Config(
|
491
|
-
app=app,
|
492
|
-
host=host,
|
493
|
-
port=port,
|
494
|
-
log_level=log_level,
|
495
|
-
reload=reload,
|
496
|
-
workers=workers,
|
497
|
-
timeout_keep_alive=timeout_keep_alive,
|
498
|
-
access_log=access_log,
|
499
|
-
use_colors=use_colors,
|
500
|
-
loop="asyncio",
|
501
|
-
)
|
502
|
-
|
503
|
-
server = Server(config_obj)
|
504
|
-
_service_manager.add_server(server)
|
505
|
-
|
506
|
-
logger.info(f"Starting async service on {host}:{port}")
|
507
|
-
_service_manager.status = ServiceStatus.STARTING
|
508
|
-
|
509
|
-
try:
|
510
|
-
await server.serve()
|
511
|
-
_service_manager.status = ServiceStatus.RUNNING
|
512
|
-
except Exception as e:
|
513
|
-
_service_manager.status = ServiceStatus.ERROR
|
514
|
-
logger.error(f"Async service failed to start: {e}")
|
515
|
-
raise
|
516
|
-
|
517
|
-
return server
|
518
|
-
|
519
|
-
|
520
|
-
def shutdown_all_services():
|
521
|
-
"""Shutdown all managed services."""
|
522
|
-
_service_manager.shutdown_all()
|
523
|
-
|
524
|
-
|
525
|
-
def get_service_status() -> ServiceStatus:
|
526
|
-
"""Get the current service status."""
|
527
|
-
return _service_manager.status
|