flexexecutor 0.0.8__tar.gz → 0.0.10__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.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.1
2
+ Name: flexexecutor
3
+ Version: 0.0.10
4
+ Summary: Executors for both async and sync functions and automatically scales the number of workers
5
+ Home-page: https://github.com/leavers/flexexecutor
6
+ Author: leavers
7
+ Author-Email: Leavers <leavers930@gmail.com>
8
+ License: MIT
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: Utilities
22
+ Project-URL: Homepage, https://github.com/leavers/flexexecutor
23
+ Project-URL: Documentation, https://github.com/leavers/flexexecutor/blob/main/README.md
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Flexexecutor
28
+
29
+ [![Testing](https://github.com/leavers/flexexecutor/workflows/Test%20Suite/badge.svg)](https://github.com/leavers/flexexecutor/actions)
30
+ [![Package version](https://img.shields.io/pypi/v/flexexecutor.svg)](https://pypi.org/project/flexexecutor/)
31
+ [![Python](https://img.shields.io/pypi/pyversions/flexexecutor.svg)](https://pypi.org/project/flexexecutor/)
32
+
33
+ Flexexecutor provides executors that can automatically scale the number of workers up
34
+ and down.
35
+
36
+ ## Overview
37
+
38
+ Flexexecutor implements several subclasses of `concurrent.futures.Executor`.
39
+ Like the built-in `ThreadPoolExecutor` and `ProcessPoolExecutor`,
40
+ they create multiple workers to execute tasks concurrently.
41
+ Additionally, it can shut down idle workers to save resources. These subclasses are:
42
+
43
+ - `ThreadPoolExecutor` - thread concurrency, same name as the built-in class and can
44
+ be a in-place replacement.
45
+ - `AsyncPoolExecutor` - coroutine concurrency. Note that in flexexecutor's
46
+ implementation, all coroutines are executed in a dedicated worker thread.
47
+ - `ProcessPoolExecutor` - flexexecutor directly imports the built-in implementation as it
48
+ already has scaling capabilities.
49
+
50
+ ## Features
51
+
52
+ - Supports various concurrency modes: threads, processes and coroutines.
53
+ - Automatically shut down idle workers to save resources.
54
+ - Single file design, keeps the code clean and easy for hackers to directly take away
55
+ and add more features.
56
+
57
+ ## Installation
58
+
59
+ Flexexecutor is available on [PyPI](https://pypi.org/project/flexexecutor/):
60
+
61
+ ```shell
62
+ pip install flexexecutor
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ### ThreadPoolExecutor
68
+
69
+ ```python
70
+ from flexexecutor import ThreadPoolExecutor
71
+
72
+
73
+ def task(i):
74
+ import time
75
+
76
+ print(f"task {i} started")
77
+ time.sleep(1)
78
+
79
+ if __name__ == "__main__":
80
+ with ThreadPoolExecutor(
81
+ # 1024 is the default value of max_workers, since workers are closed if they are
82
+ # idle for some time, you can set it to a big value to get better short-term
83
+ # performance.
84
+ max_workers=1024,
85
+ # Timeout for idle workers.
86
+ idle_timeout=60.0,
87
+ # These parameters are given for compatibility with the built-in
88
+ # `ThreadPoolExecutor`, I don't use them very often, do you?
89
+ thread_name_prefix="Task",
90
+ initializer=None,
91
+ initargs=(),
92
+ ) as executor:
93
+ for i in range(1024):
94
+ executor.submit(task, i)
95
+ ```
96
+
97
+ ### AsyncPoolExecutor
98
+
99
+ ```python
100
+ from flexexecutor import AsyncPoolExecutor
101
+
102
+
103
+ async def task(i):
104
+ import asyncio
105
+
106
+ print(f"task {i} started")
107
+ await asyncio.sleep(1)
108
+
109
+ if __name__ == "__main__":
110
+ # AsyncPoolExecutor behaves just like ThreadPoolExecutor except it only accepts
111
+ # coroutine functions.
112
+ with AsyncPoolExecutor(
113
+ # Default value of max_workers is huge, if you don't like it, set it smaller.
114
+ max_workers=1024,
115
+ # Idle timeout for the working thread.
116
+ idle_timeout=60.0,
117
+ # These parameters are given for compatibility with the built-in
118
+ # `ThreadPoolExecutor`, I don't use them very often, do you?
119
+ thread_name_prefix="Task",
120
+ initializer=None,
121
+ initargs=(),
122
+ ) as executor:
123
+ for i in range(1024):
124
+ executor.submit(task, i)
125
+ ```
126
+
127
+ ### ProcessPoolExecutor
128
+
129
+ `ProcessPoolExecutor` in flexexecutor is just the same as the built-in
130
+ `concurrent.futures.ProcessPoolExecutor`, we just import it directly for convenience.
@@ -0,0 +1,104 @@
1
+ # Flexexecutor
2
+
3
+ [![Testing](https://github.com/leavers/flexexecutor/workflows/Test%20Suite/badge.svg)](https://github.com/leavers/flexexecutor/actions)
4
+ [![Package version](https://img.shields.io/pypi/v/flexexecutor.svg)](https://pypi.org/project/flexexecutor/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/flexexecutor.svg)](https://pypi.org/project/flexexecutor/)
6
+
7
+ Flexexecutor provides executors that can automatically scale the number of workers up
8
+ and down.
9
+
10
+ ## Overview
11
+
12
+ Flexexecutor implements several subclasses of `concurrent.futures.Executor`.
13
+ Like the built-in `ThreadPoolExecutor` and `ProcessPoolExecutor`,
14
+ they create multiple workers to execute tasks concurrently.
15
+ Additionally, it can shut down idle workers to save resources. These subclasses are:
16
+
17
+ - `ThreadPoolExecutor` - thread concurrency, same name as the built-in class and can
18
+ be a in-place replacement.
19
+ - `AsyncPoolExecutor` - coroutine concurrency. Note that in flexexecutor's
20
+ implementation, all coroutines are executed in a dedicated worker thread.
21
+ - `ProcessPoolExecutor` - flexexecutor directly imports the built-in implementation as it
22
+ already has scaling capabilities.
23
+
24
+ ## Features
25
+
26
+ - Supports various concurrency modes: threads, processes and coroutines.
27
+ - Automatically shut down idle workers to save resources.
28
+ - Single file design, keeps the code clean and easy for hackers to directly take away
29
+ and add more features.
30
+
31
+ ## Installation
32
+
33
+ Flexexecutor is available on [PyPI](https://pypi.org/project/flexexecutor/):
34
+
35
+ ```shell
36
+ pip install flexexecutor
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### ThreadPoolExecutor
42
+
43
+ ```python
44
+ from flexexecutor import ThreadPoolExecutor
45
+
46
+
47
+ def task(i):
48
+ import time
49
+
50
+ print(f"task {i} started")
51
+ time.sleep(1)
52
+
53
+ if __name__ == "__main__":
54
+ with ThreadPoolExecutor(
55
+ # 1024 is the default value of max_workers, since workers are closed if they are
56
+ # idle for some time, you can set it to a big value to get better short-term
57
+ # performance.
58
+ max_workers=1024,
59
+ # Timeout for idle workers.
60
+ idle_timeout=60.0,
61
+ # These parameters are given for compatibility with the built-in
62
+ # `ThreadPoolExecutor`, I don't use them very often, do you?
63
+ thread_name_prefix="Task",
64
+ initializer=None,
65
+ initargs=(),
66
+ ) as executor:
67
+ for i in range(1024):
68
+ executor.submit(task, i)
69
+ ```
70
+
71
+ ### AsyncPoolExecutor
72
+
73
+ ```python
74
+ from flexexecutor import AsyncPoolExecutor
75
+
76
+
77
+ async def task(i):
78
+ import asyncio
79
+
80
+ print(f"task {i} started")
81
+ await asyncio.sleep(1)
82
+
83
+ if __name__ == "__main__":
84
+ # AsyncPoolExecutor behaves just like ThreadPoolExecutor except it only accepts
85
+ # coroutine functions.
86
+ with AsyncPoolExecutor(
87
+ # Default value of max_workers is huge, if you don't like it, set it smaller.
88
+ max_workers=1024,
89
+ # Idle timeout for the working thread.
90
+ idle_timeout=60.0,
91
+ # These parameters are given for compatibility with the built-in
92
+ # `ThreadPoolExecutor`, I don't use them very often, do you?
93
+ thread_name_prefix="Task",
94
+ initializer=None,
95
+ initargs=(),
96
+ ) as executor:
97
+ for i in range(1024):
98
+ executor.submit(task, i)
99
+ ```
100
+
101
+ ### ProcessPoolExecutor
102
+
103
+ `ProcessPoolExecutor` in flexexecutor is just the same as the built-in
104
+ `concurrent.futures.ProcessPoolExecutor`, we just import it directly for convenience.
@@ -1,8 +1,17 @@
1
+ """
2
+ Flexexecutor provides executors that can automatically scale the number of
3
+ workers up and down.
4
+
5
+ Copyright (c) 2020-2024, Leavers.
6
+ License: MIT
7
+ """
8
+
1
9
  import asyncio
2
10
  import atexit
3
11
  import itertools
4
- from concurrent.futures import ProcessPoolExecutor, _base
5
- from concurrent.futures import thread as _thread
12
+ from concurrent.futures import Future, ProcessPoolExecutor, _base
13
+ from concurrent.futures.thread import BrokenThreadPool, _WorkItem
14
+ from concurrent.futures.thread import ThreadPoolExecutor as _ThreadPoolExecutor
6
15
  from inspect import iscoroutinefunction
7
16
  from queue import Empty
8
17
  from threading import Event, Lock, Thread
@@ -12,11 +21,13 @@ from weakref import WeakKeyDictionary, ref
12
21
  __all__ = (
13
22
  "__version__",
14
23
  "AsyncPoolExecutor",
24
+ "BrokenThreadPool",
25
+ "Future",
15
26
  "ProcessPoolExecutor",
16
27
  "ThreadPoolExecutor",
17
28
  )
18
29
 
19
- __version__ = "0.0.8"
30
+ __version__ = "0.0.10"
20
31
 
21
32
  _threads_queues = WeakKeyDictionary() # type: ignore
22
33
  _shutdown = False
@@ -44,17 +55,16 @@ def _worker(executor_ref, work_queue, initializer, initargs, idle_timeout):
44
55
  except BaseException:
45
56
  _base.LOGGER.critical("Exception in initializer:", exc_info=True)
46
57
  executor = executor_ref()
47
- if executor is not None:
58
+ if executor is not None: # pragma: no cover
48
59
  executor._initializer_failed()
49
60
  return
50
61
 
51
- idle_tick = monotonic()
62
+ idle_tick = -1.0
52
63
  try:
53
64
  while True:
54
- if idle_timeout >= 0 and monotonic() - idle_tick > idle_timeout:
55
- executor = executor_ref()
56
- if executor is not None:
57
- executor._idle_semaphore.acquire(timeout=0)
65
+ if idle_tick == -1.0:
66
+ idle_tick = monotonic()
67
+ elif idle_timeout >= 0 and monotonic() - idle_tick > idle_timeout:
58
68
  break
59
69
  try:
60
70
  work_item = work_queue.get(block=True, timeout=0.1)
@@ -70,32 +80,80 @@ def _worker(executor_ref, work_queue, initializer, initargs, idle_timeout):
70
80
  del executor
71
81
  idle_tick = monotonic()
72
82
  continue
73
- executor = executor_ref()
74
- if _thread._shutdown or executor is None or executor._shutdown:
75
- if executor is not None:
76
- executor._shutdown = True
83
+ break # pragma: no cover
84
+ finally:
85
+ executor = executor_ref()
86
+ if executor is None:
87
+ work_queue.put(None)
88
+ else:
89
+ executor._idle_semaphore.acquire(timeout=0)
90
+ if _shutdown or executor._shutdown:
91
+ executor._shutdown = True
77
92
  work_queue.put(None)
78
- return
79
- del executor
80
- except BaseException:
81
- _base.LOGGER.critical("Exception in worker", exc_info=True)
93
+ del executor
82
94
 
83
95
 
84
- class ThreadPoolExecutor(_thread.ThreadPoolExecutor):
96
+ class ThreadPoolExecutor(_ThreadPoolExecutor):
85
97
  def __init__(
86
98
  self,
87
- max_workers=None,
99
+ max_workers=1024,
88
100
  thread_name_prefix="",
89
101
  initializer=None,
90
102
  initargs=(),
91
103
  idle_timeout=60.0,
92
104
  ):
105
+ """Initializes a new ThreadPoolExecutor instance.
106
+
107
+ :type max_workers: int, optional
108
+ :param max_workers: The maximum number of workers to create. Defaults to 1024.
109
+
110
+ :type thread_name_prefix: str, optional
111
+ :param thread_name_prefix: An optional name prefix to give our threads.
112
+
113
+ :type initializer: callable, optional
114
+ :param initializer: A callable used to initialize worker threads.
115
+
116
+ :type initargs: tuple, optional
117
+ :parm initargs: A tuple of arguments to pass to the initializer.
118
+
119
+ :type idle_timeout: float, optional
120
+ :param idle_timeout: The maximum amount of time (in seconds) that a worker
121
+ thread can remain idle before it is terminated. If set to None or negative
122
+ value, workers will never be terminated. Defaults to 60 seconds.
123
+ """
124
+ if max_workers is None:
125
+ max_workers = 1024
93
126
  super().__init__(max_workers, thread_name_prefix, initializer, initargs)
94
127
  if idle_timeout is None or idle_timeout < 0:
95
128
  self._idle_timeout = -1
96
129
  else:
97
130
  self._idle_timeout = max(0.1, idle_timeout)
98
131
 
132
+ def submit(self, fn, /, *args, **kwargs):
133
+ if iscoroutinefunction(fn):
134
+ raise TypeError("fn must not be a coroutine function")
135
+
136
+ with self._shutdown_lock, _global_shutdown_lock:
137
+ if self._broken:
138
+ raise BrokenThreadPool(self._broken)
139
+
140
+ if self._shutdown:
141
+ raise RuntimeError("cannot schedule new futures after shutdown")
142
+ if _shutdown:
143
+ # coverage didn't realize that _shutdown is set, add no cover here
144
+ raise RuntimeError( # pragma: no cover
145
+ "cannot schedule new futures after interpreter shutdown"
146
+ )
147
+
148
+ f = Future() # type: ignore
149
+ w = _WorkItem(f, fn, args, kwargs)
150
+
151
+ self._work_queue.put(w)
152
+ self._adjust_thread_count()
153
+ return f
154
+
155
+ submit.__doc__ = _base.Executor.submit.__doc__
156
+
99
157
  def _adjust_thread_count(self):
100
158
  if self._idle_semaphore.acquire(timeout=0):
101
159
  return
@@ -125,9 +183,10 @@ class ThreadPoolExecutor(_thread.ThreadPoolExecutor):
125
183
  _threads_queues[t] = self._work_queue
126
184
 
127
185
 
128
- class _AsyncWorkItem(_thread._WorkItem):
186
+ class _AsyncWorkItem(_WorkItem):
129
187
  async def run(self):
130
188
  if not self.future.set_running_or_notify_cancel():
189
+ print("cancelled")
131
190
  return
132
191
 
133
192
  try:
@@ -156,7 +215,7 @@ async def _async_worker(
156
215
  initializer(*initargs)
157
216
  except BaseException:
158
217
  _base.LOGGER.critical("Exception in initializer:", exc_info=True)
159
- if executor is not None:
218
+ if executor is not None: # pragma: no cover
160
219
  executor._running.set()
161
220
  executor._initializer_failed()
162
221
  return
@@ -169,43 +228,40 @@ async def _async_worker(
169
228
  try:
170
229
  while True:
171
230
  if idle_timeout >= 0 and monotonic() - idle_tick > idle_timeout:
172
- executor = executor_ref()
173
- if executor is not None:
174
- executor._idle_semaphore.acquire(timeout=0)
175
231
  break
176
- try:
177
- work_item = work_queue.get(block=True, timeout=0.1)
178
- except Empty:
179
- pass
180
- if work_item is not None:
181
- task = loop.create_task(work_item.run())
182
- curr_tasks.add(task)
183
- await asleep(0) # ugly but working
184
- del work_item
185
-
186
- finished_tasks = [t for t in curr_tasks if t.done()]
187
- for t in finished_tasks:
188
- curr_tasks.remove(t)
189
- if curr_tasks:
190
- idle_tick = monotonic()
191
- continue
192
-
193
- executor = executor_ref()
194
- if _thread._shutdown or executor is None or executor._shutdown:
195
- if executor is not None:
196
- executor._shutdown = True
197
- work_queue.put(None)
198
-
199
- for t in curr_tasks:
200
- await t
201
- return
202
- del executor
203
- except BaseException:
204
- _base.LOGGER.critical("Exception in worker", exc_info=True)
232
+ if len(curr_tasks) < max_workers:
233
+ try:
234
+ work_item = work_queue.get(block=True, timeout=0.1)
235
+ if work_item is not None:
236
+ task = loop.create_task(work_item.run())
237
+ curr_tasks.add(task)
238
+ await asleep(0)
239
+ del work_item
240
+ else:
241
+ break
242
+ except Empty:
243
+ pass
244
+ await asleep(0)
245
+ finished_tasks = [t for t in curr_tasks if t.done()]
246
+ for t in finished_tasks:
247
+ curr_tasks.remove(t)
248
+ if curr_tasks:
249
+ idle_tick = monotonic()
205
250
  finally:
251
+ while curr_tasks:
252
+ await asleep(0)
253
+ finished_tasks = [t for t in curr_tasks if t.done()]
254
+ for t in finished_tasks:
255
+ curr_tasks.remove(t)
206
256
  executor = executor_ref()
207
- if executor is not None:
257
+ if executor is None:
258
+ work_queue.put(None)
259
+ else:
208
260
  executor._running.clear()
261
+ if _shutdown or executor._shutdown:
262
+ executor._shutdown = True
263
+ work_queue.put(None)
264
+ del executor
209
265
 
210
266
 
211
267
  class AsyncWorker(Thread):
@@ -241,7 +297,7 @@ class AsyncWorker(Thread):
241
297
  )
242
298
 
243
299
 
244
- class AsyncPoolExecutor(_thread.ThreadPoolExecutor):
300
+ class AsyncPoolExecutor(ThreadPoolExecutor):
245
301
  _counter = itertools.count().__next__
246
302
 
247
303
  def __init__(
@@ -252,6 +308,25 @@ class AsyncPoolExecutor(_thread.ThreadPoolExecutor):
252
308
  initargs=(),
253
309
  idle_timeout=60.0,
254
310
  ):
311
+ """Initializes a new AsyncPoolExecutor instance.
312
+
313
+ :type max_workers: int, optional
314
+ :param max_workers: The maximum number of workers to create. Defaults to 261244.
315
+
316
+ :type thread_name_prefix: str, optional
317
+ :param thread_name_prefix: An optional name prefix to give our threads.
318
+
319
+ :type initializer: callable, optional
320
+ :param initializer: A callable used to initialize worker threads.
321
+
322
+ :type initargs: tuple, optional
323
+ :parm initargs: A tuple of arguments to pass to the initializer.
324
+
325
+ :type idle_timeout: float, optional
326
+ :param idle_timeout: The maximum amount of time (in seconds) that a worker
327
+ thread can remain idle before it is terminated. If set to None or negative
328
+ value, workers will never be terminated. Defaults to 60 seconds.
329
+ """
255
330
  if max_workers is None:
256
331
  max_workers = 262144
257
332
  if not thread_name_prefix:
@@ -269,16 +344,17 @@ class AsyncPoolExecutor(_thread.ThreadPoolExecutor):
269
344
  raise TypeError("fn must be a coroutine function")
270
345
  with self._shutdown_lock, _global_shutdown_lock:
271
346
  if self._broken:
272
- raise _thread.BrokenThreadPool(self._broken)
347
+ raise BrokenThreadPool(self._broken)
273
348
 
274
349
  if self._shutdown:
275
350
  raise RuntimeError("cannot schedule new futures after shutdown")
276
- if _thread._shutdown:
277
- raise RuntimeError(
351
+ if _shutdown:
352
+ # coverage didn't realize that _shutdown is set, add no cover here
353
+ raise RuntimeError( # pragma: no cover
278
354
  "cannot schedule new futures after interpreter shutdown"
279
355
  )
280
356
 
281
- f = _base.Future() # type: ignore
357
+ f = Future() # type: ignore
282
358
  w = _AsyncWorkItem(f, fn, args, kwargs)
283
359
 
284
360
  self._work_queue.put(w)
@@ -0,0 +1,107 @@
1
+ [project]
2
+ name = "flexexecutor"
3
+ description = "Executors for both async and sync functions and automatically scales the number of workers"
4
+ authors = [
5
+ { name = "leavers" },
6
+ { name = "Leavers", email = "leavers930@gmail.com" },
7
+ ]
8
+ classifiers = [
9
+ "Intended Audience :: Developers",
10
+ "License :: OSI Approved :: MIT License",
11
+ "Operating System :: OS Independent",
12
+ "Programming Language :: Python",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.8",
15
+ "Programming Language :: Python :: 3.9",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Software Development",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ "Topic :: Utilities",
22
+ ]
23
+ requires-python = ">=3.8"
24
+ readme = "README.md"
25
+ dependencies = []
26
+ dynamic = []
27
+ version = "0.0.10"
28
+
29
+ [project.license]
30
+ text = "MIT"
31
+
32
+ [project.urls]
33
+ homepage = "https://github.com/leavers/flexexecutor"
34
+ documentation = "https://github.com/leavers/flexexecutor/blob/main/README.md"
35
+
36
+ [build-system]
37
+ requires = [
38
+ "pdm-backend",
39
+ ]
40
+ build-backend = "pdm.backend"
41
+
42
+ [tool.pdm]
43
+ distribution = true
44
+
45
+ [tool.pdm.build]
46
+ includes = [
47
+ "flexexecutor.py",
48
+ ]
49
+ excludes = [
50
+ "tests",
51
+ ]
52
+
53
+ [tool.pdm.version]
54
+ source = "file"
55
+ path = "flexexecutor.py"
56
+
57
+ [tool.pdm.dev-dependencies]
58
+ dev = [
59
+ "autoflake>=2.3.1",
60
+ "coverage[toml]>=7.6.1",
61
+ "mypy>=1.11.1",
62
+ "nox>=2024.4.15",
63
+ "pytest>=8.3.2",
64
+ "pytest-asyncio>=0.23.8",
65
+ "pytest-cov>=5.0.0",
66
+ "pytest-mock>=3.14.0",
67
+ "pytest-timeout>=2.3.1",
68
+ "ruoml>=0.11.0",
69
+ "ruff>=0.6.1",
70
+ ]
71
+
72
+ [tool.autoflake]
73
+ in-place = true
74
+ recursive = true
75
+ remove-all-unused-imports = true
76
+ ignore-init-module-imports = true
77
+ remove-duplicate-keys = true
78
+ remove-unused-variables = true
79
+
80
+ [tool.coverage.run]
81
+ branch = true
82
+ source = [
83
+ "flexexecutor.py",
84
+ ]
85
+ parallel = true
86
+
87
+ [tool.coverage.html]
88
+ directory = "coverage_html_report"
89
+
90
+ [tool.coverage.report]
91
+ show_missing = true
92
+ exclude_lines = [
93
+ "pragma: no cover",
94
+ ]
95
+
96
+ [tool.ruff]
97
+ line-length = 88
98
+
99
+ [tool.mypy]
100
+ check_untyped_defs = true
101
+ follow_imports = "error"
102
+ ignore_missing_imports = true
103
+ show_error_codes = true
104
+ warn_unused_configs = true
105
+
106
+ [tool.pytest.ini_options]
107
+ asyncio_mode = "strict"
@@ -1,17 +0,0 @@
1
- ## Expected Behavior
2
-
3
-
4
- ## Actual Behavior
5
-
6
-
7
- ## Steps to Reproduce the Problem
8
-
9
- 1.
10
- 1.
11
- 1.
12
-
13
- ## Specifications
14
-
15
- - Version:
16
- - Platform:
17
- - Subsystem:
@@ -1,14 +0,0 @@
1
- ## Feature Request
2
-
3
- **Is your feature request related to a problem? Please describe.**
4
- A clear and concise description of what the problem is. Ex. I have an issue when [...]
5
-
6
- **Describe the solution you'd like**
7
- A clear and concise description of what you want to happen. Add any considered drawbacks.
8
-
9
- **Describe alternatives you've considered**
10
- A clear and concise description of any alternative solutions or features you've considered.
11
-
12
- **Teachability, Documentation, Adoption, Migration Strategy**
13
- If you can, explain how users will be able to use this and possibly write out a version the docs.
14
- Maybe a screenshot or design?