Skip to content

Commit 62fc048

Browse files
committed
refactor: make client almost client again
1 parent f1455ad commit 62fc048

25 files changed

Lines changed: 871 additions & 805 deletions

‎Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ ENV UV_PYTHON_INSTALL_DIR=/python
2929

3030
# Sync the project without its dev dependencies
3131
RUN --mount=type=cache,target=/root/.cache/uv \
32-
uv sync --locked --no-editable --no-dev --managed-python
32+
uv sync --locked --no-editable --no-dev --extra server --managed-python
3333

3434
RUN uv pip install debugpy
3535

‎pyproject.toml‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,25 @@ classifiers = [
1414
]
1515
description = "Lightweight bluesky-as-a-service wrapper application. Also usable as a library."
1616
dependencies = [
17-
"tiled[client]>=0.2.4",
17+
# Base install is the `blueapi` CLI/client (and usable as a library on its own).
18+
# Add blueapi[server] to also run `blueapi serve`.
1819
"bluesky[plotting]>=1.14.0", # plotting includes matplotlib, required for BestEffortCallback in run plans
1920
"ophyd-async>=0.13.5",
20-
"aioca",
2121
"pydantic>=2.0",
2222
"pydantic-settings",
2323
"stomp-py",
2424
"PyYAML>=6.0.2",
25-
"click>=8.2.0",
26-
"fastapi>=0.112.0",
27-
"uvicorn>=0.52.1",
25+
"click>=8.2.0", # need for blueapi serve and blueapi setup-scratch
2826
"requests",
29-
"GitPython>=3.1.58", #security base min https://github.com/advisories/GHSA-4gmw-gg2m-w46p
27+
"GitPython>=3.1.58", #security base min https://github.com/advisories/GHSA-4gmw-gg2m-w46p - used by the server's /python_environment endpoint and the CLI's scratch setup
3028
"event-model==1.24.0", # https://github.com/DiamondLightSource/blueapi/issues/684
3129
"bluesky-stomp>=0.1.6",
3230
"opentelemetry-distro>=0.48b0",
33-
"opentelemetry-instrumentation-fastapi>=0.48b0",
3431
"observability-utils>=0.1.4",
3532
"pyjwt[crypto]",
36-
"tomlkit",
37-
"graypy>=2.1.0",
38-
"httpx>=0.28.1",
39-
"aiohttp>=3.13.5",
33+
"websockets",
34+
"tqdm",
35+
"packaging",
4036
]
4137
dynamic = ["version"]
4238
license.file = "LICENSE"
@@ -45,9 +41,22 @@ requires-python = ">=3.11"
4541

4642
[project.optional-dependencies]
4743
demo = ["dls-dodal>=1.69.0", "ophyd-async[sim]"]
44+
# Only needed to run `blueapi serve` (the FastAPI worker service).
45+
server = [
46+
"fastapi>=0.112.0",
47+
"uvicorn>=0.52.1",
48+
"aiohttp>=3.13.5",
49+
"opentelemetry-instrumentation-fastapi>=0.48b0",
50+
"graypy>=2.1.0",
51+
"aioca",
52+
"tiled[client]>=0.2.4",
53+
"httpx>=0.28.1", # used by service/numtracker.py and service/authentication.py's TiledAuth
54+
"tomlkit",
55+
]
4856

4957
[dependency-groups]
5058
dev = [
59+
"blueapi[server]",
5160
"ophyd_async[sim]",
5261
"copier",
5362
"dls-dodal>=1.69.0",

‎src/blueapi/cli/cli.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
UnauthorisedAccessError,
3232
UnknownPlanError,
3333
)
34+
from blueapi.client.session import SessionCacheManager, SessionManager
3435
from blueapi.config import (
3536
ApplicationConfig,
3637
ConfigLoader,
3738
)
3839
from blueapi.core import OTLP_EXPORT_ENABLED, DataEvent
3940
from blueapi.log import set_up_logging
40-
from blueapi.service.authentication import SessionCacheManager, SessionManager
4141
from blueapi.service.model import DeviceResponse, PlanResponse, SourceInfo, TaskRequest
4242
from blueapi.worker import ProgressEvent, WorkerEvent
4343
from blueapi.worker.event import TaskError, TaskResult

‎src/blueapi/cli/scratch.py‎

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import importlib.metadata
21
import logging
32
import os
43
import stat
@@ -8,10 +7,8 @@
87
from subprocess import Popen
98

109
from git import Repo
11-
from tomlkit import parse
1210

1311
from blueapi.config import FORBIDDEN_OWN_REMOTE_URL, ScratchConfig
14-
from blueapi.service.model import PackageInfo, PythonEnvironmentResponse, SourceInfo
1512
from blueapi.utils import get_owner_gid, is_sgid_set
1613

