Skip to content
Open
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
70 changes: 59 additions & 11 deletions helm/blueapi/config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,41 @@
"title": "CORSConfig",
"type": "object"
},
"DodalSource": {
"additionalProperties": false,
"properties": {
"kind": {
"const": "dodal",
"default": "dodal",
"title": "Kind",
"type": "string"
},
"module": {
"anyOf": [
{
"format": "path",
"type": "string"
},
{
"type": "string"
}
],
"description": "Module name or path to the module to be imported",
"title": "Module"
},
"mock": {
"default": false,
"description": "If true, ophyd_async device connections are mocked",
"title": "Mock",
"type": "boolean"
}
},
"required": [
"module"
],
"title": "DodalSource",
"type": "object"
},
"EnvironmentConfig": {
"additionalProperties": false,
"description": "Config for the RunEngine environment",
Expand All @@ -80,7 +115,22 @@
}
],
"items": {
"$ref": "#/$defs/Source"
"discriminator": {
"mapping": {
"deviceFunctions": "#/$defs/Source",
"dodal": "#/$defs/DodalSource",
"planFunctions": "#/$defs/Source"
},
"propertyName": "kind"
},
"oneOf": [
{
"$ref": "#/$defs/Source"
},
{
"$ref": "#/$defs/DodalSource"
}
]
},
"title": "Sources",
"type": "array"
Expand Down Expand Up @@ -295,7 +345,13 @@
"additionalProperties": false,
"properties": {
"kind": {
"$ref": "#/$defs/SourceKind"
"description": "The type of source module",
"enum": [
"planFunctions",
"deviceFunctions"
],
"title": "Kind",
"type": "string"
},
"module": {
"anyOf": [
Expand All @@ -307,6 +363,7 @@
"type": "string"
}
],
"description": "Module name or path to the module to be imported",
"title": "Module"
}
},
Expand All @@ -317,15 +374,6 @@
"title": "Source",
"type": "object"
},
"SourceKind": {
"enum": [
"planFunctions",
"deviceFunctions",
"dodal"
],
"title": "SourceKind",
"type": "string"
},
"StompConfig": {
"additionalProperties": false,
"description": "Config for connecting to stomp broker",
Expand Down
60 changes: 49 additions & 11 deletions helm/blueapi/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,39 @@
},
"additionalProperties": false
},
"DodalSource": {
"title": "DodalSource",
"type": "object",
"required": [
"module"
],
"properties": {
"kind": {
"title": "Kind",
"default": "dodal",
"const": "dodal"
},
"mock": {
"title": "Mock",
"description": "If true, ophyd_async device connections are mocked",
"default": false,
"type": "boolean"
},
"module": {
"title": "Module",
"description": "Module name or path to the module to be imported",
"anyOf": [
{
"type": "string"
},
{
"type": "string"
}
]
}
},
"additionalProperties": false
},
"EnvironmentConfig": {
"title": "EnvironmentConfig",
"description": "Config for the RunEngine environment",
Expand Down Expand Up @@ -585,7 +618,14 @@
],
"type": "array",
"items": {
"$ref": "#/$defs/Source"
"oneOf": [
{
"$ref": "#/$defs/Source"
},
{
"$ref": "#/$defs/DodalSource"
}
]
}
}
},
Expand Down Expand Up @@ -782,10 +822,17 @@
],
"properties": {
"kind": {
"$ref": "#/$defs/SourceKind"
"title": "Kind",
"description": "The type of source module",
"type": "string",
"enum": [
"planFunctions",
"deviceFunctions"
]
},
"module": {
"title": "Module",
"description": "Module name or path to the module to be imported",
"anyOf": [
{
"type": "string"
Expand All @@ -798,15 +845,6 @@
},
"additionalProperties": false
},
"SourceKind": {
"title": "SourceKind",
"type": "string",
"enum": [
"planFunctions",
"deviceFunctions",
"dodal"
]
},
"StompConfig": {
"title": "StompConfig",
"description": "Config for connecting to stomp broker",
Expand Down
19 changes: 15 additions & 4 deletions src/blueapi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from functools import cached_property
from pathlib import Path
from string import Template
from typing import Any, Generic, Literal, TypeVar, cast
from typing import Annotated, Any, Generic, Literal, TypeVar, cast

import requests
import yaml
Expand Down Expand Up @@ -50,8 +50,19 @@ class SourceKind(str, Enum):


class Source(BlueapiBaseModel):
kind: SourceKind
module: Path | str
kind: Literal[SourceKind.PLAN_FUNCTIONS, SourceKind.DEVICE_FUNCTIONS] = Field(
description="The type of source module"
)
module: Path | str = Field(
description="Module name or path to the module to be imported"
)


class DodalSource(Source):
kind: Literal[SourceKind.DODAL] = SourceKind.DODAL # pyright: ignore [reportIncompatibleVariableOverride]
mock: bool = Field(
description="If true, ophyd_async device connections are mocked", default=False
)


class TcpUrl(AnyUrl):
Expand Down Expand Up @@ -101,7 +112,7 @@ class EnvironmentConfig(BlueapiBaseModel):
Config for the RunEngine environment
"""

sources: list[Source] = [
sources: list[Annotated[Source | DodalSource, Field(discriminator="kind")]] = [
Source(kind=SourceKind.PLAN_FUNCTIONS, module="dodal.plans"),
Source(kind=SourceKind.PLAN_FUNCTIONS, module="dodal.plan_stubs.wrapped"),
]
Expand Down
5 changes: 3 additions & 2 deletions src/blueapi/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from blueapi import utils
from blueapi.client.numtracker import NumtrackerClient
from blueapi.config import ApplicationConfig, EnvironmentConfig, SourceKind
from blueapi.config import ApplicationConfig, DodalSource, EnvironmentConfig, SourceKind
from blueapi.utils import (
BlueapiPlanModelConfig,
is_function_sourced_from_module,
Expand Down Expand Up @@ -173,7 +173,8 @@ def with_config(self, config: EnvironmentConfig) -> None:
elif source.kind is SourceKind.DEVICE_FUNCTIONS:
self.with_device_module(mod)
elif source.kind is SourceKind.DODAL:
self.with_dodal_module(mod)
assert isinstance(source, DodalSource)
self.with_dodal_module(mod, mock=source.mock)

def with_plan_module(self, module: ModuleType) -> None:
"""
Expand Down
34 changes: 32 additions & 2 deletions tests/unit_tests/core/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from types import NoneType
from typing import Generic, TypeVar, Union
from unittest.mock import patch
from unittest.mock import ANY, patch

import pytest
from bluesky.protocols import (
Expand All @@ -30,7 +30,13 @@
from pydantic.json_schema import SkipJsonSchema
from pytest import LogCaptureFixture

from blueapi.config import EnvironmentConfig, MetadataConfig, Source, SourceKind
from blueapi.config import (
DodalSource,
EnvironmentConfig,
MetadataConfig,
Source,
SourceKind,
)
from blueapi.core import BlueskyContext, is_bluesky_compatible_device
from blueapi.core.context import DefaultFactory, generic_bounds, qualified_name
from blueapi.utils.connect_devices import _establish_device_connections
Expand Down Expand Up @@ -393,6 +399,30 @@ def test_add_metadata_with_config(
assert md in empty_context.run_engine.md.items()


@pytest.mark.parametrize("mock", [True, False])
def test_with_config_passes_mock_to_with_dodal_module(
empty_context: BlueskyContext,
mock: bool,
):
with patch.object(empty_context, "with_dodal_module") as mock_with_dodal_module:
empty_context.with_config(
EnvironmentConfig(
sources=[
DodalSource(
kind=SourceKind.DODAL,
module="tests.unit_tests.core.fake_device_module",
mock=mock,
),
Source(
kind=SourceKind.PLAN_FUNCTIONS,
module="tests.unit_tests.core.fake_plan_module",
),
]
)
)
mock_with_dodal_module.assert_called_once_with(ANY, mock=mock)


def test_function_spec(empty_context: BlueskyContext):
spec = empty_context._type_spec_for_function(has_some_params)
assert spec["foo"][0] is int
Expand Down
18 changes: 14 additions & 4 deletions tests/unit_tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ def is_subset(subset: Mapping[str, Any], superset: Mapping[str, Any]) -> bool:
if isinstance(value, dict) and isinstance(superset_value, dict):
if not is_subset(value, superset_value):
return False
elif isinstance(value, list) and isinstance(superset_value, list):
for sub_val, sup_val in zip(value, superset_value, strict=True):
if not is_subset(sub_val, sup_val):
return False
# Check equality for non-dict values, ignoring None in superset
elif superset_value is not None and value != superset_value:
return False
Expand Down Expand Up @@ -296,9 +300,12 @@ def test_config_yaml_parsed(temp_yaml_config_file):
"instrument": "p01",
},
"sources": [
{"kind": "dodal", "module": "dodal.adsim"},
{"kind": "dodal", "module": "dodal.adsim", "mock": True},
{"kind": "planFunctions", "module": "dodal.plans"},
{"kind": "planFunctions", "module": "dodal.plan_stubs.wrapped"},
{
"kind": "planFunctions",
"module": "dodal.plan_stubs.wrapped",
},
],
},
"api": {
Expand Down Expand Up @@ -344,9 +351,12 @@ def test_config_yaml_parsed(temp_yaml_config_file):
"auth_token_path": None,
"env": {
"sources": [
{"kind": "dodal", "module": "dodal.adsim"},
{"kind": "dodal", "module": "dodal.adsim", "mock": False},
{"kind": "planFunctions", "module": "dodal.plans"},
{"kind": "planFunctions", "module": "dodal.plan_stubs.wrapped"},
{
"kind": "planFunctions",
"module": "dodal.plan_stubs.wrapped",
},
],
"events": {"broadcast_status_events": True},
"metadata": {
Expand Down