Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 3 additions & 3 deletions .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Changes here will be overwritten by Copier
_commit: 4.1.0
_commit: 4.3.0
_src_path: https://github.com/DiamondLightSource/python-copier-template
author_email: callum.forrester@diamond.ac.uk
author_name: Callum Forrester
author_email: abigail.emery@diamond.ac.uk
author_name: Abigail Emery
component_lifecycle: production
component_owner: group:default/data-acquisition
component_type: service
Expand Down
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ It is recommended that developers use a [vscode devcontainer](https://code.visua

This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects.

For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/4.1.0/how-to.html).
For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/4.3.0/how-to.html).
4 changes: 2 additions & 2 deletions docs/reference/asyncapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ info:
version: 0.0.2
description: Service for controlling access to and running scans based on Bluesky Plans and Ophyd Devices
contact:
name: Callum Forrester
email: callum.forrester@diamond.ac.uk
name: Abigail Emery
email: abigail.emery@diamond.ac.uk
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
Expand Down
25 changes: 23 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,28 @@ blueapi = "blueapi.cli:main"
GitHub = "https://github.com/DiamondLightSource/blueapi"

[[project.authors]] # Further authors may be added by duplicating this section
email = "callum.forrester@diamond.ac.uk"
name = "Callum Forrester"
email = "abigail.emery@diamond.ac.uk"
name = "Abigail Emery"

[[project.authors]]
email = "[email protected]"
name = "Keith Ralphs"

[[project.authors]]
email = "[email protected]"
name = "Daniel Fernandes"

[[project.authors]]
email = "[email protected]"
name = "Peter Holloway"

[[project.authors]]
email = "[email protected]"
name = "Zoheb Shaikh"

[[project.authors]]
email = "[email protected]"
name = "Joseph Ware"

[tool.setuptools_scm]
version_file = "src/blueapi/_version.py"
Expand All @@ -102,6 +121,7 @@ testpaths = "docs src tests"
asyncio_mode = "auto"

[tool.coverage.run]
patch = ["subprocess"]
data_file = "/tmp/blueapi.coverage"
omit = ["src/blueapi/startup/**/*"]

