Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/_tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ jobs:
run: helm plugin install https://github.com/losisin/helm-values-schema-json.git --version 2.3.1

- name: Run tox
env:
# To allow pyright version to be latest and not decided by the uv.lock
PYRIGHT_PYTHON_FORCE_VERSION: latest
run: uv run --locked tox -e ${{ inputs.tox }}
7 changes: 4 additions & 3 deletions .github/workflows/asyncapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches:
- main
tags:
- '*'
- "*"
pull_request:
merge_group:
types: [checks_requested]
Expand All @@ -17,8 +17,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: check asyncapi.yaml file
uses: WaleedAshraf/asyncapi-github-action@d31f1627ac7c24132e7c4e4b89948d43b619685a # v0.0.10
uses: asyncapi/cli@fb73ff3a429c1bc6a14c1b89f12601288677b9fa # v6.0.2
with:
command: validate
filepath: "./docs/reference/asyncapi.yaml"
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
docs: # Call the docs building workflow directly
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/docs.yml@v0.22.0
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/docs.yml@v0.23.0
with:
# Whatever turns your sources into docs/_build/html at $BASE_URL. uv and Node
# are preinstalled, so: make docs · tox -e docs · npm ci && npm run docs
Expand All @@ -18,7 +18,7 @@ jobs:
release:
needs: [docs]
if: github.ref_type == 'tag' # tag pushes only
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/release.yml@v0.22.0
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/release.yml@v0.23.0
permissions:
contents: write # create the Release + attach assets

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:

jobs:
publish:
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/publish.yml@v0.22.0
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/publish.yml@v0.23.0
with:
# "" on dispatch → pure durable gather
version-name: ${{ inputs.version-name }}
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11
3.14
22 changes: 17 additions & 5 deletions docs/reference/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ components:
- type
title: TaskResult
type: object
TaskStatusEnum:
enum:
- PENDING
- COMPLETE
- ERROR
- RUNNING
title: TaskStatusEnum
type: string
TasksListResponse:
additionalProperties: false
description: Diagnostic information on the tasks
Expand Down Expand Up @@ -441,7 +449,7 @@ info:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
title: BlueAPI Control
version: 1.4.0
version: 1.4.1
openapi: 3.1.0
paths:
/api/v1/devices:
Expand Down Expand Up @@ -599,15 +607,17 @@ paths:
get:
description: 'Retrieve tasks based on their status.

The status of a newly created task is ''unstarted''.'
The status of a newly created task is PENDING.'
operationId: get_tasks_api_v1_tasks_get
parameters:
- in: query
name: task_status
required: false
schema:
anyOf:
- $ref: '#/components/schemas/TaskStatusEnum'
- type: 'null'
title: Task Status
type: string
responses:
'200':
content:
Expand Down Expand Up @@ -997,15 +1007,17 @@ paths:
deprecated: true
description: 'Retrieve tasks based on their status.

