diff --git a/opvious/client/handlers.py b/opvious/client/handlers.py index 8ac6f87..4ec7d2a 100644 --- a/opvious/client/handlers.py +++ b/opvious/client/handlers.py @@ -611,7 +611,10 @@ async def _track_solve(self, uuid: Uuid) -> Optional[SolveOutcome]: details.append(f"cuts={ret.cut_count}") if ret.lp_iteration_count is not None: details.append(f"iterations={ret.lp_iteration_count}") - _logger.info("Solve is running... [%s]", ", ".join(details)) + suffix = ", ".join(details) + _logger.info( + "Solve is running...%s", f" [{suffix}]" if suffix else "" + ) else: _logger.info("Solve is queued...") return None @@ -698,6 +701,54 @@ async def _outline(): raw_constraints=data["constraints"], ) + async def paginate_solves( + self, + annotations: Optional[list[Annotation]] = None, + limit: int = 25, + ) -> AsyncIterator[QueuedSolve]: + """Lists recent queued solves + + Args: + annotations: Optional annotations to filter solves by + limit: Maximum number of solves to return + + Solves are sorted from most recently started to least. + """ + cursor = None + attempt_filter = json_dict( + operation="QUEUE_SOLVE", + annotations=encode_annotations(annotations or []), + ) + + async def _next_page() -> list[QueuedSolve]: + nonlocal cursor + data = await self._executor.execute_graphql_query( + query="@PaginateQueuedSolveAttempts", + variables=json_dict( + last=min(25, limit), + before=cursor, + filter=attempt_filter, + ), + ) + cursor = data["attempts"]["pageInfo"]["startCursor"] + solves: list[QueuedSolve] = [] + for edge in data["attempts"]["edges"]: + attempt = edge["node"] + content = attempt["content"] + if not content: + continue + solves.append(queued_solve_from_graphql(content, attempt)) + solves.reverse() + return solves + + while limit > 0: + solves = await _next_page() + if not solves: + return + for solve in solves: + yield solve + limit -= len(solves) + async def paginate_formulation_solves( self, name: str, diff --git a/pyproject.toml b/pyproject.toml index 59a606b..4dc63b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "opvious" -version = "0.19.2rc1" +version = "0.19.2rc2" description = "Opvious Python SDK" authors = ["Opvious Engineering "] readme = "README.md" diff --git a/tests/test_client.py b/tests/test_client.py index 467dbf9..c3f04cb 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -463,3 +463,8 @@ async def test_paginate_formulation_solves(self): async def test_fetch_unknown_solve_outputs(self): with pytest.raises(opvious.executors.ExecutorError): await client.fetch_solve_outputs("invalid-uuid") + + @pytest.mark.asyncio + async def test_paginate_solves(self): + async for solve in client.paginate_solves(limit=3): + assert isinstance(solve, opvious.QueuedSolve)