Expand Down Expand Up @@ -142,6 +162,7 @@ lint.select = [
"C4", # flake8-comprehensions - https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4
"E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e
"F", # pyflakes rules - https://docs.astral.sh/ruff/rules/#pyflakes-f
"N", # pep8-naming - https://docs.astral.sh/ruff/rules/#pep8-naming-n
"W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w
"I", # isort - https://docs.astral.sh/ruff/rules/#isort-i
"UP", # pyupgrade - https://docs.astral.sh/ruff/rules/#pyupgrade-up
Expand Down
16 changes: 8 additions & 8 deletions src/blueapi/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
from blueapi.client.event_bus import AnyEvent, BlueskyStreamingError, EventBusClient
from blueapi.client.rest import (
BlueskyRemoteControlError,
InvalidParameters,
UnauthorisedAccess,
UnknownPlan,
InvalidParametersError,
UnauthorisedAccessError,
UnknownPlanError,
)
from blueapi.config import (
ApplicationConfig,
Expand Down Expand Up @@ -303,7 +303,7 @@ def run_plan(
instrument_session=instrument_session,
)
except ValidationError as ve:
ip = InvalidParameters.from_validation_error(ve)
ip = InvalidParametersError.from_validation_error(ve)
raise ClickException(ip.message()) from ip

try:
Expand All @@ -324,13 +324,13 @@ def on_event(event: AnyEvent) -> None:
else:
server_task = client.create_and_start_task(task)
click.echo(server_task.task_id)
except config.MissingStompConfiguration as mse:
except config.MissingStompConfigurationError as mse:
raise ClickException(*mse.args) from mse
except UnknownPlan as up:
except UnknownPlanError as up:
raise ClickException(f"Plan '{name}' was not recognised") from up
except UnauthorisedAccess as ua:
except UnauthorisedAccessError as ua:
raise ClickException("Unauthorised request") from ua
except InvalidParameters as ip:
except InvalidParametersError as ip:
raise ClickException(ip.message()) from ip
except (BlueskyRemoteControlError, BlueskyStreamingError) as e:
raise ClickException(f"server error with this message: {e}") from e
Expand Down
4 changes: 2 additions & 2 deletions src/blueapi/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
start_as_current_span,
)

from blueapi.config import ApplicationConfig, MissingStompConfiguration
from blueapi.config import ApplicationConfig, MissingStompConfigurationError
from blueapi.core.bluesky_types import DataEvent
from blueapi.service.authentication import SessionManager
from blueapi.service.model import (
Expand Down Expand Up @@ -217,7 +217,7 @@ def run_task(
"""

if self._events is None:
raise MissingStompConfiguration(
raise MissingStompConfigurationError(
"Stomp configuration required to run plans is missing or disabled"
)

Expand Down
18 changes: 9 additions & 9 deletions src/blueapi/client/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
TRACER = get_tracer("rest")


class UnauthorisedAccess(Exception):
class UnauthorisedAccessError(Exception):
pass


Expand All @@ -46,7 +46,7 @@ def __init__(self, code: int, message: str) -> None:
super().__init__(message, code)


class NoContent(Exception):
class NoContentError(Exception):
"""Request returned 204 (No Content): handle if None is allowed"""

def __init__(self, target_type: type) -> None:
Expand Down Expand Up @@ -74,7 +74,7 @@ def __str__(self) -> str:
)


class InvalidParameters(Exception):
class InvalidParametersError(Exception):
def __init__(self, errors: list[ParameterError]):
self.errors = errors

Expand All @@ -96,7 +96,7 @@ def from_validation_error(cls, ve: ValidationError):
)


class UnknownPlan(Exception):
class UnknownPlanError(Exception):
pass


Expand All @@ -115,13 +115,13 @@ def _create_task_exceptions(response: requests.Response) -> Exception | None:
if code < 400:
return None
elif code == 401 or code == 403:
return UnauthorisedAccess()
return UnauthorisedAccessError()
elif code == 404:
return UnknownPlan()
return UnknownPlanError()
elif code == 422:
try:
content = response.json()
return InvalidParameters(
return InvalidParametersError(
TypeAdapter(list[ParameterError]).validate_python(
content.get("detail", [])
)
Expand Down Expand Up @@ -226,7 +226,7 @@ def delete_environment(self) -> EnvironmentResponse:
def get_oidc_config(self) -> OIDCConfig | None:
try:
return self._request_and_deserialize("/config/oidc", OIDCConfig)
except NoContent:
except NoContentError:
# Server is not using authentication
return None

Expand Down Expand Up @@ -264,6 +264,6 @@ def _request_and_deserialize(
if exception is not None:
raise exception
if response.status_code == status.HTTP_204_NO_CONTENT:
raise NoContent(target_type)
raise NoContentError(target_type)
deserialized = TypeAdapter(target_type).validate_python(response.json())
return deserialized
2 changes: 1 addition & 1 deletion src/blueapi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,5 +321,5 @@ def load(self) -> C:
) from exc


class MissingStompConfiguration(Exception):
class MissingStompConfigurationError(Exception):
pass
4 changes: 2 additions & 2 deletions src/blueapi/utils/connect_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _report_successful_devices(


def _establish_device_connections(
RE: RunEngine,
run_engine: RunEngine,
devices: Mapping[str, AnyDevice],
sim_backend: bool,
) -> tuple[Mapping[str, AnyDevice], Mapping[str, Exception]]:
Expand All @@ -45,7 +45,7 @@ def _establish_device_connections(

# Connect ophyd-async devices
try:
RE(ensure_connected(*ophyd_async_devices.values(), mock=sim_backend))
run_engine(ensure_connected(*ophyd_async_devices.values(), mock=sim_backend))
except NotConnected as ex:
exceptions = {**exceptions, **ex.sub_errors}

Expand Down
12 changes: 6 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@


@pytest.fixture(scope="function")
def RE(request):
def run_engine(request):
loop = asyncio.new_event_loop()
loop.set_debug(True)
RE = RunEngine({}, call_returns_result=True, loop=loop)
run_engine = RunEngine({}, call_returns_result=True, loop=loop)

def clean_event_loop():
if RE.state not in ("idle", "panicked"):
if run_engine.state not in ("idle", "panicked"):
try:
RE.halt()
run_engine.halt()
except TransitionError:
pass
loop.call_soon_threadsafe(loop.stop)
RE._th.join()
run_engine._th.join()
loop.close()

request.addfinalizer(clean_event_loop)
return RE
return run_engine


@pytest.fixture(scope="session")
Expand Down
4 changes: 2 additions & 2 deletions tests/system_tests/test_blueapi_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
BlueskyRemoteControlError,
)
from blueapi.client.event_bus import AnyEvent
from blueapi.client.rest import UnknownPlan
from blueapi.client.rest import UnknownPlanError
from blueapi.config import (
ApplicationConfig,
ConfigLoader,
Expand Down Expand Up @@ -253,7 +253,7 @@ def test_instrument_session_propagated(client: BlueapiClient):


def test_create_task_validation_error(client: BlueapiClient):
with pytest.raises(UnknownPlan):
with pytest.raises(UnknownPlanError):
client.create_task(
TaskRequest(
name="Not-exists",
Expand Down
6 changes: 3 additions & 3 deletions tests/unit_tests/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from blueapi.client.client import BlueapiClient
from blueapi.client.event_bus import AnyEvent, BlueskyStreamingError, EventBusClient
from blueapi.client.rest import BlueapiRestClient, BlueskyRemoteControlError
from blueapi.config import MissingStompConfiguration
from blueapi.config import MissingStompConfigurationError
from blueapi.core import DataEvent
from blueapi.service.model import (
DeviceModel,
Expand Down Expand Up @@ -394,7 +394,7 @@ def test_resume(

def test_cannot_run_task_without_message_bus(client: BlueapiClient):
with pytest.raises(
MissingStompConfiguration,
MissingStompConfigurationError,
match="Stomp configuration required to run plans is missing or disabled",
):
client.run_task(TaskRequest(name="foo", instrument_session="cm12345-1"))
Expand Down Expand Up @@ -663,7 +663,7 @@ def test_cannot_run_task_span_ok(
exporter: JsonObjectSpanExporter, client: BlueapiClient
):
with pytest.raises(
MissingStompConfiguration,
MissingStompConfigurationError,
match="Stomp configuration required to run plans is missing or disabled",
):
with asserting_span_exporter(exporter, "grun_task"):
Expand Down
14 changes: 7 additions & 7 deletions tests/unit_tests/client/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
BlueapiRestClient,
BlueskyRemoteControlError,
BlueskyRequestError,
InvalidParameters,
InvalidParametersError,
ParameterError,
UnauthorisedAccess,
UnknownPlan,
UnauthorisedAccessError,
UnknownPlanError,
_create_task_exceptions,
)
from blueapi.config import OIDCConfig
Expand Down Expand Up @@ -63,9 +63,9 @@ def test_rest_error_code(
"code,content,expected_exception",
[
(200, None, None),
(401, None, UnauthorisedAccess()),
(403, None, UnauthorisedAccess()),
(404, None, UnknownPlan()),
(401, None, UnauthorisedAccessError()),
(403, None, UnauthorisedAccessError()),
(404, None, UnknownPlanError()),
(
422,
"""{
Expand All @@ -76,7 +76,7 @@ def test_rest_error_code(
"input": {}
}]
}""",
InvalidParameters(
InvalidParametersError(
[
ParameterError(
loc=["body", "params", "foo"],
Expand Down
8 changes: 4 additions & 4 deletions tests/unit_tests/core/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ def has_default_nested_reference(


@pytest.fixture
def sim_motor(RE: RunEngine) -> Motor:
def sim_motor(run_engine: RunEngine) -> Motor:
with init_devices(mock=True):
sim = Motor("FOO:")
return sim


@pytest.fixture
def alt_motor(RE: RunEngine) -> Motor:
def alt_motor(run_engine: RunEngine) -> Motor:
with init_devices(mock=True):
alt = Motor("BAR:")
return alt
Expand Down Expand Up @@ -298,8 +298,8 @@ def test_extra_kwargs_in_with_dodal_module_passed_to_make_all_devices(
def test_with_dodal_module_returns_connection_exceptions(empty_context: BlueskyContext):
import tests.unit_tests.core.fake_device_module as device_module

def connect_sim_backend(RE, devices, sim_backend):
return _establish_device_connections(RE, devices, True)
def connect_sim_backend(run_engine: RunEngine, devices, sim_backend):
return _establish_device_connections(run_engine, devices, True)

with patch(
"blueapi.utils.connect_devices._establish_device_connections",
Expand Down
3 changes: 1 addition & 2 deletions tests/unit_tests/service/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,12 @@ def test_rest_config_with_cors(
)
task_id = "f8424be3-203c-494e-b22f-219933b4fa67"
mock_runner.run.side_effect = [task_id]
HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}

# Allowed method
response_post = client_with_cors.post(
"/tasks",
json=task.model_dump(),
headers=HEADERS,
headers={"Accept": "application/json", "Content-Type": "application/json"},
)
assert response_post.status_code == status.HTTP_201_CREATED
assert response_post.headers["content-type"] == "application/json"
Expand Down
Loading