Skip to content

Commit 5fdf410

Browse files
committed
support subscription topics
1 parent 2870080 commit 5fdf410

File tree

3 files changed

+82
-6
lines changed

3 files changed

+82
-6
lines changed

finegrain/src/finegrain/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,12 @@ class EditorAPIContext:
305305
user_agent: str
306306

307307
token: str | None
308+
subscription_topic: str | None = None
308309
logger: logging.Logger
310+
311+
# Note: this is always set when calling `login` or `me` and updated using SSE.
312+
# If you use a subscription topic it is *not* updated, use the value
313+
# from the response metadata or call `me`.
309314
credits: int | None = None
310315

311316
_client: httpx.AsyncClient | None
@@ -317,6 +322,7 @@ class EditorAPIContext:
317322

318323
def __init__(
319324
self,
325+
*,
320326
credentials: str | None = None,
321327
api_key: str | None = None,
322328
user: str | None = None,
@@ -325,12 +331,14 @@ def __init__(
325331
priority: Priority = "standard",
326332
verify: bool | str = True,
327333
default_timeout: float = 60.0,
334+
subscription_topic: str | None = None,
328335
user_agent: str | None = None,
329336
) -> None:
330337
self.base_url = base_url or "https://api.finegrain.ai/editor"
331338
self.priority = priority
332339
self.verify = verify
333340
self.default_timeout = default_timeout
341+
self.subscription_topic = subscription_topic
334342

335343
if credentials is not None:
336344
if (m := API_KEY_PATTERN.match(credentials)) is not None:
@@ -407,6 +415,12 @@ async def login(self) -> None:
407415
self.credits = r["user"]["credits"]
408416
self.token = r["token"]
409417

418+
async def me(self) -> dict[str, Any]:
419+
response = await self.request("GET", "auth/me")
420+
r = response.json()
421+
self.credits = r["credits"]
422+
return r
423+
410424
async def request(
411425
self,
412426
method: Literal["GET", "POST"],
@@ -441,7 +455,11 @@ async def _q() -> httpx.Response:
441455
return r
442456

443457
async def get_sub_url(self) -> str:
444-
response = await self.request("POST", "sub-auth")
458+
if self.subscription_topic is not None:
459+
params = {"subscription_topic": self.subscription_topic}
460+
else:
461+
params = None
462+
response = await self.request("POST", "sub-auth", json=params)
445463
jdata = response.json()
446464
sub_token = jdata["token"]
447465
self._ping_interval = float(jdata.get("ping_interval", 0.0))
@@ -557,6 +575,8 @@ async def call_skill(
557575
timeout = timeout or self.default_timeout
558576
user_timeout = max(int(timeout), 1)
559577
params = {"priority": self.priority, "user_timeout": user_timeout} | (params or {})
578+
if self.subscription_topic is not None:
579+
params["subscription_topic"] = self.subscription_topic
560580
response = await self.request("POST", f"skills/{url}", json=params)
561581
state_id: StateID = response.json()["state"]
562582
status = await self.sse_await(state_id, timeout=timeout)
@@ -926,6 +946,8 @@ async def _create_state(
926946
data: dict[str, str] = {}
927947
if file_url is not None:
928948
data["file_url"] = file_url
949+
if self.ctx.subscription_topic is not None:
950+
data["subscription_topic"] = self.ctx.subscription_topic
929951
if meta is not None:
930952
data["meta"] = json.dumps(meta)
931953
response = await self.ctx.request("POST", "state/create", files=files, data=data)

finegrain/tests/conftest.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,25 @@ def event_loop():
2424

2525

2626
@pytest.fixture(scope="session")
27-
async def fgctx() -> AsyncGenerator[EditorAPIContext, None]:
27+
def fx_base_url() -> str:
2828
with env.prefixed("FG_API_"):
29-
credentials = env.str("CREDENTIALS", None)
3029
url = env.str("URL", "https://api.finegrain.ai/editor")
31-
assert credentials and url, "set FG_API_CREDENTIALS"
30+
return url
31+
32+
33+
@pytest.fixture(scope="session")
34+
def fx_credentials() -> str:
35+
with env.prefixed("FG_API_"):
36+
credentials = env.str("CREDENTIALS", None)
37+
assert credentials, "set FG_API_CREDENTIALS"
38+
return credentials
39+
40+
41+
@pytest.fixture(scope="session")
42+
async def fgctx(fx_base_url: str, fx_credentials: str) -> AsyncGenerator[EditorAPIContext, None]:
3243
ctx = EditorAPIContext(
33-
credentials=credentials,
34-
base_url=url,
44+
base_url=fx_base_url,
45+
credentials=fx_credentials,
3546
user_agent="finegrain-python-tests",
3647
)
3748

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from finegrain import EditorAPIContext, OKResult
4+
5+
6+
@pytest.mark.parametrize("subscription_topic", ["fg-test-topic", None])
7+
async def test_subscription_topic(
8+
fx_base_url: str,
9+
fx_credentials: str,
10+
sofa_cushion_bytes: bytes,
11+
subscription_topic: str | None,
12+
) -> None:
13+
ctx = EditorAPIContext(
14+
base_url=fx_base_url,
15+
credentials=fx_credentials,
16+
user_agent="finegrain-python-tests",
17+
subscription_topic=subscription_topic,
18+
)
19+
20+
await ctx.login()
21+
await ctx.sse_start()
22+
23+
create_r = await ctx.call_async.create_state(
24+
file=sofa_cushion_bytes,
25+
meta={"test-key": "test-value"},
26+
)
27+
assert isinstance(create_r, OKResult)
28+
assert create_r.meta["test-key"] == "test-value"
29+
30+
# to test credits update mechanism
31+
assert isinstance(ctx.credits, int)
32+
ctx.credits = None
33+
34+
infer_ms_r = await ctx.call_async.infer_product_name(create_r.state_id)
35+
assert isinstance(infer_ms_r, OKResult)
36+
assert infer_ms_r.meta["product_name"] == "sofa"
37+
38+
if subscription_topic is not None:
39+
assert ctx.credits is None
40+
await ctx.me()
41+
assert isinstance(ctx.credits, int)
42+
43+
await ctx.sse_stop()

0 commit comments

Comments
 (0)