Skip to content

Commit

Permalink
[ENH] Implement cursor pagaination for listing environments
Browse files Browse the repository at this point in the history
  • Loading branch information
peytondmurray committed Jan 16, 2025
1 parent 42a8aae commit d5a8846
Show file tree
Hide file tree
Showing 6 changed files with 551 additions and 28 deletions.
12 changes: 10 additions & 2 deletions conda-store-server/conda_store_server/_internal/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,14 @@ class APIPaginatedResponse(APIResponse):
count: int


class APICursorPaginatedResponse(BaseModel):
data: Optional[Any] = None
status: APIStatus
message: Optional[str] = None
cursor: Optional[str] = None
count: int # the total number of results available to fetch


class APIAckResponse(BaseModel):
status: APIStatus
message: Optional[str] = None
Expand Down Expand Up @@ -509,8 +517,8 @@ class APIDeleteNamespaceRole(BaseModel):


# GET /api/v1/environment
class APIListEnvironment(APIPaginatedResponse):
data: List[Environment]
class APIListEnvironment(APICursorPaginatedResponse):
data: List[Environment] = []


# GET /api/v1/environment/{namespace}/{name}
Expand Down
74 changes: 55 additions & 19 deletions conda-store-server/conda_store_server/_internal/server/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,37 @@
from conda_store_server._internal import orm, schema
from conda_store_server._internal.environment import filter_environments
from conda_store_server._internal.server import dependencies
from conda_store_server._internal.server.views.pagination import (
Cursor,
CursorPaginatedArgs,
Ordering,
OrderingMetadata,
paginate,
)
from conda_store_server.conda_store import CondaStore
from conda_store_server.exception import CondaStoreError
from conda_store_server.server import schema as auth_schema
from conda_store_server.server.auth import Authentication
from conda_store_server.server.schema import AuthenticationToken, Permissions


def get_cursor(cursor: Optional[str] = None) -> Cursor:
return Cursor.load(cursor)


def get_cursor_paginated_args(
order: Optional[Ordering] = Ordering.ASCENDING,
limit: Optional[int] = None,
sort_by: List[str] = Query([]),
server=Depends(dependencies.get_server),
) -> CursorPaginatedArgs:
return CursorPaginatedArgs(
limit=server.max_page_size if limit is None else limit,
order=order,
sort_by=sort_by,
)


class PaginatedArgs(TypedDict):
"""Dictionary type holding information about paginated requests."""

Expand Down Expand Up @@ -634,18 +658,20 @@ async def api_delete_namespace(
response_model=schema.APIListEnvironment,
)
async def api_list_environments(
request: Request,
auth: Authentication = Depends(dependencies.get_auth),
conda_store: CondaStore = Depends(dependencies.get_conda_store),
entity: AuthenticationToken = Depends(dependencies.get_entity),
paginated_args: PaginatedArgs = Depends(get_paginated_args),
paginated_args: CursorPaginatedArgs = Depends(get_cursor_paginated_args),
cursor: Cursor = Depends(get_cursor),
artifact: Optional[schema.BuildArtifactType] = None,
jwt: Optional[str] = None,
name: Optional[str] = None,
namespace: Optional[str] = None,
packages: Optional[List[str]] = Query([]),
search: Optional[str] = None,
status: Optional[schema.BuildStatus] = None,
):
) -> schema.APIListEnvironment:
"""Retrieve a list of environments.
Parameters
Expand All @@ -656,7 +682,7 @@ async def api_list_environments(
the request
entity : AuthenticationToken
Token of the user making the request
paginated_args : PaginatedArgs
paginated_args : CursorPaginatedArgs
Arguments for controlling pagination of the response
conda_store : app.CondaStore
The running conda store application
Expand All @@ -680,9 +706,11 @@ async def api_list_environments(
Returns
-------
Dict
Paginated JSON response containing the requested environments
schema.APIListEnvironment
Paginated JSON response containing the requested environments. Results are sorted by each
envrionment's build's scheduled_on time to ensure all results are returned when iterating
over pages in systems where the number of environments is changing while results are being
requested; see https://github.com/conda-incubator/conda-store/issues/859 for context
"""
with conda_store.get_db() as db:
if jwt:
Expand All @@ -695,7 +723,7 @@ async def api_list_environments(
else:
role_bindings = None

orm_environments = api.list_environments(
query = api.list_environments(
db,
search=search,
namespace=namespace,
Expand All @@ -708,21 +736,29 @@ async def api_list_environments(
)

# Filter by environments that the user who made the query has access to
orm_environments = filter_environments(
query=orm_environments,
query = filter_environments(
query=query,
role_bindings=auth.entity_bindings(entity),
)

return paginated_api_response(
orm_environments,
paginated_args,
schema.Environment,
exclude={"current_build"},
allowed_sort_bys={
"namespace": orm.Namespace.name,
"name": orm.Environment.name,
},
default_sort_by=["namespace", "name"],
paginated, next_cursor, count = paginate(
query=query,
ordering_metadata=OrderingMetadata(
order_names=["namespace", "name"],
column_names=["namespace.name", "name"],
column_objects=[orm.Namespace.name, orm.Environment.name],
),
cursor=cursor,
order_by=paginated_args.sort_by,
order=paginated_args.order,
limit=paginated_args.limit,
)

return schema.APIListEnvironment(
data=paginated,
status="ok",
cursor=next_cursor.dump(),
count=count,
)


Expand Down
Loading

0 comments on commit d5a8846

Please sign in to comment.