Spaces:
Sleeping
Sleeping
fix(api): replace Pydantic Depends model with custom query parser to correctly handle multi-value filters
Browse files- docs/devlogs/server/runtimeerror.txt +57 -41
- src/app/api/v1/tickets.py +70 -1
- src/app/schemas/filters.py +5 -14
- src/app/services/base_filter_service.py +0 -3
- src/app/services/ticket_service.py +0 -1
- test_fastapi_filter.py +0 -44
docs/devlogs/server/runtimeerror.txt
CHANGED
|
@@ -1,41 +1,57 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
===== Application Startup at 2025-12-08 12:33:13 =====
|
| 2 |
+
|
| 3 |
+
INFO: Started server process [7]
|
| 4 |
+
INFO: Waiting for application startup.
|
| 5 |
+
INFO: 2025-12-08T12:33:24 - app.main: ============================================================
|
| 6 |
+
INFO: 2025-12-08T12:33:24 - app.main: π SwiftOps API v1.0.0 | PRODUCTION
|
| 7 |
+
INFO: 2025-12-08T12:33:24 - app.main: π Dashboard: Enabled
|
| 8 |
+
INFO: 2025-12-08T12:33:24 - app.main: ============================================================
|
| 9 |
+
INFO: 2025-12-08T12:33:24 - app.main: π¦ Database:
|
| 10 |
+
INFO: 2025-12-08T12:33:24 - app.main: β Connected | 45 tables | 6 users
|
| 11 |
+
INFO: 2025-12-08T12:33:24 - app.main: πΎ Cache & Sessions:
|
| 12 |
+
INFO: 2025-12-08T12:33:25 - app.services.otp_service: β
OTP Service initialized with Redis storage
|
| 13 |
+
INFO: 2025-12-08T12:33:26 - app.main: β Redis: Connected
|
| 14 |
+
INFO: 2025-12-08T12:33:26 - app.main: π External Services:
|
| 15 |
+
INFO: 2025-12-08T12:33:27 - app.main: β Cloudinary: Connected
|
| 16 |
+
INFO: 2025-12-08T12:33:27 - app.main: β Resend: Configured
|
| 17 |
+
INFO: 2025-12-08T12:33:27 - app.main: β WASender: Disconnected
|
| 18 |
+
INFO: 2025-12-08T12:33:27 - app.main: β Supabase: Connected | 6 buckets
|
| 19 |
+
INFO: 2025-12-08T12:33:27 - app.main: ============================================================
|
| 20 |
+
INFO: 2025-12-08T12:33:27 - app.main: β
Startup complete | Ready to serve requests
|
| 21 |
+
INFO: 2025-12-08T12:33:27 - app.main: ============================================================
|
| 22 |
+
INFO: Application startup complete.
|
| 23 |
+
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 24 |
+
INFO: 10.16.37.13:50728 - "GET / HTTP/1.1" 200 OK
|
| 25 |
+
INFO: 10.16.13.79:55356 - "GET /health HTTP/1.1" 200 OK
|
| 26 |
+
INFO: 2025-12-08T12:37:16 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
|
| 27 |
+
INFO: 2025-12-08T12:37:16 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
|
| 28 |
+
INFO: 10.16.13.79:55356 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
|
| 29 |
+
INFO: 2025-12-08T12:37:17 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
|
| 30 |
+
INFO: 2025-12-08T12:37:17 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
|
| 31 |
+
INFO: 10.16.13.79:55356 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
|
| 32 |
+
INFO: 2025-12-08T12:37:17 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
|
| 33 |
+
INFO: 2025-12-08T12:37:17 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
|
| 34 |
+
INFO: 10.16.37.13:46511 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
|
| 35 |
+
/app/src/app/services/ticket_service.py:792: SAWarning: Coercing Subquery object into a select() for use in IN(); please pass a select() construct explicitly
|
| 36 |
+
query = query.filter(Ticket.project_id.in_(team_projects))
|
| 37 |
+
INFO: 2025-12-08T12:37:18 - app.services.ticket_service: DEBUG: filters.status = None, type = <class 'NoneType'>
|
| 38 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG apply_multi_value_filter: field=status, values=None, type=<class 'NoneType'>
|
| 39 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG: Skipping filter - values=None, has_field=True
|
| 40 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG apply_multi_value_filter: field=ticket_type, values=None, type=<class 'NoneType'>
|
| 41 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG: Skipping filter - values=None, has_field=True
|
| 42 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG apply_multi_value_filter: field=priority, values=None, type=<class 'NoneType'>
|
| 43 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG: Skipping filter - values=None, has_field=True
|
| 44 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG apply_multi_value_filter: field=source, values=None, type=<class 'NoneType'>
|
| 45 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG: Skipping filter - values=None, has_field=True
|
| 46 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG apply_multi_value_filter: field=service_type, values=None, type=<class 'NoneType'>
|
| 47 |
+
INFO: 2025-12-08T12:37:18 - app.services.base_filter_service: DEBUG: Skipping filter - values=None, has_field=True
|
| 48 |
+
INFO: 2025-12-08T12:37:18 - app.services.ticket_service: Listed 13 tickets (total: 13) for user 43b778b0-2062-4724-abbb-916a4835a9b0
|
| 49 |
+
INFO: 2025-12-08T12:37:18 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
|
| 50 |
+
INFO: 2025-12-08T12:37:18 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
|
| 51 |
+
INFO: 10.16.13.79:34696 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/regions HTTP/1.1" 200 OK
|
| 52 |
+
INFO: 10.16.13.79:55356 - "GET /api/v1/tickets/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a&status=open HTTP/1.1" 200 OK
|
| 53 |
+
INFO: 10.16.37.13:46511 - "GET /api/v1/tickets?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a&page=1&page_size=50&status=open HTTP/1.1" 200 OK
|
| 54 |
+
INFO: 10.16.37.13:23303 - "GET /health HTTP/1.1" 200 OK
|
| 55 |
+
INFO: 10.16.37.13:47886 - "GET /health HTTP/1.1" 200 OK
|
| 56 |
+
INFO: 10.16.37.13:47993 - "GET /health HTTP/1.1" 200 OK
|
| 57 |
+
INFO: 10.16.13.79:30151 - "GET /health HTTP/1.1" 200 OK
|
src/app/api/v1/tickets.py
CHANGED
|
@@ -278,6 +278,75 @@ def bulk_create_tickets_from_tasks(
|
|
| 278 |
# LIST/GET TICKETS
|
| 279 |
# ============================================
|
| 280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
@router.get(
|
| 282 |
"",
|
| 283 |
response_model=TicketListResponse,
|
|
@@ -285,7 +354,7 @@ def bulk_create_tickets_from_tasks(
|
|
| 285 |
)
|
| 286 |
def list_tickets(
|
| 287 |
# New pattern (preferred) - supports all filters via TicketFilters schema
|
| 288 |
-
filters: TicketFilters = Depends(),
|
| 289 |
|
| 290 |
# Legacy support for backward compatibility
|
| 291 |
skip: Optional[int] = Query(None, ge=0, description="DEPRECATED: Use page instead"),
|
|
|
|
| 278 |
# LIST/GET TICKETS
|
| 279 |
# ============================================
|
| 280 |
|
| 281 |
+
def parse_ticket_filters(
|
| 282 |
+
project_id: Optional[UUID] = Query(None),
|
| 283 |
+
project_region_id: Optional[UUID] = Query(None),
|
| 284 |
+
contractor_invoice_id: Optional[UUID] = Query(None),
|
| 285 |
+
status: Optional[str] = Query(None),
|
| 286 |
+
ticket_type: Optional[str] = Query(None),
|
| 287 |
+
priority: Optional[str] = Query(None),
|
| 288 |
+
source: Optional[str] = Query(None),
|
| 289 |
+
service_type: Optional[str] = Query(None),
|
| 290 |
+
scheduled_date: Optional[date] = Query(None),
|
| 291 |
+
scheduled_date_from: Optional[date] = Query(None),
|
| 292 |
+
scheduled_date_to: Optional[date] = Query(None),
|
| 293 |
+
due_date_from: Optional[date] = Query(None),
|
| 294 |
+
due_date_to: Optional[date] = Query(None),
|
| 295 |
+
is_overdue: Optional[bool] = Query(None),
|
| 296 |
+
is_assigned: Optional[bool] = Query(None),
|
| 297 |
+
has_expenses: Optional[bool] = Query(None),
|
| 298 |
+
has_location: Optional[bool] = Query(None),
|
| 299 |
+
is_invoiced: Optional[bool] = Query(None),
|
| 300 |
+
sla_violated: Optional[bool] = Query(None),
|
| 301 |
+
completion_photos_verified: Optional[bool] = Query(None),
|
| 302 |
+
completion_data_verified: Optional[bool] = Query(None),
|
| 303 |
+
search: Optional[str] = Query(None),
|
| 304 |
+
sort_by: Optional[str] = Query(None),
|
| 305 |
+
sort_order: str = Query("desc"),
|
| 306 |
+
page: int = Query(1, ge=1),
|
| 307 |
+
page_size: int = Query(50, ge=1, le=100),
|
| 308 |
+
from_date: Optional[date] = Query(None),
|
| 309 |
+
to_date: Optional[date] = Query(None),
|
| 310 |
+
) -> TicketFilters:
|
| 311 |
+
"""Parse and convert query parameters to TicketFilters"""
|
| 312 |
+
# Parse comma-separated multi-value filters
|
| 313 |
+
def parse_csv(value: Optional[str]) -> Optional[List[str]]:
|
| 314 |
+
if value is None:
|
| 315 |
+
return None
|
| 316 |
+
return [item.strip() for item in value.split(',') if item.strip()]
|
| 317 |
+
|
| 318 |
+
return TicketFilters(
|
| 319 |
+
project_id=project_id,
|
| 320 |
+
project_region_id=project_region_id,
|
| 321 |
+
contractor_invoice_id=contractor_invoice_id,
|
| 322 |
+
status=parse_csv(status),
|
| 323 |
+
ticket_type=parse_csv(ticket_type),
|
| 324 |
+
priority=parse_csv(priority),
|
| 325 |
+
source=parse_csv(source),
|
| 326 |
+
service_type=parse_csv(service_type),
|
| 327 |
+
scheduled_date=scheduled_date,
|
| 328 |
+
scheduled_date_from=scheduled_date_from,
|
| 329 |
+
scheduled_date_to=scheduled_date_to,
|
| 330 |
+
due_date_from=due_date_from,
|
| 331 |
+
due_date_to=due_date_to,
|
| 332 |
+
is_overdue=is_overdue,
|
| 333 |
+
is_assigned=is_assigned,
|
| 334 |
+
has_expenses=has_expenses,
|
| 335 |
+
has_location=has_location,
|
| 336 |
+
is_invoiced=is_invoiced,
|
| 337 |
+
sla_violated=sla_violated,
|
| 338 |
+
completion_photos_verified=completion_photos_verified,
|
| 339 |
+
completion_data_verified=completion_data_verified,
|
| 340 |
+
search=search,
|
| 341 |
+
sort_by=sort_by,
|
| 342 |
+
sort_order=sort_order,
|
| 343 |
+
page=page,
|
| 344 |
+
page_size=page_size,
|
| 345 |
+
from_date=from_date,
|
| 346 |
+
to_date=to_date,
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
|
| 350 |
@router.get(
|
| 351 |
"",
|
| 352 |
response_model=TicketListResponse,
|
|
|
|
| 354 |
)
|
| 355 |
def list_tickets(
|
| 356 |
# New pattern (preferred) - supports all filters via TicketFilters schema
|
| 357 |
+
filters: TicketFilters = Depends(parse_ticket_filters),
|
| 358 |
|
| 359 |
# Legacy support for backward compatibility
|
| 360 |
skip: Optional[int] = Query(None, ge=0, description="DEPRECATED: Use page instead"),
|
src/app/schemas/filters.py
CHANGED
|
@@ -34,11 +34,11 @@ class TicketFilters(BaseListFilters):
|
|
| 34 |
contractor_invoice_id: Optional[UUID] = Field(None, description="Filter by contractor invoice")
|
| 35 |
|
| 36 |
# Multi-value filters (comma-separated in query string)
|
| 37 |
-
status: Optional[
|
| 38 |
-
ticket_type: Optional[
|
| 39 |
-
priority: Optional[
|
| 40 |
-
source: Optional[
|
| 41 |
-
service_type: Optional[
|
| 42 |
|
| 43 |
# Date filters
|
| 44 |
scheduled_date: Optional[date] = Field(None, description="Filter by exact scheduled date")
|
|
@@ -56,15 +56,6 @@ class TicketFilters(BaseListFilters):
|
|
| 56 |
sla_violated: Optional[bool] = Field(None, description="Filter SLA violations")
|
| 57 |
completion_photos_verified: Optional[bool] = Field(None, description="Filter by photo verification status")
|
| 58 |
completion_data_verified: Optional[bool] = Field(None, description="Filter by data verification status")
|
| 59 |
-
|
| 60 |
-
@validator('status', 'ticket_type', 'priority', 'source', 'service_type', pre=True)
|
| 61 |
-
def parse_comma_separated(cls, v):
|
| 62 |
-
"""Parse comma-separated strings into lists"""
|
| 63 |
-
if v is None:
|
| 64 |
-
return None
|
| 65 |
-
if isinstance(v, str):
|
| 66 |
-
return [item.strip() for item in v.split(',') if item.strip()]
|
| 67 |
-
return v
|
| 68 |
|
| 69 |
|
| 70 |
class ProjectFilters(BaseListFilters):
|
|
|
|
| 34 |
contractor_invoice_id: Optional[UUID] = Field(None, description="Filter by contractor invoice")
|
| 35 |
|
| 36 |
# Multi-value filters (comma-separated in query string)
|
| 37 |
+
status: Optional[List[str]] = Field(None, description="Filter by status: open,assigned,in_progress,pending_review,completed,cancelled")
|
| 38 |
+
ticket_type: Optional[List[str]] = Field(None, description="Filter by type: installation,support,infrastructure")
|
| 39 |
+
priority: Optional[List[str]] = Field(None, description="Filter by priority: low,normal,high,urgent")
|
| 40 |
+
source: Optional[List[str]] = Field(None, description="Filter by source: sales_order,incident,task")
|
| 41 |
+
service_type: Optional[List[str]] = Field(None, description="Filter by service: ftth,fttb,fixed_wireless,dsl,adsl,cable,other")
|
| 42 |
|
| 43 |
# Date filters
|
| 44 |
scheduled_date: Optional[date] = Field(None, description="Filter by exact scheduled date")
|
|
|
|
| 56 |
sla_violated: Optional[bool] = Field(None, description="Filter SLA violations")
|
| 57 |
completion_photos_verified: Optional[bool] = Field(None, description="Filter by photo verification status")
|
| 58 |
completion_data_verified: Optional[bool] = Field(None, description="Filter by data verification status")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
class ProjectFilters(BaseListFilters):
|
src/app/services/base_filter_service.py
CHANGED
|
@@ -137,14 +137,11 @@ class BaseFilterService:
|
|
| 137 |
Returns:
|
| 138 |
Query with filter applied
|
| 139 |
"""
|
| 140 |
-
logger.info(f"DEBUG apply_multi_value_filter: field={field_name}, values={values}, type={type(values)}")
|
| 141 |
if not values or not hasattr(model, field_name):
|
| 142 |
-
logger.info(f"DEBUG: Skipping filter - values={values}, has_field={hasattr(model, field_name)}")
|
| 143 |
return query
|
| 144 |
|
| 145 |
try:
|
| 146 |
field = getattr(model, field_name)
|
| 147 |
-
logger.info(f"DEBUG: Applying filter {field_name} IN {values}")
|
| 148 |
return query.filter(field.in_(values))
|
| 149 |
except Exception as e:
|
| 150 |
logger.error(f"Error applying multi-value filter on {field_name}: {str(e)}")
|
|
|
|
| 137 |
Returns:
|
| 138 |
Query with filter applied
|
| 139 |
"""
|
|
|
|
| 140 |
if not values or not hasattr(model, field_name):
|
|
|
|
| 141 |
return query
|
| 142 |
|
| 143 |
try:
|
| 144 |
field = getattr(model, field_name)
|
|
|
|
| 145 |
return query.filter(field.in_(values))
|
| 146 |
except Exception as e:
|
| 147 |
logger.error(f"Error applying multi-value filter on {field_name}: {str(e)}")
|
src/app/services/ticket_service.py
CHANGED
|
@@ -802,7 +802,6 @@ class TicketService(BaseFilterService):
|
|
| 802 |
query = TicketService.apply_uuid_filter(query, Ticket, 'contractor_invoice_id', filters.contractor_invoice_id)
|
| 803 |
|
| 804 |
# Apply multi-value filters
|
| 805 |
-
logger.info(f"DEBUG: filters.status = {filters.status}, type = {type(filters.status)}")
|
| 806 |
query = TicketService.apply_multi_value_filter(query, Ticket, 'status', filters.status)
|
| 807 |
query = TicketService.apply_multi_value_filter(query, Ticket, 'ticket_type', filters.ticket_type)
|
| 808 |
query = TicketService.apply_multi_value_filter(query, Ticket, 'priority', filters.priority)
|
|
|
|
| 802 |
query = TicketService.apply_uuid_filter(query, Ticket, 'contractor_invoice_id', filters.contractor_invoice_id)
|
| 803 |
|
| 804 |
# Apply multi-value filters
|
|
|
|
| 805 |
query = TicketService.apply_multi_value_filter(query, Ticket, 'status', filters.status)
|
| 806 |
query = TicketService.apply_multi_value_filter(query, Ticket, 'ticket_type', filters.ticket_type)
|
| 807 |
query = TicketService.apply_multi_value_filter(query, Ticket, 'priority', filters.priority)
|
test_fastapi_filter.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Test to understand how FastAPI handles query parameters with Depends()
|
| 3 |
-
"""
|
| 4 |
-
from fastapi import FastAPI, Depends, Query
|
| 5 |
-
from typing import Optional, List
|
| 6 |
-
from pydantic import BaseModel, validator
|
| 7 |
-
|
| 8 |
-
app = FastAPI()
|
| 9 |
-
|
| 10 |
-
class TestFilters(BaseModel):
|
| 11 |
-
status: Optional[List[str]] = None
|
| 12 |
-
|
| 13 |
-
@validator('status', pre=True)
|
| 14 |
-
def parse_comma_separated(cls, v):
|
| 15 |
-
print(f"Validator called with: {v}, type: {type(v)}")
|
| 16 |
-
if v is None:
|
| 17 |
-
return None
|
| 18 |
-
if isinstance(v, str):
|
| 19 |
-
return [item.strip() for item in v.split(',') if item.strip()]
|
| 20 |
-
return v
|
| 21 |
-
|
| 22 |
-
@app.get("/test1")
|
| 23 |
-
def test_with_depends(filters: TestFilters = Depends()):
|
| 24 |
-
"""Using Depends() - this is what we currently have"""
|
| 25 |
-
print(f"Endpoint received: {filters.status}, type: {type(filters.status)}")
|
| 26 |
-
return {"status": filters.status, "type": str(type(filters.status))}
|
| 27 |
-
|
| 28 |
-
@app.get("/test2")
|
| 29 |
-
def test_with_query(status: Optional[str] = Query(None)):
|
| 30 |
-
"""Using Query() directly"""
|
| 31 |
-
if status:
|
| 32 |
-
status_list = [item.strip() for item in status.split(',') if item.strip()]
|
| 33 |
-
else:
|
| 34 |
-
status_list = None
|
| 35 |
-
print(f"Endpoint received: {status_list}, type: {type(status_list)}")
|
| 36 |
-
return {"status": status_list, "type": str(type(status_list))}
|
| 37 |
-
|
| 38 |
-
if __name__ == "__main__":
|
| 39 |
-
import uvicorn
|
| 40 |
-
print("Testing FastAPI query parameter handling...")
|
| 41 |
-
print("\nTest URLs:")
|
| 42 |
-
print(" http://localhost:8000/test1?status=open")
|
| 43 |
-
print(" http://localhost:8000/test2?status=open")
|
| 44 |
-
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|