kamau1 commited on
Commit
e32684c
Β·
1 Parent(s): 7628716

fix(api): replace Pydantic Depends model with custom query parser to correctly handle multi-value filters

Browse files
docs/devlogs/server/runtimeerror.txt CHANGED
@@ -1,41 +1,57 @@
1
- Dec 08 12:23:32 INFO: Started server process [2]
2
- Dec 08 12:23:32 INFO: Waiting for application startup.
3
- Dec 08 12:23:32 INFO: 2025-12-08T12:23:32 - app.main: ============================================================
4
- Dec 08 12:23:32 INFO: 2025-12-08T12:23:32 - app.main: πŸš€ SwiftOps API v1.0.0 | PRODUCTION
5
- Dec 08 12:23:32 INFO: 2025-12-08T12:23:32 - app.main: πŸ“Š Dashboard: Enabled
6
- Dec 08 12:23:32 INFO: 2025-12-08T12:23:32 - app.main: ============================================================
7
- Dec 08 12:23:32 INFO: 2025-12-08T12:23:32 - app.main: πŸ“¦ Database:
8
- Dec 08 12:23:33 INFO: 2025-12-08T12:23:33 - app.main: βœ“ Connected | 45 tables | 6 users
9
- Dec 08 12:23:33 INFO: 2025-12-08T12:23:33 - app.main: πŸ’Ύ Cache & Sessions:
10
- Dec 08 12:23:34 INFO: 2025-12-08T12:23:34 - app.services.otp_service: βœ… OTP Service initialized with Redis storage
11
- Dec 08 12:23:34 INFO: 2025-12-08T12:23:34 - app.main: βœ“ Redis: Connected
12
- Dec 08 12:23:34 INFO: 2025-12-08T12:23:34 - app.main: πŸ”Œ External Services:
13
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: βœ“ Cloudinary: Connected
14
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: βœ“ Resend: Configured
15
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: β—‹ WASender: Disconnected
16
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: βœ“ Supabase: Connected | 6 buckets
17
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: ============================================================
18
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: βœ… Startup complete | Ready to serve requests
19
- Dec 08 12:23:35 INFO: 2025-12-08T12:23:35 - app.main: ============================================================
20
- Dec 08 12:23:35 INFO: Application startup complete.
21
- Dec 08 12:23:35 INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
22
- Dec 08 12:24:01 INFO: 10.244.45.245:37314 - "GET /health HTTP/1.1" 200 OK
23
- Dec 08 12:24:12 INFO: 10.244.45.245:37116 - "GET /health HTTP/1.1" 200 OK
24
- Dec 08 12:24:14 INFO: 10.244.45.245:37116 - "GET /health HTTP/1.1" 200 OK
25
- Dec 08 12:24:15 INFO: 2025-12-08T12:24:15 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
26
- Dec 08 12:24:15 INFO: 2025-12-08T12:24:15 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
27
- Dec 08 12:24:15 INFO: 10.244.16.189:44694 - "GET /api/v1/auth/me HTTP/1.1" 200 OK
28
- Dec 08 12:24:15 INFO: 2025-12-08T12:24:15 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
29
- Dec 08 12:24:15 INFO: 2025-12-08T12:24:15 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
30
- Dec 08 12:24:15 INFO: 10.244.20.101:42996 - "GET /api/v1/auth/me/preferences/available-apps HTTP/1.1" 200 OK
31
- Dec 08 12:24:15 INFO: 2025-12-08T12:24:15 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
32
- Dec 08 12:24:15 INFO: 2025-12-08T12:24:15 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
33
- Dec 08 12:24:15 INFO: 10.244.32.6:51172 - "GET /api/v1/auth/me/preferences HTTP/1.1" 200 OK
34
- Dec 08 12:24:16 /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
35
- Dec 08 12:24:16 query = query.filter(Ticket.project_id.in_(team_projects))
36
- Dec 08 12:24:16 INFO: 2025-12-08T12:24:16 - app.services.ticket_service: Listed 13 tickets (total: 13) for user 43b778b0-2062-4724-abbb-916a4835a9b0
37
- Dec 08 12:24:16 INFO: 2025-12-08T12:24:16 - app.api.deps: Checking active user: 43b778b0-2062-4724-abbb-916a4835a9b0, is_active: True, type: <class 'bool'>
38
- Dec 08 12:24:16 INFO: 2025-12-08T12:24:16 - app.api.deps: User 43b778b0-2062-4724-abbb-916a4835a9b0 is active - proceeding
39
- Dec 08 12:24:16 INFO: 10.244.32.6:51184 - "GET /api/v1/projects/0ade6bd1-e492-4e25-b681-59f42058d29a/regions HTTP/1.1" 200 OK
40
- Dec 08 12:24:16 INFO: 10.244.20.101:43012 - "GET /api/v1/tickets?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a&page=1&page_size=50&status=open HTTP/1.1" 200 OK
41
- Dec 08 12:24:16 INFO: 10.244.45.245:58490 - "GET /api/v1/tickets/stats?project_id=0ade6bd1-e492-4e25-b681-59f42058d29a&status=open HTTP/1.1" 200 OK
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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[Union[str, List[str]]] = Field(None, description="Filter by status: open,assigned,in_progress,pending_review,completed,cancelled")
38
- ticket_type: Optional[Union[str, List[str]]] = Field(None, description="Filter by type: installation,support,infrastructure")
39
- priority: Optional[Union[str, List[str]]] = Field(None, description="Filter by priority: low,normal,high,urgent")
40
- source: Optional[Union[str, List[str]]] = Field(None, description="Filter by source: sales_order,incident,task")
41
- service_type: Optional[Union[str, 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,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")