The status of a newly created task is ''unstarted''.'
The status of a newly created task is PENDING.'
operationId: get_tasks_tasks_get
parameters:
- in: query
name: task_status
required: false
schema:
anyOf:
- $ref: '#/components/schemas/TaskStatusEnum'
- type: 'null'
title: Task Status
type: string
responses:
'200':
content:
Expand Down
12 changes: 6 additions & 6 deletions prek.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,32 @@ hooks = [

[[repos]]
repo = "https://github.com/astral-sh/ruff-pre-commit"
rev = "v0.15.1"
rev = "v0.15.20"
hooks = [
{ id = "ruff-check", args = ["--fix"] },
{ id = "ruff-format" },
]

[[repos]]
repo = "https://github.com/astral-sh/uv-pre-commit"
rev = "0.10.2"
rev = "0.11.27"
hooks = [
{ id = "uv-sync", args = [] },
]

[[repos]]
repo = "https://github.com/norwoodj/helm-docs"
rev = "v1.14.2"
hooks = [{ id = "helm-docs-built", args = ["--chart-search-root=helm"] }]
hooks = [{ id = "helm-docs-container", args = ["--chart-search-root=helm"] }]

[[repos]]
repo = "https://github.com/losisin/helm-values-schema-json"
rev = "v2.3.1"
rev = "v2.5.0"
hooks = [
{ id = "helm-schema", args = ["--config", "helm/blueapi/.schema.yaml"] }
]

[[repos]]
repo = "https://github.com/gitleaks/gitleaks"
rev = "v8.28.0"
hooks = [{ id = "gitleaks" }]
rev = "v8.30.1"
hooks = [{ id = "gitleaks-docker" }]
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ dev = [
"tiled[minimal-server]>=0.2.4", # For system-test of dls.py
"respx",
"pytest-xdist[psutil]",
"httpx2>=2.5.0"
]

[project.scripts]
Expand Down
2 changes: 1 addition & 1 deletion src/blueapi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ class ApplicationConfig(BlueapiBaseModel):
"""

#: API version to publish in OpenAPI schema
REST_API_VERSION: ClassVar[str] = "1.4.0"
REST_API_VERSION: ClassVar[str] = "1.4.1"

LICENSE_INFO: ClassVar[dict[str, str]] = {
"name": "Apache 2.0",
Expand Down
13 changes: 4 additions & 9 deletions src/blueapi/service/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,6 @@ def remove_callback_when_task_finished(
return task


def get_tasks_by_status(status: TaskStatusEnum) -> list[TrackableTask]:
"""Retrieve a list of tasks based on their status."""
return worker().get_tasks_by_status(status)


def get_active_task() -> TrackableTask | None:
"""Task the worker is currently running"""
return worker().get_active_task()
Expand All @@ -264,10 +259,10 @@ def cancel_active_task(failure: bool, reason: str | None) -> str:
return worker().cancel_active_task(failure, reason)


def get_tasks() -> list[TrackableTask]:
"""Return a list of all tasks on the worker,
any one of which can be triggered with begin_task"""
return worker().get_tasks()
def get_tasks(status: TaskStatusEnum | None = None) -> list[TrackableTask]:
"""Retrieve a list of tasks based on their status.
Return a list of all tasks on the worker if status is None"""
return worker().get_tasks(status)


def get_task_by_id(task_id: str) -> TrackableTask | None:
Expand Down
41 changes: 12 additions & 29 deletions src/blueapi/service/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.trace import get_tracer_provider
from pydantic import ValidationError
from pydantic.json_schema import SkipJsonSchema
from starlette.responses import JSONResponse
from super_state_machine.errors import TransitionError

Expand Down Expand Up @@ -296,8 +295,9 @@ def submit_task(
for err in e.errors()
]

LOGGER.info("Error submitting task: %s - %s", task_request, e)
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=errors,
) from e

Expand All @@ -316,38 +316,18 @@ def delete_submitted_task(
return TaskResponse(task_id=runner.run(interface.clear_task, task_id))


@start_as_current_span(TRACER, "v")
def validate_task_status(v: str) -> TaskStatusEnum:
v_upper = v.upper()
if v_upper not in TaskStatusEnum.__members__:
raise ValueError("Invalid status query parameter")
return TaskStatusEnum(v_upper)


@secure_router_v1.get("/tasks", status_code=status.HTTP_200_OK, tags=[Tag.TASK])
@secure_router.get("/tasks", status_code=status.HTTP_200_OK, tags=[Tag.TASK])
@start_as_current_span(TRACER)
def get_tasks(
runner: Annotated[WorkerDispatcher, Depends(_runner)],
task_status: str | SkipJsonSchema[None] = None,
task_status: TaskStatusEnum | None = None,
) -> TasksListResponse:
"""
Retrieve tasks based on their status.
The status of a newly created task is 'unstarted'.
The status of a newly created task is PENDING.
"""
if task_status:
add_span_attributes({"status": task_status})
try:
desired_status = validate_task_status(task_status)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid status query parameter",
) from e

tasks = runner.run(interface.get_tasks_by_status, desired_status)
else:
tasks = runner.run(interface.get_tasks)
tasks = runner.run(interface.get_tasks, task_status)
return TasksListResponse(tasks=tasks)


Expand Down Expand Up @@ -594,16 +574,19 @@ def start(config: ApplicationConfig):
async def log_request_details(
request: Request, call_next: Callable[[Request], Awaitable[StreamingResponse]]
) -> Response:
"""Middleware to log all request's host, method, path, status and request and
body"""
log = LOGGER.debug if request.url.path == "/healthz" else LOGGER.info
"""Middleware to log request's host, method, path, status and request and
body

Logs GET as debug, all else as info."""
log = LOGGER.debug if request.method == "GET" else LOGGER.info
request_body = await request.body()
client = request.client or Address("Unknown", -1)
log_message = f"{client.host}:{client.port} {request.method} {request.url.path}"
extra = {
"request_body": request_body,
}
log(log_message, extra=extra)
if request.method != "GET":
log(log_message, extra=extra)

response = await call_next(request)
log_message += f" {response.status_code}"
Expand Down
21 changes: 8 additions & 13 deletions src/blueapi/worker/task_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,16 +194,6 @@ def cancel_active_task(
add_span_attributes({"Task stopped": reason or default_reason})
return self._current.task_id

@start_as_current_span(TRACER)
def get_tasks(self) -> list[TrackableTask]:
"""
Return a list of all tasks on the worker,
any one of which can be triggered with begin_task.
Returns:
List[TrackableTask[T]]: List of task objects
"""
return list(self._pending_tasks.values()) + list(self._completed_tasks.values())

@start_as_current_span(TRACER, "task_id")
def get_task_by_id(self, task_id: str) -> TrackableTask | None:
"""
Expand All @@ -217,12 +207,13 @@ def get_task_by_id(self, task_id: str) -> TrackableTask | None:
"""
return self._pending_tasks.get(task_id, None) or self._completed_tasks[task_id]

@start_as_current_span(TRACER, "status")
def get_tasks_by_status(self, status: TaskStatusEnum) -> list[TrackableTask]:
@start_as_current_span(TRACER)
def get_tasks(self, status: TaskStatusEnum | None = None) -> list[TrackableTask]:
"""
Retrieve a list of tasks based on their status.
Args:
status TaskStatusEnum: The status to filter tasks by.
status Optional[TaskStatusEnum]: The status to filter tasks by.
If status is None return all tasks.
Returns:
list[TrackableTask]: A list of tasks that match the given status.
"""
Expand All @@ -236,6 +227,10 @@ def get_tasks_by_status(self, status: TaskStatusEnum) -> list[TrackableTask]:
return [task for task in self._pending_tasks.values() if task.is_pending]
elif status == TaskStatusEnum.COMPLETE:
return list(self._completed_tasks.values())
elif status is None:
return list(self._pending_tasks.values()) + list(
self._completed_tasks.values()
)
return []

@start_as_current_span(TRACER)
Expand Down
Loading
Loading