kew 0.1.0__tar.gz

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.
kew-0.1.0/.gitignore ADDED
@@ -0,0 +1,31 @@
1
+ # Optional: create a .gitignore (save as kew/.gitignore)
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+ .env
24
+ .venv
25
+ env/
26
+ venv/
27
+ ENV/
28
+ env.bak/
29
+ venv.bak/
30
+ .idea/
31
+ .vscode/
kew-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.3
2
+ Name: kew
3
+ Version: 0.1.0
4
+ Summary: A flexible async task queue manager for Python applications
5
+ Project-URL: Homepage, https://github.com/justrach/kew
6
+ Project-URL: Bug Tracker, https://github.com/justrach/kew/issues
7
+ Author-email: Rach Pradhan <rach@rachit.ai>
8
+ Keywords: async,concurrent,manager,queue,task
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Framework :: AsyncIO
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.7
17
+ Requires-Dist: typing; python_version < '3.5'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Kew Task Queue Manager
21
+
22
+ A flexible and robust asynchronous task queue manager for Python applications.
23
+
24
+ ## Features
25
+
26
+ - Asynchronous task execution
27
+ - Configurable worker pool
28
+ - Task status tracking and monitoring
29
+ - Automatic cleanup of completed tasks
30
+ - Thread-safe operations
31
+ - Comprehensive logging
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install kew
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```python
42
+ import asyncio
43
+ from kew import TaskQueueManager
44
+
45
+ async def example_task(x: int):
46
+ await asyncio.sleep(1)
47
+ return x * 2
48
+
49
+ async def main():
50
+ # Initialize the task queue manager
51
+ manager = TaskQueueManager(max_workers=2)
52
+
53
+ # Submit a task
54
+ task_info = await manager.submit_task(
55
+ task_id="task1",
56
+ task_type="multiplication",
57
+ task_func=example_task,
58
+ x=5
59
+ )
60
+
61
+ # Wait for result
62
+ await asyncio.sleep(2)
63
+ status = manager.get_task_status("task1")
64
+ print(f"Result: {status.result}")
65
+
66
+ # Cleanup
67
+ await manager.shutdown()
68
+
69
+ if __name__ == "__main__":
70
+ asyncio.run(main())
71
+ ```
72
+
73
+ ## Advanced Usage
74
+
75
+ ### Concurrent Task Execution
76
+
77
+ ```python
78
+ async def main():
79
+ manager = TaskQueueManager(max_workers=2)
80
+
81
+ # Submit multiple tasks
82
+ tasks = []
83
+ for i in range(5):
84
+ task_info = await manager.submit_task(
85
+ task_id=f"task{i}",
86
+ task_type="example",
87
+ task_func=example_task,
88
+ x=i
89
+ )
90
+ tasks.append(task_info)
91
+
92
+ # Wait for all tasks to complete
93
+ await manager.wait_for_all_tasks()
94
+ ```
95
+
96
+ ### Task Status Monitoring
97
+
98
+ ```python
99
+ status = manager.get_task_status("task1")
100
+ print(f"Status: {status.status}")
101
+ print(f"Result: {status.result}")
102
+ print(f"Error: {status.error}")
103
+ ```
104
+
105
+ ## API Reference
106
+
107
+ ### TaskQueueManager
108
+
109
+ - `__init__(max_workers=2, queue_size=1000, task_timeout=3600)`
110
+ - `async submit_task(task_id, task_type, task_func, *args, **kwargs)`
111
+ - `get_task_status(task_id)`
112
+ - `async wait_for_task(task_id, timeout=None)`
113
+ - `async wait_for_all_tasks(timeout=None)`
114
+ - `cleanup_old_tasks(max_age_hours=24)`
115
+ - `async shutdown()`
116
+
117
+ ### TaskStatus
118
+
119
+ Enum with states:
120
+ - `QUEUED`
121
+ - `PROCESSING`
122
+ - `COMPLETED`
123
+ - `FAILED`
124
+
125
+ ## Contributing
126
+
127
+ Contributions are welcome! Please feel free to submit a Pull Request.
128
+
129
+ ## License
130
+
131
+ This project is licensed under the MIT License - see the LICENSE file for details.
kew-0.1.0/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Kew Task Queue Manager
2
+
3
+ A flexible and robust asynchronous task queue manager for Python applications.
4
+
5
+ ## Features
6
+
7
+ - Asynchronous task execution
8
+ - Configurable worker pool
9
+ - Task status tracking and monitoring
10
+ - Automatic cleanup of completed tasks
11
+ - Thread-safe operations
12
+ - Comprehensive logging
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install kew
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ import asyncio
24
+ from kew import TaskQueueManager
25
+
26
+ async def example_task(x: int):
27
+ await asyncio.sleep(1)
28
+ return x * 2
29
+
30
+ async def main():
31
+ # Initialize the task queue manager
32
+ manager = TaskQueueManager(max_workers=2)
33
+
34
+ # Submit a task
35
+ task_info = await manager.submit_task(
36
+ task_id="task1",
37
+ task_type="multiplication",
38
+ task_func=example_task,
39
+ x=5
40
+ )
41
+
42
+ # Wait for result
43
+ await asyncio.sleep(2)
44
+ status = manager.get_task_status("task1")
45
+ print(f"Result: {status.result}")
46
+
47
+ # Cleanup
48
+ await manager.shutdown()
49
+
50
+ if __name__ == "__main__":
51
+ asyncio.run(main())
52
+ ```
53
+
54
+ ## Advanced Usage
55
+
56
+ ### Concurrent Task Execution
57
+
58
+ ```python
59
+ async def main():
60
+ manager = TaskQueueManager(max_workers=2)
61
+
62
+ # Submit multiple tasks
63
+ tasks = []
64
+ for i in range(5):
65
+ task_info = await manager.submit_task(
66
+ task_id=f"task{i}",
67
+ task_type="example",
68
+ task_func=example_task,
69
+ x=i
70
+ )
71
+ tasks.append(task_info)
72
+
73
+ # Wait for all tasks to complete
74
+ await manager.wait_for_all_tasks()
75
+ ```
76
+
77
+ ### Task Status Monitoring
78
+
79
+ ```python
80
+ status = manager.get_task_status("task1")
81
+ print(f"Status: {status.status}")
82
+ print(f"Result: {status.result}")
83
+ print(f"Error: {status.error}")
84
+ ```
85
+
86
+ ## API Reference
87
+
88
+ ### TaskQueueManager
89
+
90
+ - `__init__(max_workers=2, queue_size=1000, task_timeout=3600)`
91
+ - `async submit_task(task_id, task_type, task_func, *args, **kwargs)`
92
+ - `get_task_status(task_id)`
93
+ - `async wait_for_task(task_id, timeout=None)`
94
+ - `async wait_for_all_tasks(timeout=None)`
95
+ - `cleanup_old_tasks(max_age_hours=24)`
96
+ - `async shutdown()`
97
+
98
+ ### TaskStatus
99
+
100
+ Enum with states:
101
+ - `QUEUED`
102
+ - `PROCESSING`
103
+ - `COMPLETED`
104
+ - `FAILED`
105
+
106
+ ## Contributing
107
+
108
+ Contributions are welcome! Please feel free to submit a Pull Request.
109
+
110
+ ## License
111
+
112
+ This project is licensed under the MIT License - see the LICENSE file for details.
kew-0.1.0/example.py ADDED
@@ -0,0 +1,36 @@
1
+ # example.py
2
+ import asyncio
3
+ from kew import TaskQueueManager
4
+
5
+ async def example_task(x: int):
6
+ await asyncio.sleep(1)
7
+ return x * 2
8
+
9
+ async def main():
10
+ # Initialize the task queue manager
11
+ manager = TaskQueueManager(max_workers=2)
12
+
13
+ # Submit a task
14
+ task_info = await manager.submit_task(
15
+ task_id="task1",
16
+ task_type="multiplication",
17
+ task_func=example_task,
18
+ x=5
19
+ )
20
+
21
+ # Get task status
22
+ status = manager.get_task_status("task1")
23
+ print(f"Task status: {status.status}")
24
+
25
+ # Wait a bit to see the result
26
+ await asyncio.sleep(2)
27
+ status = manager.get_task_status("task1")
28
+ print(f"Final status: {status.status}")
29
+ print(f"Result: {status.result}")
30
+
31
+ # Cleanup and shutdown - now properly awaited
32
+ manager.cleanup_old_tasks()
33
+ await manager.shutdown()
34
+
35
+ if __name__ == "__main__":
36
+ asyncio.run(main())
@@ -0,0 +1,5 @@
1
+ from .manager import TaskQueueManager
2
+ from .models import TaskStatus, TaskInfo
3
+
4
+ __version__ = "0.1.0"
5
+ __all__ = ["TaskQueueManager", "TaskStatus", "TaskInfo"]
@@ -0,0 +1,11 @@
1
+ class TaskQueueError(Exception):
2
+ """Base exception for task queue errors"""
3
+ pass
4
+
5
+ class TaskAlreadyExistsError(TaskQueueError):
6
+ """Raised when attempting to add a task with an ID that already exists"""
7
+ pass
8
+
9
+ class TaskNotFoundError(TaskQueueError):
10
+ """Raised when attempting to access a non-existent task"""
11
+ pass
@@ -0,0 +1,150 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from queue import Queue
3
+ import threading
4
+ from typing import Optional, Dict, Any, Callable
5
+ from datetime import datetime
6
+ import logging
7
+ import asyncio
8
+ from .models import TaskStatus, TaskInfo
9
+ from .exceptions import TaskAlreadyExistsError, TaskNotFoundError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class TaskQueueManager:
14
+ def __init__(
15
+ self,
16
+ max_workers: int = 2,
17
+ queue_size: int = 1000,
18
+ task_timeout: int = 3600
19
+ ):
20
+ self.executor = ThreadPoolExecutor(max_workers=max_workers)
21
+ self.queue = Queue(maxsize=queue_size)
22
+ self.task_timeout = task_timeout
23
+ self.tasks: Dict[str, TaskInfo] = {}
24
+ self._lock = threading.Lock()
25
+ self._shutdown = False
26
+ self._setup_logging()
27
+
28
+ def _setup_logging(self):
29
+ handler = logging.StreamHandler()
30
+ formatter = logging.Formatter(
31
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
32
+ )
33
+ handler.setFormatter(formatter)
34
+ logger.addHandler(handler)
35
+ logger.setLevel(logging.INFO)
36
+
37
+ async def submit_task(
38
+ self,
39
+ task_id: str,
40
+ task_type: str,
41
+ task_func: Callable,
42
+ *args,
43
+ **kwargs
44
+ ) -> TaskInfo:
45
+ with self._lock:
46
+ if task_id in self.tasks:
47
+ raise TaskAlreadyExistsError(
48
+ f"Task with ID {task_id} already exists"
49
+ )
50
+
51
+ task_info = TaskInfo(task_id, task_type)
52
+ self.tasks[task_id] = task_info
53
+ logger.info(f"Task {task_id} ({task_type}) submitted")
54
+
55
+ # Create task coroutine
56
+ task = asyncio.create_task(self._execute_task(
57
+ task_id,
58
+ task_func,
59
+ *args,
60
+ **kwargs
61
+ ))
62
+
63
+ # Store the task for later cleanup
64
+ task_info._task = task
65
+
66
+ return task_info
67
+
68
+ async def _execute_task(
69
+ self,
70
+ task_id: str,
71
+ task_func: Callable,
72
+ *args,
73
+ **kwargs
74
+ ):
75
+ try:
76
+ with self._lock:
77
+ task_info = self.tasks[task_id]
78
+ task_info.status = TaskStatus.PROCESSING
79
+ task_info.started_time = datetime.now()
80
+ logger.info(f"Starting execution of task {task_id}")
81
+
82
+ # Execute the task function
83
+ result = await task_func(*args, **kwargs)
84
+
85
+ with self._lock:
86
+ task_info.result = result
87
+ task_info.status = TaskStatus.COMPLETED
88
+ task_info.completed_time = datetime.now()
89
+ logger.info(f"Task {task_id} completed successfully with result: {result}")
90
+
91
+ return result
92
+
93
+ except Exception as e:
94
+ logger.error(f"Error executing task {task_id}: {str(e)}")
95
+ with self._lock:
96
+ task_info = self.tasks[task_id]
97
+ task_info.status = TaskStatus.FAILED
98
+ task_info.error = str(e)
99
+ task_info.completed_time = datetime.now()
100
+ raise
101
+
102
+ async def wait_for_task(self, task_id: str, timeout: Optional[float] = None) -> TaskInfo:
103
+ """Wait for a specific task to complete"""
104
+ task_info = self.get_task_status(task_id)
105
+ if hasattr(task_info, '_task'):
106
+ try:
107
+ await asyncio.wait_for(task_info._task, timeout=timeout)
108
+ except asyncio.TimeoutError:
109
+ logger.warning(f"Task {task_id} timed out after {timeout} seconds")
110
+ raise
111
+ return task_info
112
+
113
+ async def wait_for_all_tasks(self, timeout: Optional[float] = None):
114
+ """Wait for all tasks to complete"""
115
+ tasks = [
116
+ task_info._task for task_info in self.tasks.values()
117
+ if hasattr(task_info, '_task') and not task_info._task.done()
118
+ ]
119
+ if tasks:
120
+ await asyncio.wait(tasks, timeout=timeout)
121
+
122
+ def get_task_status(self, task_id: str) -> TaskInfo:
123
+ with self._lock:
124
+ task_info = self.tasks.get(task_id)
125
+ if not task_info:
126
+ raise TaskNotFoundError(f"Task {task_id} not found")
127
+ return task_info
128
+
129
+ def get_queue_length(self) -> int:
130
+ return self.queue.qsize()
131
+
132
+ def cleanup_old_tasks(self, max_age_hours: int = 24):
133
+ current_time = datetime.now()
134
+ cleaned_count = 0
135
+ with self._lock:
136
+ for task_id, task_info in list(self.tasks.items()):
137
+ if task_info.completed_time:
138
+ age = current_time - task_info.completed_time
139
+ if age.total_seconds() > max_age_hours * 3600:
140
+ del self.tasks[task_id]
141
+ cleaned_count += 1
142
+
143
+ logger.info(f"Cleaned up {cleaned_count} old tasks")
144
+
145
+ async def shutdown(self):
146
+ logger.info("Shutting down TaskQueueManager")
147
+ self._shutdown = True
148
+ # Wait for pending tasks
149
+ await self.wait_for_all_tasks(timeout=5.0)
150
+ self.executor.shutdown(wait=True)
@@ -0,0 +1,35 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Optional, TypeVar, Generic
4
+
5
+ T = TypeVar('T') # Generic type for task result
6
+
7
+ class TaskStatus(Enum):
8
+ QUEUED = "queued"
9
+ PROCESSING = "processing"
10
+ COMPLETED = "completed"
11
+ FAILED = "failed"
12
+
13
+ class TaskInfo(Generic[T]):
14
+ def __init__(self, task_id: str, task_type: str):
15
+ self.task_id = task_id
16
+ self.task_type = task_type
17
+ self.status = TaskStatus.QUEUED
18
+ self.queued_time = datetime.now()
19
+ self.started_time: Optional[datetime] = None
20
+ self.completed_time: Optional[datetime] = None
21
+ self.result: Optional[T] = None
22
+ self.error: Optional[str] = None
23
+
24
+ def to_dict(self):
25
+ """Convert TaskInfo to dictionary representation"""
26
+ return {
27
+ "task_id": self.task_id,
28
+ "task_type": self.task_type,
29
+ "status": self.status.value,
30
+ "queued_time": self.queued_time.isoformat(),
31
+ "started_time": self.started_time.isoformat() if self.started_time else None,
32
+ "completed_time": self.completed_time.isoformat() if self.completed_time else None,
33
+ "result": self.result,
34
+ "error": self.error
35
+ }
@@ -0,0 +1,35 @@
1
+ # First, let's create pyproject.toml (save as kew/pyproject.toml)
2
+
3
+ [build-system]
4
+ requires = ["hatchling"]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "kew"
9
+ version = "0.1.0"
10
+ authors = [
11
+ { name="Rach Pradhan", email="rach@rachit.ai" },
12
+ ]
13
+ description = "A flexible async task queue manager for Python applications"
14
+ readme = "README.md"
15
+ requires-python = ">=3.7"
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "Framework :: AsyncIO",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ keywords = ["async", "queue", "task", "manager", "concurrent"]
26
+ dependencies = [
27
+ "typing;python_version<'3.5'",
28
+ ]
29
+
30
+ [project.urls]
31
+ "Homepage" = "https://github.com/justrach/kew"
32
+ "Bug Tracker" = "https://github.com/justrach/kew/issues"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["kew"]
kew-0.1.0/setup.py ADDED
@@ -0,0 +1,21 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="kew",
5
+ version="0.1.0",
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ "typing;python_version<'3.5'",
9
+ ],
10
+ author="Your Name",
11
+ author_email="your.email@example.com",
12
+ description="A flexible async task queue manager for Python applications",
13
+ long_description=open("README.md").read(),
14
+ long_description_content_type="text/markdown",
15
+ classifiers=[
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ],
20
+ python_requires=">=3.7",
21
+ )
@@ -0,0 +1,60 @@
1
+ # test_multiple.py
2
+ import asyncio
3
+ from kew import TaskQueueManager, TaskStatus
4
+ import random
5
+
6
+ async def long_task(task_num: int, sleep_time: int) -> dict:
7
+ """Simulate a long-running task"""
8
+ print(f"Starting task {task_num} (will take {sleep_time} seconds)")
9
+ await asyncio.sleep(sleep_time)
10
+ result = sleep_time * 2
11
+ print(f"Task {task_num} completed with result: {result}")
12
+ return {"task_num": task_num, "result": result}
13
+
14
+ async def main():
15
+ # Initialize manager with 2 workers (so only 2 tasks can run simultaneously)
16
+ manager = TaskQueueManager(max_workers=2)
17
+
18
+ # Submit 5 tasks with different durations
19
+ tasks = []
20
+ for i in range(5):
21
+ sleep_time = random.randint(3, 7) # Random duration between 3-7 seconds
22
+ task_info = await manager.submit_task(
23
+ task_id=f"task{i+1}",
24
+ task_type="long_calculation",
25
+ task_func=long_task,
26
+ task_num=i+1,
27
+ sleep_time=sleep_time
28
+ )
29
+ tasks.append(task_info)
30
+ print(f"Submitted task {i+1}")
31
+
32
+ # Monitor task progress
33
+ while True:
34
+ all_completed = True
35
+ print("\nCurrent status:")
36
+ for task in tasks:
37
+ status = manager.get_task_status(task.task_id)
38
+ print(f"{task.task_id}: {status.status.value} - Result: {status.result}")
39
+ if status.status not in (TaskStatus.COMPLETED, TaskStatus.FAILED):
40
+ all_completed = False
41
+
42
+ if all_completed:
43
+ break
44
+
45
+ await asyncio.sleep(1) # Wait a second before checking again
46
+
47
+ # Final results
48
+ print("\nFinal results:")
49
+ for task in tasks:
50
+ status = manager.get_task_status(task.task_id)
51
+ print(f"{task.task_id}: {status.result}")
52
+
53
+ # Properly await shutdown
54
+ await manager.shutdown()
55
+
56
+ if __name__ == "__main__":
57
+ try:
58
+ asyncio.run(main())
59
+ except KeyboardInterrupt:
60
+ print("\nShutting down gracefully...")