Skip to content

Commit

Permalink
refactor(robot-server): Ensure SQL selects are ordered (#14535)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxColoring authored and Carlos-fernandez committed May 20, 2024
1 parent f0855a0 commit 4db3c13
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 26 deletions.
20 changes: 11 additions & 9 deletions robot-server/robot_server/protocols/protocol_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,10 @@ def get(self, protocol_id: str) -> ProtocolResource:

@lru_cache(maxsize=_CACHE_ENTRIES)
def get_all(self) -> List[ProtocolResource]:
"""Get all protocols currently saved in this store."""
"""Get all protocols currently saved in this store.
Results are ordered from first-added to last-added.
"""
all_sql_resources = self._sql_get_all()
return [
ProtocolResource(
Expand Down Expand Up @@ -297,17 +300,16 @@ def get_referencing_run_ids(self, protocol_id: str) -> List[str]:
See the `runs` module for information about runs.
Results are ordered with the oldest-added (NOT created) run first.
Results are ordered with the oldest run first.
"""
select_referencing_run_ids = sqlalchemy.select(run_table.c.id).where(
run_table.c.protocol_id == protocol_id
select_referencing_run_ids = (
sqlalchemy.select(run_table.c.id)
.where(run_table.c.protocol_id == protocol_id)
.order_by(sqlite_rowid)
)

with self._sql_engine.begin() as transaction:
referencing_run_ids = (
transaction.execute(select_referencing_run_ids).scalars().all()
)
return referencing_run_ids
return transaction.execute(select_referencing_run_ids).scalars().all()

def _sql_insert(self, resource: _DBProtocolResource) -> None:
statement = sqlalchemy.insert(protocol_table).values(
Expand All @@ -334,7 +336,7 @@ def _sql_get_all(self) -> List[_DBProtocolResource]:
def _sql_get_all_from_engine(
sql_engine: sqlalchemy.engine.Engine,
) -> List[_DBProtocolResource]:
statement = sqlalchemy.select(protocol_table)
statement = sqlalchemy.select(protocol_table).order_by(sqlite_rowid)
with sql_engine.begin() as transaction:
all_rows = transaction.execute(statement).all()
return [_convert_sql_row_to_dataclass(sql_row=row) for row in all_rows]
Expand Down
15 changes: 13 additions & 2 deletions robot-server/robot_server/protocols/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ class ProtocolLinks(BaseModel):

referencingRuns: List[RunLink] = Field(
...,
description="Links to runs that reference the protocol.",
description=(
"Links to runs that reference the protocol,"
" in order from the oldest run to the newest run."
),
)


Expand All @@ -129,11 +132,18 @@ class ProtocolLinks(BaseModel):
When too many protocols already exist, old ones will be automatically deleted
to make room for the new one.
A protocol will never be automatically deleted if there's a run
referring to it, though.
referring to it, though. (See the `/runs/` endpoints.)
If you upload the exact same set of files multiple times, the first protocol
resource will be returned instead of creating duplicate ones.
When a new protocol resource is created, an analysis is started for it.
See the `/protocols/{id}/analyses/` endpoints.
"""
),
status_code=status.HTTP_201_CREATED,
responses={
status.HTTP_200_OK: {"model": SimpleBody[Protocol]},
status.HTTP_201_CREATED: {"model": SimpleBody[Protocol]},
status.HTTP_422_UNPROCESSABLE_ENTITY: {
"model": ErrorBody[Union[ProtocolFilesInvalid, ProtocolRobotTypeMismatch]]
Expand Down Expand Up @@ -280,6 +290,7 @@ async def create_protocol(
protocols_router.get,
path="/protocols",
summary="Get uploaded protocols",
description="Return all stored protocols, in order from first-uploaded to last-uploaded.",
responses={status.HTTP_200_OK: {"model": SimpleMultiBody[Protocol]}},
)
async def get_protocols(
Expand Down
4 changes: 3 additions & 1 deletion robot-server/robot_server/runs/router/base_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@ async def create_run(
base_router.get,
path="/runs",
summary="Get all runs",
description="Get a list of all active and inactive runs.",
description=(
"Get a list of all active and inactive runs, in order from oldest to newest."
),
responses={
status.HTTP_200_OK: {"model": MultiBody[Run, AllRunsLinks]},
},
Expand Down
3 changes: 3 additions & 0 deletions robot-server/robot_server/runs/router/commands_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ async def create_run_command(
summary="Get a list of all protocol commands in the run",
description=(
"Get a list of all commands in the run and their statuses. "
"\n\n"
"The commands are returned in order from oldest to newest."
"\n\n"
"This endpoint returns command summaries. Use "
"`GET /runs/{runId}/commands/{commandId}` to get all "
"information available for a given command."
Expand Down
6 changes: 4 additions & 2 deletions robot-server/robot_server/runs/run_data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,10 @@ def get_run_loaded_labware_definitions(
def get_all(self, length: Optional[int]) -> List[Run]:
"""Get current and stored run resources.
Returns:
All run resources.
Results are ordered from oldest to newest.
Params:
length: If `None`, return all runs. Otherwise, return the newest n runs.
"""
return [
_build_run(
Expand Down
2 changes: 1 addition & 1 deletion robot-server/robot_server/runs/run_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class Run(ResourceModel):
)
actions: List[RunAction] = Field(
...,
description="Client-initiated run control actions.",
description="Client-initiated run control actions, ordered oldest to newest.",
)
errors: List[ErrorOccurrence] = Field(
...,
Expand Down
28 changes: 17 additions & 11 deletions robot-server/robot_server/runs/run_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ def update_run_state(
select_run_resource = sqlalchemy.select(*_run_columns).where(
run_table.c.id == run_id
)
select_actions = sqlalchemy.select(action_table).where(
action_table.c.run_id == run_id
select_actions = (
sqlalchemy.select(action_table)
.where(action_table.c.run_id == run_id)
.order_by(sqlite_rowid)
)

with self._sql_engine.begin() as transaction:
Expand Down Expand Up @@ -220,8 +222,10 @@ def get(self, run_id: str) -> RunResource:
run_table.c.id == run_id
)

select_actions = sqlalchemy.select(action_table).where(
action_table.c.run_id == run_id
select_actions = (
sqlalchemy.select(action_table)
.where(action_table.c.run_id == run_id)
.order_by(sqlite_rowid)
)

with self._sql_engine.begin() as transaction:
Expand All @@ -237,26 +241,28 @@ def get(self, run_id: str) -> RunResource:
def get_all(self, length: Optional[int] = None) -> List[RunResource]:
"""Get all known run resources.
Returns:
All stored run entries.
Results are ordered from oldest to newest.
Params:
length: If `None`, return all runs. Otherwise, return the newest n runs.
"""
select_runs = sqlalchemy.select(*_run_columns)
select_actions = sqlalchemy.select(action_table).order_by(sqlite_rowid.asc())
actions_by_run_id = defaultdict(list)

with self._sql_engine.begin() as transaction:
if length is not None:
select_runs = (
select_runs.limit(length)
sqlalchemy.select(*_run_columns)
.order_by(sqlite_rowid.desc())
.limit(length)
)
# need to select the last inserted runs and return by asc order
runs = list(reversed(transaction.execute(select_runs).all()))
else:
runs = transaction.execute(
select_runs.order_by(sqlite_rowid.asc())
).all()
select_runs = sqlalchemy.select(*_run_columns).order_by(
sqlite_rowid.asc()
)
runs = transaction.execute(select_runs).all()

actions = transaction.execute(select_actions).all()

Expand Down

0 comments on commit 4db3c13

Please sign in to comment.