Skip to content

Commit 29dbd86

Browse files
committed
support Python 3.10 (do not merge)
1 parent a306663 commit 29dbd86

5 files changed

Lines changed: 203 additions & 34 deletions

File tree

finegrain/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dependencies = [
1111
"pyjwt[crypto]>=2.10.1",
1212
]
1313
readme = "README.md"
14-
requires-python = ">= 3.12, <3.13"
14+
requires-python = ">= 3.10"
1515

1616
[dependency-groups]
1717
# pytest-asyncio > 0.21 changes event loop management
@@ -35,7 +35,7 @@ packages = ["src/finegrain"]
3535

3636
[tool.ruff]
3737
line-length = 120
38-
target-version = "py312"
38+
target-version = "py310"
3939

4040
[tool.ruff.lint]
4141
select = [

finegrain/src/finegrain/__init__.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
from collections import defaultdict
88
from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
9-
from typing import IO, Any, Literal, NewType, cast, get_args
9+
from typing import IO, Any, Generic, Literal, NewType, TypeVar, cast, get_args
1010

1111
import httpx
1212
import httpx_sse
@@ -52,7 +52,14 @@ def default_message(self) -> str:
5252
return f"SSE loop stopped (first error: {self.first_error}, last error: {self.last_error})"
5353

5454

55-
class Futures[Tk, Tv]:
55+
Tk = TypeVar("Tk")
56+
Tv = TypeVar("Tv")
57+
Tok = TypeVar("Tok", bound="OKResult")
58+
Tko = TypeVar("Tko", bound="ErrorResult")
59+
TokWithImage = TypeVar("TokWithImage", bound="OKResultWithImage")
60+
61+
62+
class Futures(Generic[Tk, Tv]):
5663
_event_loop: asyncio.AbstractEventLoop | None
5764

5865
@property
@@ -140,7 +147,10 @@ def success(self) -> None:
140147
self.failures = 0
141148

142149

143-
class TimeoutableAsyncIterator[T](AsyncIterator[T]):
150+
T = TypeVar("T")
151+
152+
153+
class TimeoutableAsyncIterator(AsyncIterator[T], Generic[T]):
144154
def __init__(self, iterator: AsyncIterator[T], timeout: float) -> None:
145155
self.iterator = iterator
146156
self.timeout = timeout
@@ -187,7 +197,7 @@ def reset(self) -> None:
187197
self.active = asyncio.get_running_loop().create_future()
188198

189199
@staticmethod
190-
def async_return[T](x: T) -> Callable[[], Awaitable[T]]:
200+
def async_return(x: T) -> Callable[[], Awaitable[T]]:
191201
async def f() -> T:
192202
return x
193203

@@ -613,11 +623,11 @@ async def get_image(
613623
response = await self.request("GET", f"state/image/{state_id}", params=params)
614624
return response.content
615625

616-
async def _run_one[Tin, Tout](
626+
async def _run_one(
617627
self,
618-
co: Callable[["EditorAPIContext", Tin], Awaitable[Tout]],
619-
params: Tin,
620-
) -> Tout:
628+
co: Callable[["EditorAPIContext", Any], Awaitable[Any]],
629+
params: Any,
630+
) -> Any:
621631
# This wraps the coroutine in the SSE loop.
622632
# This is mostly useful if you use synchronous Python,
623633
# otherwise you can call the functions directly.
@@ -630,11 +640,11 @@ async def _run_one[Tin, Tout](
630640
finally:
631641
await self.sse_stop()
632642

633-
def run_one_sync[Tin, Tout](
643+
def run_one_sync(
634644
self,
635-
co: Callable[["EditorAPIContext", Tin], Awaitable[Tout]],
636-
params: Tin,
637-
) -> Tout:
645+
co: Callable[["EditorAPIContext", Any], Awaitable[Any]],
646+
params: Any,
647+
) -> Any:
638648
try:
639649
loop = asyncio.get_event_loop()
640650
except RuntimeError:
@@ -1032,7 +1042,7 @@ async def _create_state(
10321042
status = await self.ctx.sse_await(state_id, timeout=timeout)
10331043
return state_id, status
10341044

1035-
async def _response[Tok: OKResult, Tko: ErrorResult](
1045+
async def _response(
10361046
self,
10371047
st: StateID,
10381048
ok: bool,
@@ -1047,22 +1057,21 @@ async def _response[Tok: OKResult, Tko: ErrorResult](
10471057
assert meta["status"] == "ko"
10481058
return t_ko(state_id=st, meta=meta)
10491059

1050-
async def _response_with_image[Tok: OKResultWithImage, Tko: ErrorResult](
1060+
async def _response_with_image(
10511061
self,
10521062
st: StateID,
10531063
ok: bool,
1054-
t_ok: type[Tok] = OKResultWithImage,
1064+
t_ok: type[TokWithImage] = OKResultWithImage,
10551065
t_ko: type[Tko] = ErrorResult,
10561066
params: ImageOutParams | None = None,
1057-
) -> Tok | Tko:
1067+
) -> TokWithImage | Tko:
10581068
if ok:
10591069
if params is None:
10601070
params = ImageOutParams()
1061-
async with asyncio.TaskGroup() as tg:
1062-
meta_f = tg.create_task(self.ctx.get_meta(st))
1063-
image_f = tg.create_task(self.ctx.get_image(st, params.image_format, params.resolution))
1064-
meta = meta_f.result()
1065-
image = image_f.result()
1071+
meta, image = await asyncio.gather(
1072+
self.ctx.get_meta(st),
1073+
self.ctx.get_image(st, params.image_format, params.resolution),
1074+
)
10661075
assert meta["status"] == "ok"
10671076
return t_ok(state_id=st, meta=meta, image=image)
10681077
else:

finegrain/tests/test_move.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ async def test_move(
1616
segment_r = await fgctx.call_async.segment(st_input, bbox=bbox_r.bbox)
1717
assert isinstance(segment_r, OKResult)
1818

19-
async with asyncio.TaskGroup() as tg:
20-
erase_task = tg.create_task(fgctx.call_async.erase(st_input, segment_r.state_id))
21-
cutout_task = tg.create_task(fgctx.call_async.cutout(st_input, segment_r.state_id))
22-
23-
erase_r, cutout_r = erase_task.result(), cutout_task.result()
19+
erase_r, cutout_r = await asyncio.gather(
20+
fgctx.call_async.erase(st_input, segment_r.state_id),
21+
fgctx.call_async.cutout(st_input, segment_r.state_id),
22+
)
2423
assert isinstance(erase_r, OKResult)
2524
assert isinstance(cutout_r, OKResult)
2625

finegrain/tests/test_recolor.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ async def test_recolor(
1717
(1210, 629, 1298, 733),
1818
]
1919

20-
async with asyncio.TaskGroup() as tg:
21-
tasks = [tg.create_task(fgctx.call_async.segment(st_input, bbox)) for bbox in bboxes]
22-
23-
segment_results = [t.result() for t in tasks]
20+
segment_results = await asyncio.gather(*[fgctx.call_async.segment(st_input, bbox) for bbox in bboxes])
2421
for r in segment_results:
2522
assert isinstance(r, OKResult)
2623
segment_state_ids = [r.state_id for r in segment_results]

0 commit comments

Comments
 (0)