Skip to content

Commit

Permalink
121 dodal integration (#171)
Browse files Browse the repository at this point in the history
* Add dodal git dependency

* Add initial dodal device loading to context

* Allow only dodal style device modules

* Allow list of device and plan sources

* Enable context to search multiple modules for devices and plans

* Update to use dodal function format

* Add checking when adding devices to context

* Update p45 config to use dodal

* Update adsim.yaml to use multiple sources

* Update worker config for multiple sources

* Add module loading tests for context
DiamondLightSource/python3-pip-skeleton#127
* Correct ignore pre-commit comment

* Make module name qualification explicit

* Swap typedict for basemodel in config

* Test device functions with dependencies

* Make source type an enum

* Allocate device and plan adding based on source kind

* Change source type to kind

* Remove unused imports

* Make test context use default environment config

* Update source format

* Update enum case to abide by pep8

* Update dodal dependency format

* Exclude dodal from lockfile

Avoids current skeleton bug, DiamondLightSource/python3-pip-skeleton#127.
  • Loading branch information
abbiemery authored May 15, 2023
1 parent 340de26 commit a90b53b
Show file tree
Hide file tree
Showing 17 changed files with 208 additions and 167 deletions.
3 changes: 1 addition & 2 deletions .github/actions/install_requirements/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ runs:
- name: Create lockfile
run: |
mkdir -p lockfiles
pip freeze --exclude-editable > lockfiles/${{ inputs.requirements_file }}
pip freeze --exclude-editable --exclude dodal > lockfiles/${{ inputs.requirements_file }}
# delete the self referencing line and make sure it isn't blank
sed -i '/file:/d' lockfiles/${{ inputs.requirements_file }}
shell: bash
Expand All @@ -55,4 +55,3 @@ runs:
fi
fi
shell: bash

6 changes: 5 additions & 1 deletion config/adsim.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
env:
startupScript: blueapi.startup.adsim
sources:
- kind: deviceFunctions
module: blueapi.startup.adsim
- kind: planFunctions
module: blueapi.startup.adsim
6 changes: 5 additions & 1 deletion config/bl45p.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
env:
startupScript: blueapi.startup.bl45p
sources:
- kind: dodal
module: dodal.p45
- kind: planFunctions
module: blueapi.plans
6 changes: 5 additions & 1 deletion config/defaults.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
env:
startupScript: blueapi.startup.example
sources:
- kind: deviceFunctions
module: blueapi.startup.example
- kind: planFunctions
module: blueapi.plans
stomp:
host: localhost
port: 61613
Expand Down
6 changes: 5 additions & 1 deletion helm/blueapi/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ affinity: {}

worker:
env:
startupScript: blueapi.startup.example
sources:
- kind: deviceFunctions
module: blueapi.startup.example
- kind: planFunctions
module: blueapi.plans
stomp:
host: activemq
port: 61613
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ dependencies = [
"fastapi[all]",
"uvicorn",
"requests",
"dodal @ git+https://github.com/DiamondLightSource/dodal.git",

]
dynamic = ["version"]
license.file = "LICENSE"
Expand Down Expand Up @@ -70,6 +72,7 @@ write_to = "src/blueapi/_version.py"

[tool.mypy]
ignore_missing_imports = true # Ignore missing stubs in imported modules
namespace_packages = false # rely only on __init__ files to determine fully qualified module names.

[tool.isort]
float_to_top = true
Expand Down
21 changes: 18 additions & 3 deletions src/blueapi/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from pathlib import Path
from typing import Any, Generic, Literal, Mapping, Type, TypeVar, Union

Expand All @@ -9,7 +10,18 @@
LogLevel = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]


class StompConfig(BlueapiBaseModel):
class SourceKind(str, Enum):
PLAN_FUNCTIONS = "planFunctions"
DEVICE_FUNCTIONS = "deviceFunctions"
DODAL = "dodal"


class Source(BaseModel):
kind: SourceKind
module: Union[Path, str]


class StompConfig(BaseModel):
"""
Config for connecting to stomp broker
"""
Expand All @@ -23,11 +35,14 @@ class EnvironmentConfig(BlueapiBaseModel):
Config for the RunEngine environment
"""

startup_script: Union[Path, str] = "blueapi.startup.example"
sources: list[Source] = [
Source(kind=SourceKind.DEVICE_FUNCTIONS, module="blueapi.startup.example"),
Source(kind=SourceKind.PLAN_FUNCTIONS, module="blueapi.plans"),
]

def __eq__(self, other: object) -> bool:
if isinstance(other, EnvironmentConfig):
return str(self.startup_script) == str(other.startup_script)
return str(self.sources) == str(other.sources)
return False


Expand Down
27 changes: 17 additions & 10 deletions src/blueapi/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from dataclasses import dataclass, field
from importlib import import_module
from inspect import Parameter, signature
from pathlib import Path
from types import ModuleType
from typing import (
Any,
Expand All @@ -24,6 +23,7 @@
from pydantic import create_model
from pydantic.fields import FieldInfo

from blueapi.config import EnvironmentConfig, SourceKind
from blueapi.utils import BlueapiPlanModelConfig, load_module_all

from .bluesky_types import (
Expand Down Expand Up @@ -73,13 +73,16 @@ def find_device(self, addr: Union[str, List[str]]) -> Optional[Device]:
else:
return find_component(self.devices, addr)

def with_startup_script(self, path: Union[Path, str]) -> None:
mod = import_module(str(path))
self.with_module(mod)
def with_config(self, config: EnvironmentConfig) -> None:
for source in config.sources:
mod = import_module(str(source.module))

def with_module(self, module: ModuleType) -> None:
self.with_plan_module(module)
self.with_device_module(module)
if source.kind is SourceKind.PLAN_FUNCTIONS:
self.with_plan_module(mod)
elif source.kind is SourceKind.DEVICE_FUNCTIONS:
self.with_device_module(mod)
elif source.kind is SourceKind.DODAL:
self.with_dodal_module(mod)

def with_plan_module(self, module: ModuleType) -> None:
"""
Expand All @@ -106,9 +109,13 @@ def plan_2(...):
self.plan(obj)

