Skip to content

Commit

Permalink
Pagination works, but I need a way to get the last values from the
Browse files Browse the repository at this point in the history
results; InstantiatedAttribute can't be used to get values from a db
table entry?
  • Loading branch information
peytondmurray committed Jan 3, 2025
1 parent bd0fc41 commit f8bf29f
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 18 deletions.
8 changes: 4 additions & 4 deletions conda-store-server/conda_store_server/_internal/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,10 +499,10 @@ class APIPaginatedResponse(APIResponse):


class APICursorPaginatedResponse(BaseModel):
data: Optional[Any]
data: Optional[Any] = None
status: APIStatus
message: Optional[str]
cursor: Optional[str]
message: Optional[str] = None
cursor: Optional[str] = None
count: int


Expand Down Expand Up @@ -571,7 +571,7 @@ class APIDeleteNamespaceRole(BaseModel):

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


# GET /api/v1/environment/{namespace}/{name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,11 +744,13 @@ async def api_list_environments(
query=query,
ordering_metadata=OrderingMetadata(
order_names=["namespace", "name"],
column_names=["namespace.name", "name"],
# column_names=['namespace.name', 'name'],
column_names=[orm.Namespace.name, orm.Environment.name],
),
cursor=cursor,
order_by=paginated_args["sort_by"],
limit=paginated_args["limit"],
order_by=paginated_args.sort_by,
order=paginated_args.order,
limit=paginated_args.limit,
)

return schema.APIListEnvironment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import base64
import operator
from typing import Any, TypedDict
from typing import Any

import pydantic
from fastapi import HTTPException
Expand All @@ -23,13 +23,13 @@ class Cursor(pydantic.BaseModel):
last_value: dict[str, str] | None = {}

def dump(self) -> str:
return base64.b64encode(self.model_dump_json())
return base64.b64encode(self.model_dump_json().encode("utf8"))

@classmethod
def load(cls, data: str | None = None) -> Cursor | None:
if data is None:
return None
return cls.from_json(base64.b64decode(data))
return cls.from_json(base64.b64decode(data).decode("utf8"))

def get_last_values(self, order_names: list[str]) -> list[Any]:
if order_names:
Expand Down Expand Up @@ -106,6 +106,7 @@ def paginate(
)
)

breakpoint()
query = query.order_by(
*[order_func(col) for col in columns], order_func(queried_type.id)
)
Expand All @@ -125,11 +126,33 @@ def paginate(
return (data, next_cursor)


class CursorPaginatedArgs(TypedDict):
class CursorPaginatedArgs(pydantic.BaseModel):
limit: int
order: str
sort_by: list[str]

@pydantic.field_validator("sort_by")
def validate_sort_by(cls, v: list[str]) -> list[str]:
"""Validate the columns to sort by.
FastAPI doesn't support lists in query parameters, so if the
`sort_by` value is a single-element list, assume that this
could be a comma-separated list. No harm in attempting to split
this by commas.
Parameters
----------
v : list[str]
Returns
-------
list[str]
"""
if len(v) == 1:
v = v[0].split(",")
return v


class OrderingMetadata:
def __init__(
Expand Down Expand Up @@ -173,6 +196,7 @@ def get_requested_columns(
if order_by:
for order_name in order_by:
idx = self.order_names.index(order_name)
# columns.append(text(self.column_names[idx]))
columns.append(self.column_names[idx])

return columns
Expand All @@ -199,10 +223,12 @@ def get_attr_values(
A mapping between the `order_by` values and the attribute values on `obj`
"""
breakpoint()
values = {}
for order_name in order_by:
idx = self.order_names.index(order_name)
values[order_name] = get_nested_attribute(obj, self.column_names[idx])
attr = self.column_names[idx]
values[order_name] = get_nested_attribute(obj, attr)

return values

Expand Down
74 changes: 68 additions & 6 deletions conda-store-server/tests/_internal/server/views/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,16 +1072,78 @@ def test_default_conda_store_dir():
assert dir == f"/home/{user}/.local/share/conda-store"


def test_api_list_environments(
@pytest.mark.parametrize(
"order",
[
"asc",
"desc",
],
)
def test_api_list_environments_by_name(
conda_store_server,
testclient,
seed_conda_store,
seed_conda_store_big,
authenticate,
order,
):
"""Test that the REST API lists the expected paginated environments."""
response = testclient.get("api/v1/environment/?sort_by=name")
"""Test the REST API lists the paginated envs when sorting by name."""
response = testclient.get(f"api/v1/environment/?sort_by=name&order={order}")
response.raise_for_status()

r = schema.APIListEnvironment.parse_obj(response.json())
model = schema.APIListEnvironment.model_validate(response.json())
assert model.status == schema.APIStatus.OK

assert r.status == schema.APIStatus.OK
env_names = [env.name for env in model.data]
assert sorted(env_names, reverse=order == "desc") == env_names


@pytest.mark.parametrize(
"order",
[
"asc",
"desc",
],
)
def test_api_list_environments_by_namespace(
conda_store_server,
testclient,
seed_conda_store_big,
authenticate,
order,
):
"""Test the REST API lists the paginated envs when sorting by namespace."""
response = testclient.get(f"api/v1/environment/?sort_by=namespace&order={order}")
response.raise_for_status()

model = schema.APIListEnvironment.model_validate(response.json())
assert model.status == schema.APIStatus.OK

env_names = [env.namespace.name for env in model.data]
assert sorted(env_names, reverse=order == "desc") == env_names


@pytest.mark.parametrize(
"order",
[
"asc",
"desc",
],
)
def test_api_list_environments_by_namespace_name(
conda_store_server,
testclient,
seed_conda_store_big,
authenticate,
order,
):
"""Test the REST API lists the paginated envs when sorting by namespace."""
response = testclient.get(
f"api/v1/environment/?sort_by=namespace,name&order={order}"
)
response.raise_for_status()

model = schema.APIListEnvironment.model_validate(response.json())
assert model.status == schema.APIStatus.OK

env_names = [env.namespace.name for env in model.data]
assert sorted(env_names, reverse=order == "desc") == env_names
46 changes: 46 additions & 0 deletions conda-store-server/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import datetime
import json
import pathlib
import random
import string
import sys
import typing
import uuid
Expand Down Expand Up @@ -165,6 +167,50 @@ def seed_conda_store(db, conda_store):
return db


@pytest.fixture
def seed_conda_store_big(db, conda_store):
default = {}
namespace1 = {}
namespace2 = {}
for i in range(50):
name = "".join(random.choices(string.ascii_letters, k=10))
default[name] = schema.CondaSpecification(
name=name, channels=["defaults"], dependencies=["numpy"]
)

name = "".join(random.choices(string.ascii_letters, k=11))
namespace1[name] = schema.CondaSpecification(
name=name,
channels=["defaults"],
dependencies=["flask"],
)

name = "".join(random.choices(string.ascii_letters, k=12))
namespace2[name] = schema.CondaSpecification(
name=name,
channels=["defaults"],
dependencies=["flask"],
)

_seed_conda_store(
db,
conda_store,
{
"default": default,
"namespace1": namespace1,
"namespace2": namespace2,
},
)

# for testing purposes make build 4 complete
build = api.get_build(db, build_id=4)
build.started_on = datetime.datetime.utcnow()
build.ended_on = datetime.datetime.utcnow()
build.status = schema.BuildStatus.COMPLETED
db.commit()
return db


@pytest.fixture
def conda_store(conda_store_config):
_conda_store = app.CondaStore(config=conda_store_config)
Expand Down

0 comments on commit f8bf29f

Please sign in to comment.