-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Overhaul documentation #674
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #674 +/- ##
=======================================
Coverage 92.17% 92.17%
=======================================
Files 35 35
Lines 1803 1803
=======================================
Hits 1662 1662
Misses 141 141 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much is this just re-formatting the docs to pass current linting/build and how much is it intending to update the docs? There's several things that we're actively moving away from that aren't being changed, but also more changes than just updating the formatting.
docs/explanations/extension-code.md
Outdated
|
||
## Dodal | ||
|
||
[Dodal](https://github.com/DiamondLightSource/dodal) is a repository for DLS device configuration, providing factory functions for devices used at DLS. If you work at DLS, new devices should be added there. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new devices = new device classes?
This is mostly true, get devices into dodal, then plan to move to ophyd-async. Let's treat mostly true as true for the purposes of introductory docs.
Should be expanded to explain that beamlines should also be added there, with a link to dodal how to make a new beamline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a problem in general, some of these docs belong in dodal with links from blueapi. I think we can and should do that migration gradually though. I'm going to advocate for removing this sentence and making an issue in dodal for adding a page/pages explaining how to:
- Make a new device
- Add a device to a beamline
- Make a new beamline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I asked is there is already docs in dodal for at least part of that
https://diamondlightsource.github.io/dodal/main/how-to/create-beamline.html
https://diamondlightsource.github.io/dodal/main/reference/device-standards.html (?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah cool! I'll reference those
docs/explanations/extension-code.md
Outdated
* A Github repository | ||
* A local directory via the [scratch area](../how-to/edit-live.md). | ||
|
||
The easiest place to put the code is a repository created with the [`python-copier-template`](https://diamondlightsource.github.io/python-copier-template/main/index.html). Which can then become any of the above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could: an example, like i22?
docs/explanations/lifecycle.md
Outdated
|
||
|
||
def count( | ||
detectors: List[Readable] = [inject("det")], # default valid for Blueapi only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change going in prior to deprecating inject or after?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update to >=3.10 typing (List->list, Union[x, y] -> x | y, Optional[x] -> x | None)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to make a separate issue for moving all of the code snippets to Python files and checking that they still work and lint properly at docs build time, similar to ophyd-async.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs/explanations/type_validators.md
Outdated
```python | ||
def my_plan(a: int, b: str = "b") -> Plan | ||
... | ||
|
||
# Internally becomes something like | ||
# Internally becomes something like | ||
|
||
class MyPlanModel(BaseModel): | ||
a: int | ||
b: str = "b" | ||
class MyPlanModel(BaseModel): | ||
a: int | ||
b: str = "b" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this repeating what is explained in plans.md?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably does need a bit of consolidation, my read is that plans.md explains which parts of blueapi plans are special and why, and type_validators.md explains the very technical implementation of plan parameter parsing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is just repeating what is in lifecycle.md. It even includes the same json example, I'd really like to see one removed or the difference between the two explained.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the idea is that lifecycle.md
is a general overview of how blueapi stores, retrieves and runs plans whereas this is a deep dive into the special handling for substituting devices for strings. Do you have any suggestions as to what we can shuffle around to make that clearer?
Devices are made using the [dodal](https://github.com/DiamondLightSource/dodal) style available through factory functions like this: | ||
|
||
```python | ||
from my_facility_devices import MyTypeOfDetector | ||
|
||
def my_detector(name: str) -> MyTypeOfDetector: | ||
return MyTypeOfDetector(name, {"other_config": "foo"}) | ||
``` | ||
|
||
The return type annotation `-> MyTypeOfDetector` is required as blueapi uses it to determine that this function creates a device. Meaning you can have a Python file where only some functions create devices and they will be automatically picked up. Similarly, these functions can be organized per-preference into files. | ||
|
||
The device is created via a function rather than a global to preserve side-effect-free imports. Each device must have its own factory function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should: defer to dodal's docs on writing device classes and beamlines, and ensure they are up to date?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, see other discussions, I think we should migrate some of this to dodal as a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dodal already has docs on creating a beamline and making a device class.
https://diamondlightsource.github.io/dodal/main/how-to/create-beamline.html
and
https://diamondlightsource.github.io/dodal/main/how-to/make-new-ophyd-async-device.html
We should defer to these and fix those pages. The former should be updated to use device_factory and the second should be fixed as the Mermaid docs do not render responsively and should explain how to make a Device class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but I would rather merge this now and raise separate issues for fixing those pages and consolidating, otherwise this PR will languish for a long time and the published docs (that everyone reads) will continue to contain mistakes that throw people off. Alternatively, I can remove this file entirely from the PR and make issues in dodal, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs/how-to/write-plans.md
Outdated
Blueapi exposes the docstrings of plans to clients, along with the parameter types. It is therefore worthwhile to make these detailed and descriptive. This may include units of arguments (e.g. seconds or microseconds), its purpose in the function, the purpose of the plan etc. | ||
|
||
```python | ||
def temp_pressure_snapshot( | ||
detectors: List[Readable], | ||
temperature: Movable = inject("sample_temperature"), | ||
pressure: Movable = inject("sample_pressure"), | ||
target_temperature: float = 273.0, | ||
target_pressure: float = 10**5, | ||
metadata: Optional[Mapping[str, Any]] = None, | ||
) -> MsgGenerator: | ||
""" | ||
Moves devices for pressure and temperature (defaults fetched from the context) | ||
and captures a single frame from a collection of devices | ||
Args: | ||
detectors (List[Readable]): A list of devices to read while the sample is at STP | ||
temperature (Optional[Movable]): A device controlling temperature of the sample, | ||
defaults to fetching a device name "sample_temperature" from the context | ||
pressure (Optional[Movable]): A device controlling pressure on the sample, | ||
defaults to fetching a device name "sample_pressure" from the context | ||
target_pressure (Optional[float]): target temperature in Kelvin. Default 273 | ||
target_pressure (Optional[float]): target pressure in Pa. Default 10**5 | ||
Returns: | ||
MsgGenerator: Plan | ||
Yields: | ||
Iterator[MsgGenerator]: Bluesky messages | ||
""" | ||
yield from move({temperature: target_temperature, pressure: target_pressure}) | ||
yield from count(detectors, 1, metadata or {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should: replacing this form of docstring with using Annotated/Field arguments so that the values propagate into json schema?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I did not realise they didn't propagate, that's quite bad. I was hoping to make the plans as vanilla-bluesky as we could, so vanilla python function docstrings are definitely desirable...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lemme get a couple of example plans into blueapi and compare the json schemas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@attach_data_session_metadata_decorator()
@validate_call(config={"arbitrary_types_allowed": True})
def linkam_plan_1(
trajectory: LinkamTrajectory,
linkam: Linkam3 = LINKAM,
shutter_time: float = 0.04,
) -> MsgGenerator:
"""
Follow a trajectory in temperature, collecting a number of frames either at equally
spaced positions or while continually scanning. e.g. for 2 segments, the first
stepped and the 2nd flown:\n
trajectory start v v final segment stop\n
\\ /\n
stepped segment__\\__ /\n
\\ / flown segment\n
1st segment stop \\__ /\n
exposures: xx xx xx 1/N seconds
Args:
trajectory: Trajectory for the scan to follow.
linkam: Temperature controller
shutter_time: Time allowed (s) for opening shutter before triggering detectors.
Returns:
MsgGenerator: Plan
Yields:
Iterator[MsgGenerator]: Bluesky messages
"""
...
@attach_data_session_metadata_decorator()
@validate_call(config={"arbitrary_types_allowed": True})
def linkam_plan_2(
trajectory: Annotated[
LinkamTrajectory, Field(description="Trajectory for the scan to follow.")
],
linkam: Annotated[Linkam3, Field(LINKAM, description="Temperature controller.")],
shutter_time: Annotated[
float,
Field(
0.04,
description="Time allowed for opening shutter before triggering detectors.",
json_schema_extra={"units": "s"},
),
],
) -> MsgGenerator:
"""
Follow a trajectory in temperature, collecting a number of frames either at equally
spaced positions or while continually scanning. e.g. for 2 segments, the first
stepped and the 2nd flown:\n
trajectory start v v final segment stop\n
\\ /\n
stepped segment__\\__ /\n
\\ / flown segment\n
1st segment stop \\__ /\n
exposures: xx xx xx 1/N seconds
"""
...
@attach_data_session_metadata_decorator()
@validate_call(config={"arbitrary_types_allowed": True})
def linkam_plan_3(
trajectory: LinkamTrajectory = Field(
description="Trajectory for the scan to follow."
),
linkam: Linkam3 = Field(LINKAM, description="Temperature controller."),
shutter_time: float = Field(
0.04,
description="Time allowed for opening shutter before triggering detectors.",
json_schema_extra={"units": "s"},
),
) -> MsgGenerator:
"""
Follow a trajectory in temperature, collecting a number of frames either at equally
spaced positions or while continually scanning. e.g. for 2 segments, the first
stepped and the 2nd flown:\n
trajectory start v v final segment stop\n
\\ /\n
stepped segment__\\__ /\n
\\ / flown segment\n
1st segment stop \\__ /\n
exposures: xx xx xx 1/N seconds
"""
...
linkam_plan_1.json
linkam_plan_2.json
linkam_plan_3.json
I've just gone through all that just to prove they're basically identical and the Pydantic model doesn't actually get the information about the Fields 🙃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can continue this discussion in #644
@DiamondJoseph regarding the fact that some of this is going out of date "soon": I have been bitten too many times by writing code and docs in anticipation of changes that are due "in a week or so" and end up taking 6 months. I'm strongly against putting anything in here that is not true in Ideally docs should be updated in the same PR as the change they reference, second-best option is after the PR is merged, worst-best is before the PR is merged. |
docs/tutorials/run-bus.md
Outdated
``` | ||
podman run -it --rm --net host rmohr/activemq:5.15.9-alpine | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove per discussion with @DominicOram
3eed614
to
076c875
Compare
076c875
to
fc787c3
Compare
Move lots of detail to explanations
Co-authored-by: Joseph Ware <[email protected]>
fc787c3
to
94a95b8
Compare
docs/explanations/type_validators.md
Outdated
```python | ||
def my_plan(a: int, b: str = "b") -> Plan | ||
... | ||
|
||
# Internally becomes something like | ||
# Internally becomes something like | ||
|
||
class MyPlanModel(BaseModel): | ||
a: int | ||
b: str = "b" | ||
class MyPlanModel(BaseModel): | ||
a: int | ||
b: str = "b" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is just repeating what is in lifecycle.md. It even includes the same json example, I'd really like to see one removed or the difference between the two explained.
Devices are made using the [dodal](https://github.com/DiamondLightSource/dodal) style available through factory functions like this: | ||
|
||
```python | ||
from my_facility_devices import MyTypeOfDetector | ||
|
||
def my_detector(name: str) -> MyTypeOfDetector: | ||
return MyTypeOfDetector(name, {"other_config": "foo"}) | ||
``` | ||
|
||
The return type annotation `-> MyTypeOfDetector` is required as blueapi uses it to determine that this function creates a device. Meaning you can have a Python file where only some functions create devices and they will be automatically picked up. Similarly, these functions can be organized per-preference into files. | ||
|
||
The device is created via a function rather than a global to preserve side-effect-free imports. Each device must have its own factory function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dodal already has docs on creating a beamline and making a device class.
https://diamondlightsource.github.io/dodal/main/how-to/create-beamline.html
and
https://diamondlightsource.github.io/dodal/main/how-to/make-new-ophyd-async-device.html
We should defer to these and fix those pages. The former should be updated to use device_factory and the second should be fixed as the Mermaid docs do not render responsively and should explain how to make a Device class.
docs/how-to/write-plans.md
Outdated
def pass_metadata(x: Movable, metadata: Optional[Mapping[str, Any]] = None) -> MsgGenerator: | ||
yield from bp.count{[x], md=metadata or {}} | ||
```python | ||
def pass_metadata(x: Movable, metadata: Mapping[str, Any] | None = None) -> MsgGenerator: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to tighten up Mapping -> dict to behave well with bp and bps usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, not sure I follow, what issues are you seeing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bluesky.plans have their equivalent md argument (which we should use when possible) is CustomPlanMetadata = dict[str, Any]
: we therefore cannot pass a Mapping[str, Any]
down to it: Mapping
does not allow assignment, where dict
does. We also sometimes need to mutate metadata in a "sub"plan before passing it to a "super"plan.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This very plan would fail type checking for the issue. We can quite neatly then have the required parts of the plan be from bluesky.utils:
from bluesky.utils import CustomPlanMetadata, MsgGenerator
def foo(metadata: CustomPlanMetadata | None = None) -> MsgGenerator:
yield from {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to change it, please feel free to read or ignore the small rant below.
The reason it's dict
is because it's mutated upstream:
def my_plan(params: ..., md: dict[str, Any]):
...
md["plan_name"] = my_plan.__name__
yield from ...
It could instead be kept immutable:
def my_plan(params: ..., md: Mapping[str, Any]):
...
md = {**md, **{"plan_name": my_plan.__name__}}
yield from ...
Immutable = good, so I would prefer we change upstream, but the verbosity of that solution may be unpalletable.
Co-authored-by: Joseph Ware <[email protected]>
Co-authored-by: Joseph Ware <[email protected]>
Co-authored-by: Joseph Ware <[email protected]>
Co-authored-by: Joseph Ware <[email protected]>
motor: Movable, | ||
steps: int, | ||
sample_name: str, | ||
extra_metadata: dict[str, Any]) -> MsgGenerator: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the plan linting in dodal enforces that plans have exactly this argument metadata: dict[str, Any] | None = None
- would be nice to have this example compliant with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also line 43 of this file which says that plans should have this arg...
Fixes #447
Fixes #361
Fixes #671