def with_device_module(self, module: ModuleType) -> None:
for obj in load_module_all(module):
if is_bluesky_compatible_device(obj):
self.device(obj)
self.with_dodal_module(module)

def with_dodal_module(self, module: ModuleType) -> None:
from dodal.utils import make_all_devices

for device in make_all_devices(module).values():
self.device(device)

def plan(self, plan: PlanGenerator) -> PlanGenerator:
"""
Expand Down
2 changes: 1 addition & 1 deletion src/blueapi/service/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(self, config: Optional[ApplicationConfig] = None) -> None:

logging.basicConfig(level=self.config.logging.level)

self.context.with_startup_script(self.config.env.startup_script)
self.context.with_config(self.config.env)

self.worker = RunEngineWorker(self.context)
self.message_bus = StompMessagingTemplate.autoconfigured(self.config.stomp)
Expand Down
113 changes: 0 additions & 113 deletions src/blueapi/startup/bl45p.py

This file was deleted.

98 changes: 67 additions & 31 deletions src/blueapi/startup/example.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,74 @@
from ophyd.sim import Syn2DGauss, SynGauss, SynSignal

from blueapi.plans import * # noqa: F401, F403

from .simmotor import BrokenSynAxis, SynAxisWithMotionEvents

x = SynAxisWithMotionEvents(name="x", delay=1.0, events_per_move=8)
y = SynAxisWithMotionEvents(name="y", delay=3.0, events_per_move=24)
z = SynAxisWithMotionEvents(name="z", delay=2.0, events_per_move=16)
theta = SynAxisWithMotionEvents(
name="theta", delay=0.2, events_per_move=12, egu="degrees"
)
x_err = BrokenSynAxis(name="x_err", timeout=1.0)
sample_pressure = SynAxisWithMotionEvents(
name="sample_pressure", delay=30.0, events_per_move=128, egu="MPa", value=0.101
)
sample_temperature = SynSignal(
func=lambda: ((x.position + y.position + z.position) / 1000.0) + 20.0,

def x(name="x") -> SynAxisWithMotionEvents:
return SynAxisWithMotionEvents(name=name, delay=1.0, events_per_move=8)


def y(name="y") -> SynAxisWithMotionEvents:
return SynAxisWithMotionEvents(name=name, delay=3.0, events_per_move=24)


def z(name="z") -> SynAxisWithMotionEvents:
return SynAxisWithMotionEvents(name=name, delay=2.0, events_per_move=16)


def theta(name="theta") -> SynAxisWithMotionEvents:
return SynAxisWithMotionEvents(
name=name, delay=0.2, events_per_move=12, egu="degrees"
)


def x_err(name="x_err") -> BrokenSynAxis:
return BrokenSynAxis(name=name, timeout=1.0)


def sample_pressure(name="sample_pressure") -> SynAxisWithMotionEvents:
return SynAxisWithMotionEvents(
name=name, delay=30.0, events_per_move=128, egu="MPa", value=0.101
)


def sample_temperature(
x: SynAxisWithMotionEvents,
y: SynAxisWithMotionEvents,
z: SynAxisWithMotionEvents,
name="sample_temperature",
)
image_det = Syn2DGauss(
) -> SynSignal:
return SynSignal(
func=lambda: ((x.position + y.position + z.position) / 1000.0) + 20.0,
name=name,
)


def image_det(
x: SynAxisWithMotionEvents,
y: SynAxisWithMotionEvents,
name="image_det",
motor0=x,
motor_field0="x",
motor1=y,
motor_field1="y",
center=(0, 0),
Imax=1,
labels={"detectors"},
)
current_det = SynGauss(
) -> Syn2DGauss:
return Syn2DGauss(
name=name,
motor0=x,
motor_field0="x",
motor1=y,
motor_field1="y",
center=(0, 0),
Imax=1,
labels={"detectors"},
)


def current_det(
x: SynAxisWithMotionEvents,
name="current_det",
motor=x,
motor_field="x",
center=0.0,
Imax=1,
labels={"detectors"},
)
) -> SynGauss:
return SynGauss(
name=name,
motor=x,
motor_field="x",
center=0.0,
Imax=1,
labels={"detectors"},
)
Empty file added tests/__init__.py
Empty file.
Loading

0 comments on commit a90b53b

Please sign in to comment.