mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.23__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 (42) hide show
  1. mcp_ticketer/__init__.py +7 -7
  2. mcp_ticketer/__version__.py +4 -2
  3. mcp_ticketer/adapters/__init__.py +4 -4
  4. mcp_ticketer/adapters/aitrackdown.py +66 -49
  5. mcp_ticketer/adapters/github.py +192 -125
  6. mcp_ticketer/adapters/hybrid.py +99 -53
  7. mcp_ticketer/adapters/jira.py +161 -151
  8. mcp_ticketer/adapters/linear.py +396 -246
  9. mcp_ticketer/cache/__init__.py +1 -1
  10. mcp_ticketer/cache/memory.py +15 -16
  11. mcp_ticketer/cli/__init__.py +1 -1
  12. mcp_ticketer/cli/configure.py +69 -93
  13. mcp_ticketer/cli/discover.py +43 -35
  14. mcp_ticketer/cli/main.py +283 -298
  15. mcp_ticketer/cli/mcp_configure.py +39 -15
  16. mcp_ticketer/cli/migrate_config.py +11 -13
  17. mcp_ticketer/cli/queue_commands.py +21 -58
  18. mcp_ticketer/cli/utils.py +121 -66
  19. mcp_ticketer/core/__init__.py +2 -2
  20. mcp_ticketer/core/adapter.py +46 -39
  21. mcp_ticketer/core/config.py +128 -92
  22. mcp_ticketer/core/env_discovery.py +69 -37
  23. mcp_ticketer/core/http_client.py +57 -40
  24. mcp_ticketer/core/mappers.py +98 -54
  25. mcp_ticketer/core/models.py +38 -24
  26. mcp_ticketer/core/project_config.py +145 -80
  27. mcp_ticketer/core/registry.py +16 -16
  28. mcp_ticketer/mcp/__init__.py +1 -1
  29. mcp_ticketer/mcp/server.py +199 -145
  30. mcp_ticketer/queue/__init__.py +2 -2
  31. mcp_ticketer/queue/__main__.py +1 -1
  32. mcp_ticketer/queue/manager.py +30 -26
  33. mcp_ticketer/queue/queue.py +147 -85
  34. mcp_ticketer/queue/run_worker.py +2 -3
  35. mcp_ticketer/queue/worker.py +55 -40
  36. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/METADATA +1 -1
  37. mcp_ticketer-0.1.23.dist-info/RECORD +42 -0
  38. mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
  39. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/WHEEL +0 -0
  40. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/entry_points.txt +0 -0
  41. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/licenses/LICENSE +0 -0
  42. {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """Async queue system for mcp-ticketer."""
2
2
 
3
+ from .manager import WorkerManager
3
4
  from .queue import Queue, QueueItem, QueueStatus
4
5
  from .worker import Worker
5
- from .manager import WorkerManager
6
6
 
7
- __all__ = ["Queue", "QueueItem", "QueueStatus", "Worker", "WorkerManager"]
7
+ __all__ = ["Queue", "QueueItem", "QueueStatus", "Worker", "WorkerManager"]
@@ -3,4 +3,4 @@
3
3
  from .run_worker import main
4
4
 
5
5
  if __name__ == "__main__":
6
- main()
6
+ main()
@@ -1,17 +1,17 @@
1
1
  """Worker manager with file-based locking for single instance."""
2
2
 
3
+ import fcntl
4
+ import logging
3
5
  import os
4
- import psutil
5
6
  import subprocess
6
7
  import sys
7
8
  import time
8
9
  from pathlib import Path
9
- from typing import Optional, Dict, Any
10
- import fcntl
11
- import logging
10
+ from typing import Any, Optional
11
+
12
+ import psutil
12
13
 
13
14
  from .queue import Queue
14
- from .worker import Worker
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
@@ -31,6 +31,7 @@ class WorkerManager:
31
31
 
32
32
  Returns:
33
33
  True if lock acquired, False otherwise
34
+
34
35
  """
35
36
  try:
36
37
  # Create lock file if it doesn't exist
@@ -46,13 +47,13 @@ class WorkerManager:
46
47
  self.lock_fd.flush()
47
48
 
48
49
  return True
49
- except IOError:
50
+ except OSError:
50
51
  # Lock already held
51
52
  return False
52
53
 
53
54
  def _release_lock(self):
54
55
  """Release worker lock."""
55
- if hasattr(self, 'lock_fd'):
56
+ if hasattr(self, "lock_fd"):
56
57
  fcntl.lockf(self.lock_fd, fcntl.LOCK_UN)
57
58
  self.lock_fd.close()
58
59
 
@@ -65,6 +66,7 @@ class WorkerManager:
65
66
 
66
67
  Returns:
67
68
  True if worker started or already running, False otherwise
69
+
68
70
  """
69
71
  # Check if worker is already running
70
72
  if self.is_running():
@@ -84,6 +86,7 @@ class WorkerManager:
84
86
 
85
87
  Returns:
86
88
  True if started successfully, False otherwise
89
+
87
90
  """
88
91
  # Check if already running
89
92
  if self.is_running():
@@ -97,18 +100,14 @@ class WorkerManager:
97
100
 
98
101
  try:
99
102
  # Start worker in subprocess
100
- cmd = [
101
- sys.executable,
102
- "-m",
103
- "mcp_ticketer.queue.run_worker"
104
- ]
103
+ cmd = [sys.executable, "-m", "mcp_ticketer.queue.run_worker"]
105
104
 
106
105
  # Start as background process
107
106
  process = subprocess.Popen(
108
107
  cmd,
109
108
  stdout=subprocess.DEVNULL,
110
109
  stderr=subprocess.DEVNULL,
111
- start_new_session=True
110
+ start_new_session=True,
112
111
  )
113
112
 
114
113
  # Save PID
@@ -116,6 +115,7 @@ class WorkerManager:
116
115
 
117
116
  # Give the process a moment to start
118
117
  import time
118
+
119
119
  time.sleep(0.5)
120
120
 
121
121
  # Verify process is running
@@ -137,6 +137,7 @@ class WorkerManager:
137
137
 
138
138
  Returns:
139
139
  True if stopped successfully, False otherwise
140
+
140
141
  """
141
142
  pid = self._get_pid()
142
143
  if not pid:
@@ -177,6 +178,7 @@ class WorkerManager:
177
178
 
178
179
  Returns:
179
180
  True if restarted successfully, False otherwise
181
+
180
182
  """
181
183
  logger.info("Restarting worker...")
182
184
  self.stop()
@@ -188,6 +190,7 @@ class WorkerManager:
188
190
 
189
191
  Returns:
190
192
  True if running, False otherwise
193
+
191
194
  """
192
195
  pid = self._get_pid()
193
196
  if not pid:
@@ -204,30 +207,30 @@ class WorkerManager:
204
207
 
205
208
  return False
206
209
 
207
- def get_status(self) -> Dict[str, Any]:
210
+ def get_status(self) -> dict[str, Any]:
208
211
  """Get detailed worker status.
209
212
 
210
213
  Returns:
211
214
  Status information
215
+
212
216
  """
213
217
  is_running = self.is_running()
214
218
  pid = self._get_pid() if is_running else None
215
219
 
216
- status = {
217
- "running": is_running,
218
- "pid": pid
219
- }
220
+ status = {"running": is_running, "pid": pid}
220
221
 
221
222
  # Add process info if running
222
223
  if is_running and pid:
223
224
  try:
224
225
  process = psutil.Process(pid)
225
- status.update({
226
- "cpu_percent": process.cpu_percent(),
227
- "memory_mb": process.memory_info().rss / 1024 / 1024,
228
- "create_time": process.create_time(),
229
- "status": process.status()
230
- })
226
+ status.update(
227
+ {
228
+ "cpu_percent": process.cpu_percent(),
229
+ "memory_mb": process.memory_info().rss / 1024 / 1024,
230
+ "create_time": process.create_time(),
231
+ "status": process.status(),
232
+ }
233
+ )
231
234
  except (psutil.NoSuchProcess, psutil.AccessDenied):
232
235
  pass
233
236
 
@@ -242,6 +245,7 @@ class WorkerManager:
242
245
 
243
246
  Returns:
244
247
  Process ID or None if not found
248
+
245
249
  """
246
250
  if not self.pid_file.exists():
247
251
  return None
@@ -249,7 +253,7 @@ class WorkerManager:
249
253
  try:
250
254
  pid_text = self.pid_file.read_text().strip()
251
255
  return int(pid_text)
252
- except (ValueError, IOError):
256
+ except (OSError, ValueError):
253
257
  return None
254
258
 
255
259
  def _cleanup(self):
@@ -258,4 +262,4 @@ class WorkerManager:
258
262
  if self.pid_file.exists():
259
263
  self.pid_file.unlink()
260
264
  if self.lock_file.exists():
261
- self.lock_file.unlink()
265
+ self.lock_file.unlink()
@@ -1,18 +1,19 @@
1
1
  """SQLite-based queue system for async ticket operations."""
2
2
 
3
- import sqlite3
4
3
  import json
4
+ import sqlite3
5
5
  import threading
6
+ import uuid
7
+ from dataclasses import asdict, dataclass
6
8
  from datetime import datetime, timedelta
7
9
  from enum import Enum
8
10
  from pathlib import Path
9
- from typing import Optional, Dict, Any, List
10
- from dataclasses import dataclass, asdict
11
- import uuid
11
+ from typing import Any, Optional
12
12
 
13
13
 
14
14
  class QueueStatus(str, Enum):
15
15
  """Queue item status values."""
16
+
16
17
  PENDING = "pending"
17
18
  PROCESSING = "processing"
18
19
  COMPLETED = "completed"
@@ -22,8 +23,9 @@ class QueueStatus(str, Enum):
22
23
  @dataclass
23
24
  class QueueItem:
24
25
  """Represents a queued operation."""
26
+
25
27
  id: str
26
- ticket_data: Dict[str, Any]
28
+ ticket_data: dict[str, Any]
27
29
  adapter: str
28
30
  operation: str
29
31
  status: QueueStatus
@@ -31,15 +33,15 @@ class QueueItem:
31
33
  processed_at: Optional[datetime] = None
32
34
  error_message: Optional[str] = None
33
35
  retry_count: int = 0
34
- result: Optional[Dict[str, Any]] = None
36
+ result: Optional[dict[str, Any]] = None
35
37
  project_dir: Optional[str] = None
36
38
 
37
39
  def to_dict(self) -> dict:
38
40
  """Convert to dictionary for storage."""
39
41
  data = asdict(self)
40
- data['created_at'] = self.created_at.isoformat()
42
+ data["created_at"] = self.created_at.isoformat()
41
43
  if self.processed_at:
42
- data['processed_at'] = self.processed_at.isoformat()
44
+ data["processed_at"] = self.processed_at.isoformat()
43
45
  return data
44
46
 
45
47
  @classmethod
@@ -56,7 +58,7 @@ class QueueItem:
56
58
  error_message=row[7],
57
59
  retry_count=row[8],
58
60
  result=json.loads(row[9]) if row[9] else None,
59
- project_dir=row[10] if len(row) > 10 else None
61
+ project_dir=row[10] if len(row) > 10 else None,
60
62
  )
61
63
 
62
64
 
@@ -68,6 +70,7 @@ class Queue:
68
70
 
69
71
  Args:
70
72
  db_path: Path to SQLite database. Defaults to ~/.mcp-ticketer/queue.db
73
+
71
74
  """
72
75
  if db_path is None:
73
76
  db_dir = Path.home() / ".mcp-ticketer"
@@ -81,7 +84,8 @@ class Queue:
81
84
  def _init_database(self):
82
85
  """Initialize database schema."""
83
86
  with sqlite3.connect(self.db_path) as conn:
84
- conn.execute('''
87
+ conn.execute(
88
+ """
85
89
  CREATE TABLE IF NOT EXISTS queue (
86
90
  id TEXT PRIMARY KEY,
87
91
  ticket_data TEXT NOT NULL,
@@ -95,35 +99,44 @@ class Queue:
95
99
  result TEXT,
96
100
  CHECK (status IN ('pending', 'processing', 'completed', 'failed'))
97
101
  )
98
- ''')
102
+ """
103
+ )
99
104
 
100
105
  # Create indices for efficient queries
101
- conn.execute('''
106
+ conn.execute(
107
+ """
102
108
  CREATE INDEX IF NOT EXISTS idx_queue_status
103
109
  ON queue(status)
104
- ''')
105
- conn.execute('''
110
+ """
111
+ )
112
+ conn.execute(
113
+ """
106
114
  CREATE INDEX IF NOT EXISTS idx_queue_created
107
115
  ON queue(created_at)
108
- ''')
109
- conn.execute('''
116
+ """
117
+ )
118
+ conn.execute(
119
+ """
110
120
  CREATE INDEX IF NOT EXISTS idx_queue_adapter
111
121
  ON queue(adapter)
112
- ''')
122
+ """
123
+ )
113
124
 
114
125
  # Migration: Add project_dir column if it doesn't exist
115
126
  cursor = conn.execute("PRAGMA table_info(queue)")
116
127
  columns = [row[1] for row in cursor.fetchall()]
117
- if 'project_dir' not in columns:
118
- conn.execute('ALTER TABLE queue ADD COLUMN project_dir TEXT')
128
+ if "project_dir" not in columns:
129
+ conn.execute("ALTER TABLE queue ADD COLUMN project_dir TEXT")
119
130
 
120
131
  conn.commit()
121
132
 
122
- def add(self,
123
- ticket_data: Dict[str, Any],
124
- adapter: str,
125
- operation: str,
126
- project_dir: Optional[str] = None) -> str:
133
+ def add(
134
+ self,
135
+ ticket_data: dict[str, Any],
136
+ adapter: str,
137
+ operation: str,
138
+ project_dir: Optional[str] = None,
139
+ ) -> str:
127
140
  """Add item to queue.
128
141
 
129
142
  Args:
@@ -134,6 +147,7 @@ class Queue:
134
147
 
135
148
  Returns:
136
149
  Queue ID for tracking
150
+
137
151
  """
138
152
  queue_id = f"Q-{uuid.uuid4().hex[:8].upper()}"
139
153
 
@@ -143,21 +157,24 @@ class Queue:
143
157
 
144
158
  with self._lock:
145
159
  with sqlite3.connect(self.db_path) as conn:
146
- conn.execute('''
160
+ conn.execute(
161
+ """
147
162
  INSERT INTO queue (
148
163
  id, ticket_data, adapter, operation,
149
164
  status, created_at, retry_count, project_dir
150
165
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
151
- ''', (
152
- queue_id,
153
- json.dumps(ticket_data),
154
- adapter,
155
- operation,
156
- QueueStatus.PENDING.value,
157
- datetime.now().isoformat(),
158
- 0,
159
- project_dir
160
- ))
166
+ """,
167
+ (
168
+ queue_id,
169
+ json.dumps(ticket_data),
170
+ adapter,
171
+ operation,
172
+ QueueStatus.PENDING.value,
173
+ datetime.now().isoformat(),
174
+ 0,
175
+ project_dir,
176
+ ),
177
+ )
161
178
  conn.commit()
162
179
 
163
180
  return queue_id
@@ -167,25 +184,32 @@ class Queue:
167
184
 
168
185
  Returns:
169
186
  Next pending QueueItem or None if queue is empty
187
+
170
188
  """
171
189
  with self._lock:
172
190
  with sqlite3.connect(self.db_path) as conn:
173
191
  # Get next pending item ordered by creation time
174
- cursor = conn.execute('''
192
+ cursor = conn.execute(
193
+ """
175
194
  SELECT * FROM queue
176
195
  WHERE status = ?
177
196
  ORDER BY created_at
178
197
  LIMIT 1
179
- ''', (QueueStatus.PENDING.value,))
198
+ """,
199
+ (QueueStatus.PENDING.value,),
200
+ )
180
201
 
181
202
  row = cursor.fetchone()
182
203
  if row:
183
204
  # Mark as processing
184
- conn.execute('''
205
+ conn.execute(
206
+ """
185
207
  UPDATE queue
186
208
  SET status = ?
187
209
  WHERE id = ?
188
- ''', (QueueStatus.PROCESSING.value, row[0]))
210
+ """,
211
+ (QueueStatus.PROCESSING.value, row[0]),
212
+ )
189
213
  conn.commit()
190
214
 
191
215
  # Create QueueItem from row and update status
@@ -195,11 +219,13 @@ class Queue:
195
219
 
196
220
  return None
197
221
 
198
- def update_status(self,
199
- queue_id: str,
200
- status: QueueStatus,
201
- error_message: Optional[str] = None,
202
- result: Optional[Dict[str, Any]] = None):
222
+ def update_status(
223
+ self,
224
+ queue_id: str,
225
+ status: QueueStatus,
226
+ error_message: Optional[str] = None,
227
+ result: Optional[dict[str, Any]] = None,
228
+ ):
203
229
  """Update queue item status.
204
230
 
205
231
  Args:
@@ -207,25 +233,31 @@ class Queue:
207
233
  status: New status
208
234
  error_message: Error message if failed
209
235
  result: Result data if completed
236
+
210
237
  """
211
238
  with self._lock:
212
239
  with sqlite3.connect(self.db_path) as conn:
213
- processed_at = datetime.now().isoformat() if status in [
214
- QueueStatus.COMPLETED, QueueStatus.FAILED
215
- ] else None
240
+ processed_at = (
241
+ datetime.now().isoformat()
242
+ if status in [QueueStatus.COMPLETED, QueueStatus.FAILED]
243
+ else None
244
+ )
216
245
 
217
- conn.execute('''
246
+ conn.execute(
247
+ """
218
248
  UPDATE queue
219
249
  SET status = ?, processed_at = ?,
220
250
  error_message = ?, result = ?
221
251
  WHERE id = ?
222
- ''', (
223
- status.value,
224
- processed_at,
225
- error_message,
226
- json.dumps(result) if result else None,
227
- queue_id
228
- ))
252
+ """,
253
+ (
254
+ status.value,
255
+ processed_at,
256
+ error_message,
257
+ json.dumps(result) if result else None,
258
+ queue_id,
259
+ ),
260
+ )
229
261
  conn.commit()
230
262
 
231
263
  def increment_retry(self, queue_id: str) -> int:
@@ -236,16 +268,20 @@ class Queue:
236
268
 
237
269
  Returns:
238
270
  New retry count
271
+
239
272
  """
240
273
  with self._lock:
241
274
  with sqlite3.connect(self.db_path) as conn:
242
- cursor = conn.execute('''
275
+ cursor = conn.execute(
276
+ """
243
277
  UPDATE queue
244
278
  SET retry_count = retry_count + 1,
245
279
  status = ?
246
280
  WHERE id = ?
247
281
  RETURNING retry_count
248
- ''', (QueueStatus.PENDING.value, queue_id))
282
+ """,
283
+ (QueueStatus.PENDING.value, queue_id),
284
+ )
249
285
 
250
286
  result = cursor.fetchone()
251
287
  conn.commit()
@@ -259,18 +295,22 @@ class Queue:
259
295
 
260
296
  Returns:
261
297
  QueueItem or None if not found
298
+
262
299
  """
263
300
  with sqlite3.connect(self.db_path) as conn:
264
- cursor = conn.execute('''
301
+ cursor = conn.execute(
302
+ """
265
303
  SELECT * FROM queue WHERE id = ?
266
- ''', (queue_id,))
304
+ """,
305
+ (queue_id,),
306
+ )
267
307
 
268
308
  row = cursor.fetchone()
269
309
  return QueueItem.from_row(row) if row else None
270
310
 
271
- def list_items(self,
272
- status: Optional[QueueStatus] = None,
273
- limit: int = 50) -> List[QueueItem]:
311
+ def list_items(
312
+ self, status: Optional[QueueStatus] = None, limit: int = 50
313
+ ) -> list[QueueItem]:
274
314
  """List queue items.
275
315
 
276
316
  Args:
@@ -279,21 +319,28 @@ class Queue:
279
319
 
280
320
  Returns:
281
321
  List of QueueItems
322
+
282
323
  """
283
324
  with sqlite3.connect(self.db_path) as conn:
284
325
  if status:
285
- cursor = conn.execute('''
326
+ cursor = conn.execute(
327
+ """
286
328
  SELECT * FROM queue
287
329
  WHERE status = ?
288
330
  ORDER BY created_at DESC
289
331
  LIMIT ?
290
- ''', (status.value, limit))
332
+ """,
333
+ (status.value, limit),
334
+ )
291
335
  else:
292
- cursor = conn.execute('''
336
+ cursor = conn.execute(
337
+ """
293
338
  SELECT * FROM queue
294
339
  ORDER BY created_at DESC
295
340
  LIMIT ?
296
- ''', (limit,))
341
+ """,
342
+ (limit,),
343
+ )
297
344
 
298
345
  return [QueueItem.from_row(row) for row in cursor.fetchall()]
299
346
 
@@ -302,12 +349,16 @@ class Queue:
302
349
 
303
350
  Returns:
304
351
  Number of pending items
352
+
305
353
  """
306
354
  with sqlite3.connect(self.db_path) as conn:
307
- cursor = conn.execute('''
355
+ cursor = conn.execute(
356
+ """
308
357
  SELECT COUNT(*) FROM queue
309
358
  WHERE status = ?
310
- ''', (QueueStatus.PENDING.value,))
359
+ """,
360
+ (QueueStatus.PENDING.value,),
361
+ )
311
362
 
312
363
  return cursor.fetchone()[0]
313
364
 
@@ -316,20 +367,24 @@ class Queue:
316
367
 
317
368
  Args:
318
369
  days: Delete items older than this many days
370
+
319
371
  """
320
372
  cutoff_date = (datetime.now() - timedelta(days=days)).isoformat()
321
373
 
322
374
  with self._lock:
323
375
  with sqlite3.connect(self.db_path) as conn:
324
- conn.execute('''
376
+ conn.execute(
377
+ """
325
378
  DELETE FROM queue
326
379
  WHERE status IN (?, ?)
327
380
  AND processed_at < ?
328
- ''', (
329
- QueueStatus.COMPLETED.value,
330
- QueueStatus.FAILED.value,
331
- cutoff_date
332
- ))
381
+ """,
382
+ (
383
+ QueueStatus.COMPLETED.value,
384
+ QueueStatus.FAILED.value,
385
+ cutoff_date,
386
+ ),
387
+ )
333
388
  conn.commit()
334
389
 
335
390
  def reset_stuck_items(self, timeout_minutes: int = 30):
@@ -337,39 +392,46 @@ class Queue:
337
392
 
338
393
  Args:
339
394
  timeout_minutes: Consider items stuck after this many minutes
395
+
340
396
  """
341
397
  cutoff_time = (datetime.now() - timedelta(minutes=timeout_minutes)).isoformat()
342
398
 
343
399
  with self._lock:
344
400
  with sqlite3.connect(self.db_path) as conn:
345
- conn.execute('''
401
+ conn.execute(
402
+ """
346
403
  UPDATE queue
347
404
  SET status = ?, error_message = ?
348
405
  WHERE status = ?
349
406
  AND created_at < ?
350
- ''', (
351
- QueueStatus.PENDING.value,
352
- "Reset from stuck processing state",
353
- QueueStatus.PROCESSING.value,
354
- cutoff_time
355
- ))
407
+ """,
408
+ (
409
+ QueueStatus.PENDING.value,
410
+ "Reset from stuck processing state",
411
+ QueueStatus.PROCESSING.value,
412
+ cutoff_time,
413
+ ),
414
+ )
356
415
  conn.commit()
357
416
 
358
- def get_stats(self) -> Dict[str, int]:
417
+ def get_stats(self) -> dict[str, int]:
359
418
  """Get queue statistics.
360
419
 
361
420
  Returns:
362
421
  Dictionary with counts by status
422
+
363
423
  """
364
424
  with sqlite3.connect(self.db_path) as conn:
365
- cursor = conn.execute('''
425
+ cursor = conn.execute(
426
+ """
366
427
  SELECT status, COUNT(*)
367
428
  FROM queue
368
429
  GROUP BY status
369
- ''')
430
+ """
431
+ )
370
432
 
371
433
  stats = {status.value: 0 for status in QueueStatus}
372
434
  for status, count in cursor.fetchall():
373
435
  stats[status] = count
374
436
 
375
- return stats
437
+ return stats
@@ -8,8 +8,7 @@ from .worker import Worker
8
8
 
9
9
  # Set up logging
10
10
  logging.basicConfig(
11
- level=logging.INFO,
12
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
11
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
13
12
  )
14
13
  logger = logging.getLogger(__name__)
15
14
 
@@ -35,4 +34,4 @@ def main():
35
34
 
36
35
 
37
36
  if __name__ == "__main__":
38
- main()
37
+ main()