Skip to content

Commit 87bd335

Browse files
authored
Merge pull request #8 from pyper-dev/dev
Dev
2 parents 114c4a4 + ea0d70f commit 87bd335

File tree

11 files changed

+347
-10
lines changed

11 files changed

+347
-10
lines changed

.coveragerc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ exclude_also =
33
# pragma: no cover
44
if TYPE_CHECKING:
55
if t.TYPE_CHECKING:
6-
raise NotImplementedError
6+
raise NotImplementedError
7+
8+
[run]
9+
omit = **/pyper/_core/util/task_group.py

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jobs:
1515
strategy:
1616
matrix:
1717
python-version: [
18+
3.8,
19+
3.9,
20+
3.10,
1821
3.11,
1922
3.12,
2023
3.13

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "python-pyper"
99
dynamic = ["version"]
1010
description = "Concurrent Python made simple"
1111
readme = "README.md"
12-
requires-python = ">=3.11"
12+
requires-python = ">=3.8"
1313
authors = [
1414
{ name = "Richard Zhu", email = "[email protected]" },
1515
]
@@ -23,10 +23,17 @@ classifiers = [
2323
"Framework :: AsyncIO",
2424
"License :: OSI Approved :: MIT License",
2525
"Programming Language :: Python :: 3 :: Only",
26+
"Programming Language :: Python :: 3.8",
27+
"Programming Language :: Python :: 3.9",
28+
"Programming Language :: Python :: 3.10",
2629
"Programming Language :: Python :: 3.11",
2730
"Programming Language :: Python :: 3.12",
2831
"Programming Language :: Python :: 3.13"
2932
]
33+
dependencies = [
34+
# Typing support dependency only for Python versions < 3.10
35+
"typing_extensions; python_version<'3.10'"
36+
]
3037

3138
[project.urls]
3239
Documentation = "https://pyper-dev.github.io/pyper/"

src/pyper/_core/async_helper/output.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import sys
45
from typing import TYPE_CHECKING
56

67
from .stage import AsyncProducer, AsyncProducerConsumer
78
from ..util.sentinel import StopSentinel
89
from ..util.thread_pool import ThreadPool
910

11+
if sys.version_info < (3, 11): # pragma: no cover
12+
from ..util.task_group import TaskGroup, ExceptionGroup
13+
else:
14+
from asyncio import TaskGroup
15+
1016
if TYPE_CHECKING:
1117
from ..pipeline import AsyncPipeline
1218

@@ -15,7 +21,7 @@ class AsyncPipelineOutput:
1521
def __init__(self, pipeline: AsyncPipeline):
1622
self.pipeline = pipeline
1723

18-
def _get_q_out(self, tg: asyncio.TaskGroup, tp: ThreadPool ,*args, **kwargs) -> asyncio.Queue:
24+
def _get_q_out(self, tg: TaskGroup, tp: ThreadPool ,*args, **kwargs) -> asyncio.Queue:
1925
"""Feed forward each stage to the next, returning the output queue of the final stage."""
2026
q_out = None
2127
for task, next_task in zip(self.pipeline.tasks, self.pipeline.tasks[1:] + [None]):
@@ -36,7 +42,7 @@ async def __call__(self, *args, **kwargs):
3642
# We unify async and thread-based concurrency by
3743
# 1. using TaskGroup to spin up asynchronous tasks
3844
# 2. using ThreadPool to spin up synchronous tasks
39-
async with asyncio.TaskGroup() as tg, ThreadPool() as tp:
45+
async with TaskGroup() as tg, ThreadPool() as tp:
4046
q_out = self._get_q_out(tg, tp, *args, **kwargs)
4147
while (data := await q_out.get()) is not StopSentinel:
4248
yield data

src/pyper/_core/async_helper/stage.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import sys
45
from typing import TYPE_CHECKING
56

67
from .queue_io import AsyncDequeue, AsyncEnqueue
78
from ..util.asynchronize import ascynchronize
89
from ..util.sentinel import StopSentinel
910

11+
if sys.version_info < (3, 11): # pragma: no cover
12+
from ..util.task_group import TaskGroup
13+
else:
14+
from asyncio import TaskGroup
15+
1016
if TYPE_CHECKING:
1117
from ..util.thread_pool import ThreadPool
1218
from ..task import Task
1319

1420

1521
class AsyncProducer:
16-
def __init__(self, task: Task, tg: asyncio.TaskGroup, tp: ThreadPool, n_consumers: int):
22+
def __init__(self, task: Task, tg: TaskGroup, tp: ThreadPool, n_consumers: int):
1723
self.task = ascynchronize(task, tp)
1824
if task.concurrency > 1:
1925
raise RuntimeError(f"The first task in a pipeline ({task.func.__qualname__}) cannot have concurrency greater than 1")
@@ -36,7 +42,7 @@ def start(self, *args, **kwargs):
3642

3743

3844
class AsyncProducerConsumer:
39-
def __init__(self, q_in: asyncio.Queue, task: Task, tg: asyncio.TaskGroup, tp: ThreadPool, n_consumers: int):
45+
def __init__(self, q_in: asyncio.Queue, task: Task, tg: TaskGroup, tp: ThreadPool, n_consumers: int):
4046
self.q_in = q_in
4147
self.task = ascynchronize(task, tp)
4248
self.tg = tg

src/pyper/_core/decorators.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
from __future__ import annotations
22

33
import functools
4+
import sys
45
import typing as t
56

67
from .pipeline import AsyncPipeline, Pipeline
78
from .task import Task
89

10+
if sys.version_info < (3, 10): # pragma: no cover
11+
from typing_extensions import ParamSpec
12+
else:
13+
from typing import ParamSpec
914

10-
_P = t.ParamSpec('P')
15+
16+
_P = ParamSpec('P')
1117
_R = t.TypeVar('R')
1218
_ArgsKwargs: t.TypeAlias = t.Optional[t.Tuple[t.Tuple[t.Any], t.Dict[str, t.Any]]]
1319

src/pyper/_core/pipeline.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
from __future__ import annotations
22

33
import inspect
4+
import sys
45
import typing as t
56

67
from .async_helper.output import AsyncPipelineOutput
78
from .sync_helper.output import PipelineOutput
89

10+
if sys.version_info < (3, 10): # pragma: no cover
11+
from typing_extensions import ParamSpec
12+
else:
13+
from typing import ParamSpec
14+
915
if t.TYPE_CHECKING:
1016
from .task import Task
1117

1218

13-
_P = t.ParamSpec('P')
19+
_P = ParamSpec('P')
1420
_R = t.TypeVar('R')
15-
_P_Other = t.ParamSpec("P_Other")
21+
_P_Other = ParamSpec("P_Other")
1622
_R_Other = t.TypeVar("R_Other")
1723

1824

@@ -90,7 +96,7 @@ def __gt__(self, other: t.Callable[..., _R_Other]) -> t.Callable[_P, _R_Other]:
9096
return self.consume(other)
9197

9298
def __repr__(self):
93-
return f"{self.__class__.__name__} {[task.func for task in self.tasks]}"
99+
return f"<{self.__class__.__name__} {[task.func for task in self.tasks]}>"
94100

95101

96102
class AsyncPipeline(Pipeline[_P, _R]):

0 commit comments

Comments
 (0)