mcp-ticketer 0.4.1__py3-none-any.whl → 0.4.3__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.

Potentially problematic release.


This version of mcp-ticketer might be problematic. Click here for more details.

Files changed (56) hide show
  1. mcp_ticketer/__init__.py +3 -12
  2. mcp_ticketer/__version__.py +1 -1
  3. mcp_ticketer/adapters/aitrackdown.py +243 -11
  4. mcp_ticketer/adapters/github.py +15 -14
  5. mcp_ticketer/adapters/hybrid.py +11 -11
  6. mcp_ticketer/adapters/jira.py +22 -25
  7. mcp_ticketer/adapters/linear/adapter.py +9 -21
  8. mcp_ticketer/adapters/linear/client.py +2 -1
  9. mcp_ticketer/adapters/linear/mappers.py +2 -1
  10. mcp_ticketer/cache/memory.py +6 -5
  11. mcp_ticketer/cli/adapter_diagnostics.py +4 -2
  12. mcp_ticketer/cli/auggie_configure.py +66 -0
  13. mcp_ticketer/cli/codex_configure.py +70 -2
  14. mcp_ticketer/cli/configure.py +7 -14
  15. mcp_ticketer/cli/diagnostics.py +2 -2
  16. mcp_ticketer/cli/discover.py +6 -11
  17. mcp_ticketer/cli/gemini_configure.py +68 -2
  18. mcp_ticketer/cli/linear_commands.py +6 -7
  19. mcp_ticketer/cli/main.py +341 -203
  20. mcp_ticketer/cli/mcp_configure.py +61 -2
  21. mcp_ticketer/cli/ticket_commands.py +27 -30
  22. mcp_ticketer/cli/utils.py +23 -22
  23. mcp_ticketer/core/__init__.py +3 -1
  24. mcp_ticketer/core/adapter.py +82 -13
  25. mcp_ticketer/core/config.py +27 -29
  26. mcp_ticketer/core/env_discovery.py +10 -10
  27. mcp_ticketer/core/env_loader.py +8 -8
  28. mcp_ticketer/core/http_client.py +16 -16
  29. mcp_ticketer/core/mappers.py +10 -10
  30. mcp_ticketer/core/models.py +50 -20
  31. mcp_ticketer/core/project_config.py +40 -34
  32. mcp_ticketer/core/registry.py +2 -2
  33. mcp_ticketer/mcp/dto.py +32 -32
  34. mcp_ticketer/mcp/response_builder.py +2 -2
  35. mcp_ticketer/mcp/server.py +17 -37
  36. mcp_ticketer/mcp/server_sdk.py +93 -0
  37. mcp_ticketer/mcp/tools/__init__.py +36 -0
  38. mcp_ticketer/mcp/tools/attachment_tools.py +179 -0
  39. mcp_ticketer/mcp/tools/bulk_tools.py +273 -0
  40. mcp_ticketer/mcp/tools/comment_tools.py +90 -0
  41. mcp_ticketer/mcp/tools/hierarchy_tools.py +383 -0
  42. mcp_ticketer/mcp/tools/pr_tools.py +154 -0
  43. mcp_ticketer/mcp/tools/search_tools.py +206 -0
  44. mcp_ticketer/mcp/tools/ticket_tools.py +277 -0
  45. mcp_ticketer/queue/health_monitor.py +4 -4
  46. mcp_ticketer/queue/manager.py +2 -2
  47. mcp_ticketer/queue/queue.py +16 -16
  48. mcp_ticketer/queue/ticket_registry.py +7 -7
  49. mcp_ticketer/queue/worker.py +2 -2
  50. {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/METADATA +90 -17
  51. mcp_ticketer-0.4.3.dist-info/RECORD +73 -0
  52. mcp_ticketer-0.4.1.dist-info/RECORD +0 -64
  53. {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/WHEEL +0 -0
  54. {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/entry_points.txt +0 -0
  55. {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/licenses/LICENSE +0 -0
  56. {mcp_ticketer-0.4.1.dist-info → mcp_ticketer-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,206 @@
1
+ """Search and query tools for finding tickets.
2
+
3
+ This module implements advanced search capabilities for tickets using
4
+ various filters and criteria.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from ...core.models import Priority, SearchQuery, TicketState
10
+ from ..server_sdk import get_adapter, mcp
11
+
12
+
13
+ @mcp.tool()
14
+ async def ticket_search(
15
+ query: str | None = None,
16
+ state: str | None = None,
17
+ priority: str | None = None,
18
+ tags: list[str] | None = None,
19
+ assignee: str | None = None,
20
+ limit: int = 10,
21
+ ) -> dict[str, Any]:
22
+ """Search tickets using advanced filters.
23
+
24
+ Searches for tickets matching the specified criteria. All filters are
25
+ optional and can be combined.
26
+
27
+ Args:
28
+ query: Text search query to match against title and description
29
+ state: Filter by state - must be one of: open, in_progress, ready, tested, done, closed, waiting, blocked
30
+ priority: Filter by priority - must be one of: low, medium, high, critical
31
+ tags: Filter by tags - tickets must have all specified tags
32
+ assignee: Filter by assigned user ID or email
33
+ limit: Maximum number of results to return (default: 10, max: 100)
34
+
35
+ Returns:
36
+ List of tickets matching search criteria, or error information
37
+
38
+ """
39
+ try:
40
+ adapter = get_adapter()
41
+
42
+ # Validate and build search query
43
+ state_enum = None
44
+ if state is not None:
45
+ try:
46
+ state_enum = TicketState(state.lower())
47
+ except ValueError:
48
+ return {
49
+ "status": "error",
50
+ "error": f"Invalid state '{state}'. Must be one of: open, in_progress, ready, tested, done, closed, waiting, blocked",
51
+ }
52
+
53
+ priority_enum = None
54
+ if priority is not None:
55
+ try:
56
+ priority_enum = Priority(priority.lower())
57
+ except ValueError:
58
+ return {
59
+ "status": "error",
60
+ "error": f"Invalid priority '{priority}'. Must be one of: low, medium, high, critical",
61
+ }
62
+
63
+ # Create search query
64
+ search_query = SearchQuery(
65
+ query=query,
66
+ state=state_enum,
67
+ priority=priority_enum,
68
+ tags=tags,
69
+ assignee=assignee,
70
+ limit=min(limit, 100), # Enforce max limit
71
+ )
72
+
73
+ # Execute search via adapter
74
+ results = await adapter.search(search_query)
75
+
76
+ return {
77
+ "status": "completed",
78
+ "tickets": [ticket.model_dump() for ticket in results],
79
+ "count": len(results),
80
+ "query": {
81
+ "text": query,
82
+ "state": state,
83
+ "priority": priority,
84
+ "tags": tags,
85
+ "assignee": assignee,
86
+ },
87
+ }
88
+ except Exception as e:
89
+ return {
90
+ "status": "error",
91
+ "error": f"Failed to search tickets: {str(e)}",
92
+ }
93
+
94
+
95
+ @mcp.tool()
96
+ async def ticket_search_hierarchy(
97
+ query: str,
98
+ include_children: bool = True,
99
+ max_depth: int = 3,
100
+ ) -> dict[str, Any]:
101
+ """Search tickets and include their hierarchy.
102
+
103
+ Performs a text search and returns matching tickets along with their
104
+ hierarchical context (parent epics/issues and child issues/tasks).
105
+
106
+ Args:
107
+ query: Text search query to match against title and description
108
+ include_children: Whether to include child tickets in results
109
+ max_depth: Maximum hierarchy depth to include (1-3, default: 3)
110
+
111
+ Returns:
112
+ List of tickets with hierarchy information, or error information
113
+
114
+ """
115
+ try:
116
+ adapter = get_adapter()
117
+
118
+ # Validate max_depth
119
+ if max_depth < 1 or max_depth > 3:
120
+ return {
121
+ "status": "error",
122
+ "error": "max_depth must be between 1 and 3",
123
+ }
124
+
125
+ # Create search query
126
+ search_query = SearchQuery(
127
+ query=query,
128
+ limit=50, # Reasonable limit for hierarchical search
129
+ )
130
+
131
+ # Execute search via adapter
132
+ results = await adapter.search(search_query)
133
+
134
+ # Build hierarchical results
135
+ hierarchical_results = []
136
+ for ticket in results:
137
+ ticket_data = {
138
+ "ticket": ticket.model_dump(),
139
+ "hierarchy": {},
140
+ }
141
+
142
+ # Get parent epic if applicable
143
+ parent_epic_id = getattr(ticket, "parent_epic", None)
144
+ if parent_epic_id and max_depth >= 2:
145
+ try:
146
+ parent_epic = await adapter.read(parent_epic_id)
147
+ if parent_epic:
148
+ ticket_data["hierarchy"][
149
+ "parent_epic"
150
+ ] = parent_epic.model_dump()
151
+ except Exception:
152
+ pass # Parent not found, continue
153
+
154
+ # Get parent issue if applicable (for tasks)
155
+ parent_issue_id = getattr(ticket, "parent_issue", None)
156
+ if parent_issue_id and max_depth >= 2:
157
+ try:
158
+ parent_issue = await adapter.read(parent_issue_id)
159
+ if parent_issue:
160
+ ticket_data["hierarchy"][
161
+ "parent_issue"
162
+ ] = parent_issue.model_dump()
163
+ except Exception:
164
+ pass # Parent not found, continue
165
+
166
+ # Get children if requested
167
+ if include_children and max_depth >= 2:
168
+ children = []
169
+
170
+ # Get child issues (for epics)
171
+ child_issue_ids = getattr(ticket, "child_issues", [])
172
+ for child_id in child_issue_ids:
173
+ try:
174
+ child = await adapter.read(child_id)
175
+ if child:
176
+ children.append(child.model_dump())
177
+ except Exception:
178
+ pass # Child not found, continue
179
+
180
+ # Get child tasks (for issues)
181
+ child_task_ids = getattr(ticket, "children", [])
182
+ for child_id in child_task_ids:
183
+ try:
184
+ child = await adapter.read(child_id)
185
+ if child:
186
+ children.append(child.model_dump())
187
+ except Exception:
188
+ pass # Child not found, continue
189
+
190
+ if children:
191
+ ticket_data["hierarchy"]["children"] = children
192
+
193
+ hierarchical_results.append(ticket_data)
194
+
195
+ return {
196
+ "status": "completed",
197
+ "results": hierarchical_results,
198
+ "count": len(hierarchical_results),
199
+ "query": query,
200
+ "max_depth": max_depth,
201
+ }
202
+ except Exception as e:
203
+ return {
204
+ "status": "error",
205
+ "error": f"Failed to search with hierarchy: {str(e)}",
206
+ }
@@ -0,0 +1,277 @@
1
+ """Basic CRUD operations for tickets.
2
+
3
+ This module implements the core create, read, update, delete, and list
4
+ operations for tickets using the FastMCP SDK.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from ...core.models import Priority, Task, TicketState
10
+ from ..server_sdk import get_adapter, mcp
11
+
12
+
13
+ @mcp.tool()
14
+ async def ticket_create(
15
+ title: str,
16
+ description: str = "",
17
+ priority: str = "medium",
18
+ tags: list[str] | None = None,
19
+ assignee: str | None = None,
20
+ ) -> dict[str, Any]:
21
+ """Create a new ticket with specified details.
22
+
23
+ Args:
24
+ title: Ticket title (required)
25
+ description: Detailed description of the ticket
26
+ priority: Priority level - must be one of: low, medium, high, critical
27
+ tags: List of tags to categorize the ticket
28
+ assignee: User ID or email to assign the ticket to
29
+
30
+ Returns:
31
+ Created ticket details including ID and metadata, or error information
32
+
33
+ """
34
+ try:
35
+ adapter = get_adapter()
36
+
37
+ # Validate and convert priority
38
+ try:
39
+ priority_enum = Priority(priority.lower())
40
+ except ValueError:
41
+ return {
42
+ "status": "error",
43
+ "error": f"Invalid priority '{priority}'. Must be one of: low, medium, high, critical",
44
+ }
45
+
46
+ # Create task object
47
+ task = Task(
48
+ title=title,
49
+ description=description or "",
50
+ priority=priority_enum,
51
+ tags=tags or [],
52
+ assignee=assignee,
53
+ )
54
+
55
+ # Create via adapter
56
+ created = await adapter.create(task)
57
+
58
+ return {
59
+ "status": "completed",
60
+ "ticket": created.model_dump(),
61
+ }
62
+ except Exception as e:
63
+ return {
64
+ "status": "error",
65
+ "error": f"Failed to create ticket: {str(e)}",
66
+ }
67
+
68
+
69
+ @mcp.tool()
70
+ async def ticket_read(ticket_id: str) -> dict[str, Any]:
71
+ """Read a ticket by its ID.
72
+
73
+ Args:
74
+ ticket_id: Unique identifier of the ticket to retrieve
75
+
76
+ Returns:
77
+ Ticket details if found, or error information
78
+
79
+ """
80
+ try:
81
+ adapter = get_adapter()
82
+ ticket = await adapter.read(ticket_id)
83
+
84
+ if ticket is None:
85
+ return {
86
+ "status": "error",
87
+ "error": f"Ticket {ticket_id} not found",
88
+ }
89
+
90
+ return {
91
+ "status": "completed",
92
+ "ticket": ticket.model_dump(),
93
+ }
94
+ except Exception as e:
95
+ return {
96
+ "status": "error",
97
+ "error": f"Failed to read ticket: {str(e)}",
98
+ }
99
+
100
+
101
+ @mcp.tool()
102
+ async def ticket_update(
103
+ ticket_id: str,
104
+ title: str | None = None,
105
+ description: str | None = None,
106
+ priority: str | None = None,
107
+ state: str | None = None,
108
+ assignee: str | None = None,
109
+ tags: list[str] | None = None,
110
+ ) -> dict[str, Any]:
111
+ """Update an existing ticket.
112
+
113
+ Args:
114
+ ticket_id: Unique identifier of the ticket to update
115
+ title: New title for the ticket
116
+ description: New description for the ticket
117
+ priority: New priority - must be one of: low, medium, high, critical
118
+ state: New state - must be one of: open, in_progress, ready, tested, done, closed, waiting, blocked
119
+ assignee: User ID or email to assign the ticket to
120
+ tags: New list of tags (replaces existing tags)
121
+
122
+ Returns:
123
+ Updated ticket details, or error information
124
+
125
+ """
126
+ try:
127
+ adapter = get_adapter()
128
+
129
+ # Build updates dictionary with only provided fields
130
+ updates: dict[str, Any] = {}
131
+
132
+ if title is not None:
133
+ updates["title"] = title
134
+ if description is not None:
135
+ updates["description"] = description
136
+ if assignee is not None:
137
+ updates["assignee"] = assignee
138
+ if tags is not None:
139
+ updates["tags"] = tags
140
+
141
+ # Validate and convert priority if provided
142
+ if priority is not None:
143
+ try:
144
+ updates["priority"] = Priority(priority.lower())
145
+ except ValueError:
146
+ return {
147
+ "status": "error",
148
+ "error": f"Invalid priority '{priority}'. Must be one of: low, medium, high, critical",
149
+ }
150
+
151
+ # Validate and convert state if provided
152
+ if state is not None:
153
+ try:
154
+ updates["state"] = TicketState(state.lower())
155
+ except ValueError:
156
+ return {
157
+ "status": "error",
158
+ "error": f"Invalid state '{state}'. Must be one of: open, in_progress, ready, tested, done, closed, waiting, blocked",
159
+ }
160
+
161
+ # Update via adapter
162
+ updated = await adapter.update(ticket_id, updates)
163
+
164
+ if updated is None:
165
+ return {
166
+ "status": "error",
167
+ "error": f"Ticket {ticket_id} not found or update failed",
168
+ }
169
+
170
+ return {
171
+ "status": "completed",
172
+ "ticket": updated.model_dump(),
173
+ }
174
+ except Exception as e:
175
+ return {
176
+ "status": "error",
177
+ "error": f"Failed to update ticket: {str(e)}",
178
+ }
179
+
180
+
181
+ @mcp.tool()
182
+ async def ticket_delete(ticket_id: str) -> dict[str, Any]:
183
+ """Delete a ticket by its ID.
184
+
185
+ Args:
186
+ ticket_id: Unique identifier of the ticket to delete
187
+
188
+ Returns:
189
+ Success confirmation or error information
190
+
191
+ """
192
+ try:
193
+ adapter = get_adapter()
194
+ success = await adapter.delete(ticket_id)
195
+
196
+ if not success:
197
+ return {
198
+ "status": "error",
199
+ "error": f"Ticket {ticket_id} not found or delete failed",
200
+ }
201
+
202
+ return {
203
+ "status": "completed",
204
+ "message": f"Ticket {ticket_id} deleted successfully",
205
+ }
206
+ except Exception as e:
207
+ return {
208
+ "status": "error",
209
+ "error": f"Failed to delete ticket: {str(e)}",
210
+ }
211
+
212
+
213
+ @mcp.tool()
214
+ async def ticket_list(
215
+ limit: int = 10,
216
+ offset: int = 0,
217
+ state: str | None = None,
218
+ priority: str | None = None,
219
+ assignee: str | None = None,
220
+ ) -> dict[str, Any]:
221
+ """List tickets with pagination and optional filters.
222
+
223
+ Args:
224
+ limit: Maximum number of tickets to return (default: 10)
225
+ offset: Number of tickets to skip for pagination (default: 0)
226
+ state: Filter by state - must be one of: open, in_progress, ready, tested, done, closed, waiting, blocked
227
+ priority: Filter by priority - must be one of: low, medium, high, critical
228
+ assignee: Filter by assigned user ID or email
229
+
230
+ Returns:
231
+ List of tickets matching criteria, or error information
232
+
233
+ """
234
+ try:
235
+ adapter = get_adapter()
236
+
237
+ # Build filters dictionary
238
+ filters: dict[str, Any] = {}
239
+
240
+ if state is not None:
241
+ try:
242
+ filters["state"] = TicketState(state.lower())
243
+ except ValueError:
244
+ return {
245
+ "status": "error",
246
+ "error": f"Invalid state '{state}'. Must be one of: open, in_progress, ready, tested, done, closed, waiting, blocked",
247
+ }
248
+
249
+ if priority is not None:
250
+ try:
251
+ filters["priority"] = Priority(priority.lower())
252
+ except ValueError:
253
+ return {
254
+ "status": "error",
255
+ "error": f"Invalid priority '{priority}'. Must be one of: low, medium, high, critical",
256
+ }
257
+
258
+ if assignee is not None:
259
+ filters["assignee"] = assignee
260
+
261
+ # List tickets via adapter
262
+ tickets = await adapter.list(
263
+ limit=limit, offset=offset, filters=filters if filters else None
264
+ )
265
+
266
+ return {
267
+ "status": "completed",
268
+ "tickets": [ticket.model_dump() for ticket in tickets],
269
+ "count": len(tickets),
270
+ "limit": limit,
271
+ "offset": offset,
272
+ }
273
+ except Exception as e:
274
+ return {
275
+ "status": "error",
276
+ "error": f"Failed to list tickets: {str(e)}",
277
+ }
@@ -4,7 +4,7 @@ import logging
4
4
  import time
5
5
  from datetime import datetime, timedelta
6
6
  from enum import Enum
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  import psutil
10
10
 
@@ -30,8 +30,8 @@ class HealthAlert:
30
30
  self,
31
31
  level: HealthStatus,
32
32
  message: str,
33
- details: Optional[dict[str, Any]] = None,
34
- timestamp: Optional[datetime] = None,
33
+ details: dict[str, Any] | None = None,
34
+ timestamp: datetime | None = None,
35
35
  ):
36
36
  self.level = level
37
37
  self.message = message
@@ -52,7 +52,7 @@ class QueueHealthMonitor:
52
52
  QUEUE_BACKLOG_WARNING = 10 # Warn if more than 10 pending items
53
53
  QUEUE_BACKLOG_CRITICAL = 50 # Critical if more than 50 pending items
54
54
 
55
- def __init__(self, queue: Optional[Queue] = None):
55
+ def __init__(self, queue: Queue | None = None):
56
56
  """Initialize health monitor.
57
57
 
58
58
  Args:
@@ -7,7 +7,7 @@ import subprocess
7
7
  import sys
8
8
  import time
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  import psutil
13
13
 
@@ -304,7 +304,7 @@ class WorkerManager:
304
304
 
305
305
  return status
306
306
 
307
- def _get_pid(self) -> Optional[int]:
307
+ def _get_pid(self) -> int | None:
308
308
  """Get worker PID from file.
309
309
 
310
310
  Returns:
@@ -8,7 +8,7 @@ from dataclasses import asdict, dataclass
8
8
  from datetime import datetime, timedelta
9
9
  from enum import Enum
10
10
  from pathlib import Path
11
- from typing import Any, Optional
11
+ from typing import Any
12
12
 
13
13
 
14
14
  class QueueStatus(str, Enum):
@@ -30,12 +30,12 @@ class QueueItem:
30
30
  operation: str
31
31
  status: QueueStatus
32
32
  created_at: datetime
33
- processed_at: Optional[datetime] = None
34
- error_message: Optional[str] = None
33
+ processed_at: datetime | None = None
34
+ error_message: str | None = None
35
35
  retry_count: int = 0
36
- result: Optional[dict[str, Any]] = None
37
- project_dir: Optional[str] = None
38
- adapter_config: Optional[dict[str, Any]] = None # Adapter configuration
36
+ result: dict[str, Any] | None = None
37
+ project_dir: str | None = None
38
+ adapter_config: dict[str, Any] | None = None # Adapter configuration
39
39
 
40
40
  def to_dict(self) -> dict:
41
41
  """Convert to dictionary for storage."""
@@ -67,7 +67,7 @@ class QueueItem:
67
67
  class Queue:
68
68
  """Thread-safe SQLite queue for ticket operations."""
69
69
 
70
- def __init__(self, db_path: Optional[Path] = None):
70
+ def __init__(self, db_path: Path | None = None):
71
71
  """Initialize queue with database connection.
72
72
 
73
73
  Args:
@@ -139,8 +139,8 @@ class Queue:
139
139
  ticket_data: dict[str, Any],
140
140
  adapter: str,
141
141
  operation: str,
142
- project_dir: Optional[str] = None,
143
- adapter_config: Optional[dict[str, Any]] = None,
142
+ project_dir: str | None = None,
143
+ adapter_config: dict[str, Any] | None = None,
144
144
  ) -> str:
145
145
  """Add item to queue.
146
146
 
@@ -186,7 +186,7 @@ class Queue:
186
186
 
187
187
  return queue_id
188
188
 
189
- def get_next_pending(self) -> Optional[QueueItem]:
189
+ def get_next_pending(self) -> QueueItem | None:
190
190
  """Get next pending item from queue atomically.
191
191
 
192
192
  Returns:
@@ -251,9 +251,9 @@ class Queue:
251
251
  self,
252
252
  queue_id: str,
253
253
  status: QueueStatus,
254
- error_message: Optional[str] = None,
255
- result: Optional[dict[str, Any]] = None,
256
- expected_status: Optional[QueueStatus] = None,
254
+ error_message: str | None = None,
255
+ result: dict[str, Any] | None = None,
256
+ expected_status: QueueStatus | None = None,
257
257
  ) -> bool:
258
258
  """Update queue item status atomically.
259
259
 
@@ -328,7 +328,7 @@ class Queue:
328
328
  raise
329
329
 
330
330
  def increment_retry(
331
- self, queue_id: str, expected_status: Optional[QueueStatus] = None
331
+ self, queue_id: str, expected_status: QueueStatus | None = None
332
332
  ) -> int:
333
333
  """Increment retry count and reset to pending atomically.
334
334
 
@@ -387,7 +387,7 @@ class Queue:
387
387
  conn.rollback()
388
388
  raise
389
389
 
390
- def get_item(self, queue_id: str) -> Optional[QueueItem]:
390
+ def get_item(self, queue_id: str) -> QueueItem | None:
391
391
  """Get specific queue item by ID.
392
392
 
393
393
  Args:
@@ -409,7 +409,7 @@ class Queue:
409
409
  return QueueItem.from_row(row) if row else None
410
410
 
411
411
  def list_items(
412
- self, status: Optional[QueueStatus] = None, limit: int = 50
412
+ self, status: QueueStatus | None = None, limit: int = 50
413
413
  ) -> list[QueueItem]:
414
414
  """List queue items.
415
415
 
@@ -5,13 +5,13 @@ import sqlite3
5
5
  import threading
6
6
  from datetime import datetime, timedelta
7
7
  from pathlib import Path
8
- from typing import Any, Optional
8
+ from typing import Any
9
9
 
10
10
 
11
11
  class TicketRegistry:
12
12
  """Persistent registry for tracking ticket IDs and their lifecycle."""
13
13
 
14
- def __init__(self, db_path: Optional[Path] = None):
14
+ def __init__(self, db_path: Path | None = None):
15
15
  """Initialize ticket registry.
16
16
 
17
17
  Args:
@@ -130,10 +130,10 @@ class TicketRegistry:
130
130
  self,
131
131
  queue_id: str,
132
132
  status: str,
133
- ticket_id: Optional[str] = None,
134
- result_data: Optional[dict[str, Any]] = None,
135
- error_message: Optional[str] = None,
136
- retry_count: Optional[int] = None,
133
+ ticket_id: str | None = None,
134
+ result_data: dict[str, Any] | None = None,
135
+ error_message: str | None = None,
136
+ retry_count: int | None = None,
137
137
  ) -> None:
138
138
  """Update ticket operation status.
139
139
 
@@ -179,7 +179,7 @@ class TicketRegistry:
179
179
  )
180
180
  conn.commit()
181
181
 
182
- def get_ticket_info(self, queue_id: str) -> Optional[dict[str, Any]]:
182
+ def get_ticket_info(self, queue_id: str) -> dict[str, Any] | None:
183
183
  """Get ticket information by queue ID.
184
184
 
185
185
  Args:
@@ -7,7 +7,7 @@ import threading
7
7
  import time
8
8
  from datetime import datetime
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  from dotenv import load_dotenv
13
13
 
@@ -58,7 +58,7 @@ class Worker:
58
58
 
59
59
  def __init__(
60
60
  self,
61
- queue: Optional[Queue] = None,
61
+ queue: Queue | None = None,
62
62
  batch_size: int = DEFAULT_BATCH_SIZE,
63
63
  max_concurrent: int = DEFAULT_MAX_CONCURRENT,
64
64
  ):