├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── dist ├── pytasking-1.0.0-py3-none-any.whl ├── pytasking-1.0.0.tar.gz ├── pytasking-1.1.0-py3-none-any.whl ├── pytasking-1.1.0.tar.gz ├── pytasking-1.2-py3-none-any.whl └── pytasking-1.2.tar.gz ├── example.py ├── pytasking ├── __init__.py ├── manager.py ├── utilities.py └── wrappers.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *__pycache__/ 2 | *build/ 3 | *.egg-info/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 1.2 4 | 5 | - Changing naming convention, moving toward 1.x convention. 6 | 7 | ### Breaking Changes 8 | 9 | - All wrapped exceptions and data structures from the `asyncio` and `multiprocessing` modules have now been namespaced into pytasking. For example; `pytasking.CancelledError` is now `pytasking.asyncio.CancelledError`. This change is so that it is more explicit and natural. 10 | 11 | ## 1.1.0 12 | 13 | - Improved documentation. 14 | - Implemented additional helper methods for the Manager class – see the documentation for details. 15 | 16 | ## 1.0.0 17 | 18 | - This is the initial release of pytasking. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 Jason Lei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Downloads](https://pepy.tech/badge/pytasking)](https://pepy.tech/project/pytasking) 2 | 3 | # Pytasking 4 | 5 | A simple library for Python 3.5+ that provides an easy interface for multitasking. 6 | 7 | ## Table of Contents 8 | 9 | - [Pytasking](#pytasking) 10 | - [Table of Contents](#table-of-contents) 11 | - [Dependencies](#dependencies) 12 | - [Installation](#installation) 13 | - [Source](#source) 14 | - [PyPi](#pypi) 15 | - [Usage](#usage) 16 | - [API](#api) 17 | - [class pytasking.Manager()](#class-pytaskingmanager) 18 | - [add_task(task, *args, **kwargs)](#addtasktask-args-kwargs) 19 | - [delete_task(t_id)](#deletetasktid) 20 | - [get_task(t_id)](#gettasktid) 21 | - [get_tasks()](#gettasks) 22 | - [add_proc(proc, *args, **kwargs)](#addprocproc-args-kwargs) 23 | - [delete_proc(p_id)](#deleteprocpid) 24 | - [get_proc(p_id)](#getprocpid) 25 | - [get_procs()](#getprocs) 26 | - [start()](#start) 27 | - [Known Issues](#known-issues) 28 | - [Recursive spawning](#recursive-spawning) 29 | - [Pipe/Queue corruption](#pipequeue-corruption) 30 | - [Changes](#changes) 31 | - [1.2](#12) 32 | - [Breaking Changes](#breaking-changes) 33 | - [1.1.0](#110) 34 | - [1.0.0](#100) 35 | - [Stargazers Over Time](#stargazers-over-time) 36 | 37 | 38 | ## Dependencies 39 | 40 | - Python 3.5+ 41 | 42 | *There are no external module dependencies outside of the standard library however, if you'd like to take advantage of `uvloop`, you can install that and the `pytasking` library will use it automatically (Only available on Linux/MacOS).* 43 | 44 | ## Installation 45 | 46 | ### Source 47 | 48 | - Include the directory `pytasking` in your project root directory. 49 | - If on Linux/MacOS; run `python -m pip install -r requirements.txt`. 50 | 51 | ### PyPi 52 | 53 | - Run `pip install pytasking`. 54 | 55 | ## Usage 56 | 57 | A basic python example: 58 | 59 | ```python 60 | #!/usr/bin/env python 61 | 62 | import pytasking 63 | import time 64 | 65 | 66 | def hello(hello_queue): 67 | while True: 68 | hello_queue.put_nowait("Hello World!") 69 | pytasking.sleep(1.5, sync=True) 70 | 71 | 72 | async def ping(): 73 | while True: 74 | try: 75 | print("Ping!") 76 | await pytasking.sleep(1.0) 77 | print("Pong!") 78 | except pytasking.asyncio.CancelledError: 79 | print("Pang!") 80 | break 81 | 82 | 83 | async def main(task_manager): 84 | hellos = 0 85 | hello_queue = pytasking.multiprocessing.Queue() 86 | hello_proc = task_manager.add_proc(hello, hello_queue) 87 | 88 | while True: 89 | try: 90 | if hellos == 5: 91 | task_manager.delete_proc(hello_proc) 92 | 93 | if not hello_queue.empty(): 94 | try: 95 | print(hello_queue.get_nowait()) 96 | hellos += 1 97 | except: 98 | pass 99 | 100 | ping_task = task_manager.add_task(ping) 101 | await pytasking.sleep(0.5) 102 | task_manager.delete_task(ping_task) 103 | except pytasking.asyncio.CancelledError: 104 | break 105 | 106 | 107 | if __name__ == "__main__": 108 | task_manager = pytasking.Manager() 109 | task_manager.add_task(main, task_manager) 110 | 111 | try: 112 | task_manager.start() 113 | except KeyboardInterrupt: 114 | pass 115 | except: 116 | raise 117 | ``` 118 | 119 | ## API 120 | 121 | ### `class pytasking.Manager()` 122 | 123 | Instances of the `Manager` class provide an asynchronous event loop to the program. Currently pytasking **only supports 1 asynchronous event loop** at any given time. 124 | 125 | Asynchronous tasks and parallel processes are spawned and managed by the `Manager` instance. 126 | 127 | #### `add_task(task, *args, **kwargs)` 128 | 129 | Create an asynchronous task from a function definition. Pass arguments and keyword arguments as you would normally. This function returns an id from the has of the task. You can use the id to retrieve and delete the task. Make sure you define your function with the following template: 130 | 131 | ```python 132 | async def asynchronous_task_definition(): # Define any arguments or keyword arguments as you normally would. 133 | # Do whatever you need to do here as you normally would. 134 | 135 | # If you want this task to run indefinitely, do this: 136 | while True: 137 | try: 138 | # Do something forever. 139 | await pytasking.sleep(1.0) 140 | except pytasking.asyncio.CancelledError: # This one is important. 141 | # Normally you catch the cancel event and do something with it, but in this case, use it to break the loop and allow the task to close the task. 142 | break 143 | except: 144 | raise 145 | ``` 146 | 147 | Tasks will start immediately and you may add a task anytime. 148 | 149 | #### `delete_task(t_id)` 150 | 151 | Given a task id, you can call to delete a task. This method calls the `cancel()` method of the coroutine, it will give the coroutine the chance to cleanup and even deny the request if caught and handled in the `pytasking.CancelledError`. 152 | 153 | #### `get_task(t_id)` 154 | 155 | If you want to retrieve the underlying coroutine, you can use this method and provide the task id to get it. 156 | 157 | #### `get_tasks()` 158 | 159 | This will return all the task ids as a list, you can use this method in conjunction with `get_task(t_id)`. 160 | 161 | #### `add_proc(proc, *args, **kwargs)` 162 | 163 | Create a parallel process from a function definition. Pass arguments and keyword arguments as you would normally. This function returns an id from the has of the process. You can use the id to retrieve and delete the process. Do note, by default the process runs sequentially. Try to follow this template: 164 | 165 | ```python 166 | def parallel_process(): # Define any arguments or keyword arguments as you normally would. 167 | # Do whatever you need to do here as you normally would. 168 | 169 | # If you want this task to run indefinitely, do this: 170 | while True: 171 | try: 172 | # Do something forever. 173 | pytasking.sleep(1.0, sync=True) 174 | except: 175 | raise 176 | ``` 177 | 178 | #### `delete_proc(p_id)` 179 | 180 | Given a process id, you can call to delete a process. This method calls `terminate()` and `join()` to attempt to cleanly close the process. Closing the process while it is accessing a Pipe or Queue, may corrupt the resource. 181 | 182 | #### `get_proc(p_id)` 183 | 184 | If you want to retrieve the underlying process, you can use this method and provide the process id to get it. 185 | 186 | #### `get_procs()` 187 | 188 | This will return all the process ids as a list, you can use this method in conjunction with `get_process(p_id)`. 189 | 190 | #### `start()` 191 | 192 | This begins the `Manager` instance and starts all added tasks and processes. 193 | 194 | ## Known Issues 195 | 196 | ### Recursive spawning 197 | 198 | There maybe situations where you cannot spawn a task in a task, process in a process, task in a process, or a process in a task. I'll need to investigate further. 199 | 200 | ### Pipe/Queue corruption 201 | 202 | If you decide to delete a process be wary, if the process was in the middle of accessing a Queue or Pipe, that Queue or Pipe will be liable to corruption and will not be usable again. 203 | 204 | ## Changes 205 | 206 | ### 1.2 207 | 208 | - Changing naming convention, moving toward 1.x convention. 209 | 210 | #### Breaking Changes 211 | 212 | - All wrapped exceptions and data structures from the `asyncio` and `multiprocessing` modules have now been namespaced into pytasking. For example; `pytasking.CancelledError` is now `pytasking.asyncio.CancelledError`. This change is so that it is more explicit and natural. 213 | 214 | ### 1.1.0 215 | 216 | - Improved documentation. 217 | - Implemented additional helper methods for the Manager class – see the documentation for details. 218 | 219 | ### 1.0.0 220 | 221 | - This is the initial release of pytasking. 222 | 223 | ## Stargazers Over Time 224 | 225 | [![Stargazers over time](https://starchart.cc/TokenChingy/pytasking.svg)](https://starchart.cc/TokenChingy/pytasking) 226 | -------------------------------------------------------------------------------- /dist/pytasking-1.0.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TokenChingy/pytasking/5aabf03c89294c6430d74533bbcc8bd8cba02b1c/dist/pytasking-1.0.0-py3-none-any.whl -------------------------------------------------------------------------------- /dist/pytasking-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TokenChingy/pytasking/5aabf03c89294c6430d74533bbcc8bd8cba02b1c/dist/pytasking-1.0.0.tar.gz -------------------------------------------------------------------------------- /dist/pytasking-1.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TokenChingy/pytasking/5aabf03c89294c6430d74533bbcc8bd8cba02b1c/dist/pytasking-1.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /dist/pytasking-1.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TokenChingy/pytasking/5aabf03c89294c6430d74533bbcc8bd8cba02b1c/dist/pytasking-1.1.0.tar.gz -------------------------------------------------------------------------------- /dist/pytasking-1.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TokenChingy/pytasking/5aabf03c89294c6430d74533bbcc8bd8cba02b1c/dist/pytasking-1.2-py3-none-any.whl -------------------------------------------------------------------------------- /dist/pytasking-1.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TokenChingy/pytasking/5aabf03c89294c6430d74533bbcc8bd8cba02b1c/dist/pytasking-1.2.tar.gz -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pytasking 4 | import time 5 | 6 | 7 | def hello(hello_queue): 8 | while True: 9 | hello_queue.put_nowait("Hello World!") 10 | pytasking.sleep(1.5, sync=True) 11 | 12 | 13 | async def ping(): 14 | while True: 15 | try: 16 | print("Ping!") 17 | await pytasking.sleep(1.0) 18 | print("Pong!") 19 | except pytasking.asyncio.CancelledError: 20 | print("Pang!") 21 | break 22 | 23 | 24 | async def main(task_manager): 25 | hellos = 0 26 | hello_queue = pytasking.multiprocessing.Queue() 27 | hello_proc = task_manager.add_proc(hello, hello_queue) 28 | 29 | while True: 30 | try: 31 | if hellos == 5: 32 | task_manager.delete_proc(hello_proc) 33 | 34 | if not hello_queue.empty(): 35 | try: 36 | print(hello_queue.get_nowait()) 37 | hellos += 1 38 | except: 39 | pass 40 | 41 | ping_task = task_manager.add_task(ping) 42 | await pytasking.sleep(0.5) 43 | task_manager.delete_task(ping_task) 44 | except pytasking.asyncio.CancelledError: 45 | break 46 | 47 | 48 | if __name__ == "__main__": 49 | task_manager = pytasking.Manager() 50 | task_manager.add_task(main, task_manager) 51 | 52 | try: 53 | task_manager.start() 54 | except KeyboardInterrupt: 55 | pass 56 | except: 57 | raise 58 | -------------------------------------------------------------------------------- /pytasking/__init__.py: -------------------------------------------------------------------------------- 1 | from pytasking.wrappers import * 2 | from pytasking.utilities import * 3 | from pytasking.manager import * 4 | name = "pytasking" 5 | -------------------------------------------------------------------------------- /pytasking/manager.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import multiprocessing 3 | 4 | 5 | class Manager: 6 | def __init__(self, *args, **kwargs): 7 | super().__init__(*args, **kwargs) 8 | 9 | try: 10 | import uvloop 11 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 12 | except: 13 | pass 14 | 15 | self.__started = False 16 | self.__tasks = {} 17 | self.__procs = {} 18 | self.loop = asyncio.get_event_loop() 19 | 20 | async def __tasks__(self): 21 | await asyncio.gather(*self.__tasks.values()) 22 | 23 | def __procs__(self): 24 | for p in self.__procs.values(): 25 | p.start() 26 | 27 | self.__started = True 28 | 29 | def add_task(self, task, *args, **kwargs): 30 | t = asyncio.ensure_future(task(*args, **kwargs)) 31 | t_id = hash(t) 32 | self.__tasks[t_id] = t 33 | return t_id 34 | 35 | def delete_task(self, t_id): 36 | self.__tasks[t_id].cancel() 37 | del self.__tasks[t_id] 38 | 39 | def get_task(self, t_id): 40 | return self.__tasks[t_id] 41 | 42 | def get_tasks(self): 43 | return [*self.__tasks.keys()] 44 | 45 | def add_proc(self, proc, *args, **kwargs): 46 | p = multiprocessing.Process( 47 | target=proc, 48 | args=args, 49 | kwargs=kwargs 50 | ) 51 | p.daemon = True 52 | p_id = hash(p) 53 | self.__procs[p_id] = p 54 | 55 | if self.__started: 56 | p.start() 57 | 58 | return p_id 59 | 60 | def delete_proc(self, p_id): 61 | self.__procs[p_id].terminate() 62 | self.__procs[p_id].join() 63 | 64 | def get_proc(self, p_id): 65 | return self.__procs[p_id] 66 | 67 | def get_procs(self): 68 | return [*self.__procs.keys()] 69 | 70 | def start(self): 71 | self.__procs__() 72 | self.loop.run_until_complete(asyncio.ensure_future(self.__tasks__())) 73 | self.loop.close() 74 | -------------------------------------------------------------------------------- /pytasking/utilities.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | 5 | def sleep(seconds, sync=False): 6 | if sync: 7 | return time.sleep(seconds) 8 | else: 9 | return asyncio.sleep(seconds) 10 | -------------------------------------------------------------------------------- /pytasking/wrappers.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import multiprocessing 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | uvloop>=0.13.0; sys_platform == "darwin" or sys_platform == "linux" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="pytasking", 8 | version="1.2", 9 | author="Jason Zi Feng Lei", 10 | author_email="TokenChingy@gmail.com", 11 | description="A multitasking library for Python 3.5+", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/TokenChingy/pytasking", 15 | packages=setuptools.find_packages(), 16 | classifiers=[ 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ], 21 | python_requires='>=3.5', 22 | ) 23 | --------------------------------------------------------------------------------