1714
_DEFAULT_INSTALL_TIMEOUT: float = 300.0
@@ -178,91 +175,3 @@ def _validate_directory(path: Path) -> None:
178175
raise KeyError(f"{path}: No such file or directory")
179176
elif path.is_file():
180177
raise KeyError(f"{path}: Is a file, not a directory")
181-
182-
183-
def _get_project_name_from_pyproject(path: Path) -> str:
184-
pyproject_path = path / "pyproject.toml"
185-
if pyproject_path.exists():
186-
with pyproject_path.open("r", encoding="utf-8") as file:
187-
toml_data = parse(file.read())
188-
return toml_data.get("project", {}).get("name", "")
189-
return ""
190-
191-
192-
def _fetch_installed_packages_details() -> list[PackageInfo]:
193-
installed_packages = importlib.metadata.distributions()
194-
return [
195-
PackageInfo(
196-
name=dist.metadata["Name"],
197-
version=dist.version,
198-
location=str(dist.locate_file("")),
199-
is_dirty=False,
200-
)
201-
for dist in installed_packages
202-
]
203-
204-
205-
def get_python_environment(
206-
config: ScratchConfig | None,
207-
name: str | None = None,
208-
source: SourceInfo | None = None,
209-
) -> PythonEnvironmentResponse:
210-
"""
211-
Get the Python environment. This includes all installed packages and
212-
the scratch packages.
213-
"""
214-
scratch_packages = {}
215-
packages = []
216-
217-
if config is None:
218-
python_env_response = PythonEnvironmentResponse(scratch_enabled=False)
219-
else:
220-
python_env_response = PythonEnvironmentResponse(scratch_enabled=True)
221-
_validate_directory(config.root)
222-
for repo in config.repositories:
223-
local_directory = config.root / repo.name
224-
repo = Repo(local_directory)
225-
try:
226-
branch = repo.active_branch.name
227-
except TypeError:
228-
branch = repo.head.commit.hexsha
229-
230-
is_dirty = repo.is_dirty()
231-
232-
version = (
233-
f"{repo.remotes[0].url} @{branch}"
234-
if repo.remotes
235-
else f"UNKNOWN REMOTE @{branch}"
236-
)
237-
package_name = _get_project_name_from_pyproject(local_directory)
238-
package_location = ""
239-
240-
packages.append(
241-
PackageInfo(
242-
name=package_name,
243-
version=version,
244-
location=package_location,
245-
source=SourceInfo.SCRATCH,
246-
is_dirty=is_dirty,
247-
)
248-
)
249-
scratch_packages = {p.name: p for p in packages}
250-
251-
for pkg in _fetch_installed_packages_details():
252-
if pkg.name not in scratch_packages:
253-
packages.append(pkg)
254-
else:
255-
scratch_packages[pkg.name].location += f"{pkg.location} &&"
256-
257-
python_env_response.installed_packages = sorted(
258-
packages, key=lambda pkg: pkg.name.lower()
259-
)
260-
if name:
261-
python_env_response.installed_packages = [
262-
p for p in python_env_response.installed_packages if p.name == name
263-
]
264-
if source:
265-
python_env_response.installed_packages = [
266-
p for p in python_env_response.installed_packages if p.source == source
267-
]
268-
return python_env_response

‎src/blueapi/client/client.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
start_as_current_span,
1616
)
1717

18+
from blueapi.client.session import SessionCacheManager, SessionManager
1819
from blueapi.config import (
1920
ApplicationConfig,
2021
ConfigLoader,
2122
MissingStompConfigurationError,
2223
)
2324
from blueapi.core.bluesky_types import DataEvent
24-
from blueapi.service.authentication import SessionCacheManager, SessionManager
2525
from blueapi.service.model import (
2626
DeviceModel,
2727
DeviceResponse,

‎src/blueapi/client/rest.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import json
22
import logging
33
from collections.abc import Callable, Iterable, Mapping
4+
from http import HTTPStatus
45
from typing import Any, Literal, TypeVar
56

67
import requests
7-
from fastapi import status
88
from observability_utils.tracing import (
99
get_context_propagator,
1010
get_tracer,
@@ -17,9 +17,9 @@
1717

1818
from blueapi import __version__
1919
from blueapi.client import client
20+
from blueapi.client.session import JWTAuth, SessionManager
2021
from blueapi.config import RestConfig
2122
from blueapi.core.bluesky_types import DataEvent
22-
from blueapi.service.authentication import JWTAuth, SessionManager
2323
from blueapi.service.model import (
2424
DeviceModel,
2525
DeviceResponse,
@@ -338,7 +338,7 @@ def _request_and_deserialize(
338338
exception = get_exception(response)
339339
if exception is not None:
340340
raise exception
341-
if response.status_code == status.HTTP_204_NO_CONTENT:
341+
if response.status_code == HTTPStatus.NO_CONTENT:
342342
raise NoContentError(target_type)
343343
if (server_version := response.headers.get("x-blueapi-version")) is not None:
344344
from packaging.version import Version

0 commit comments

Comments
 (0)