Skip to content
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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Overhaul documentation #674

wants to merge 13 commits into from

Conversation

callumforrester
Copy link
Collaborator

Fixes #447
Fixes #361
Fixes #671

Copy link

codecov bot commented Oct 17, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.56%. Comparing base (cd91f0f) to head (b691f06).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #674      +/-   ##
==========================================
- Coverage   92.62%   92.56%   -0.07%     
==========================================
  Files          35       35              
  Lines        1654     1654              
==========================================
- Hits         1532     1531       -1     
- Misses        122      123       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Collaborator

@DiamondJoseph DiamondJoseph left a 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.

Handles communications and the API layer. This object holds a reference to the worker
can interrogate it/give it instructions in response to messages it receives from the message
bus. It can also forward the various events generated by the worker to topics on the bus.
Above are the main components of blueapi. The main process houses the REST API and manages the subprocess, which wraps the `RunEngine`, devices and optional connection to the downstream message broker.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Above are the main components of blueapi. The main process houses the REST API and manages the subprocess, which wraps the `RunEngine`, devices and optional connection to the downstream message broker.
Above are the main components of blueapi. The main process houses the REST API and manages the subprocess, which wraps the `RunEngine`, devices and external connections.

Do we want to be general or specific?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this a bit more general because I didn't think a list of major classes and their purposes was that useful and was prone to being out of date. It may be more useful when blueapi is more stable.

@@ -0,0 +1,17 @@
# Home of Plans and Devices

Plans and devices can be in any pip-installable package, such as:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move the device/dodal part first, so that it's clear that devices belong in dodal, rather than someone adding devices to their repo then realising it's meant to be in dodal.


## 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.
Copy link
Collaborator

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.

Copy link
Collaborator Author

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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

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

* 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.
Copy link
Collaborator

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?



def count(
detectors: List[Readable] = [inject("det")], # default valid for Blueapi only
Copy link
Collaborator

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?

Copy link
Collaborator

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)

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +6 to +14
```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"
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

Comment on lines +85 to +89
b: List[Readable],
c: Dict[str, Readable],
d: List[List[Readable]],
e: List[Dict[str, Set[Readable]]]) -> Plan:
...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 3.10ify

Copy link
Collaborator Author

@callumforrester callumforrester Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#677 unless you strongly want it here

however the option exists to override these by defining a yaml file which
can be passed to the `blueapi` command.
Blueapi's default configuration can be overridden
by defining a yaml file which can be passed to the `blueapi` command.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memo: this is about to get changed by changes to overriding config values from env vars etc, make sure to update it then

Comment on lines +14 to +25
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.
Copy link
Collaborator

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?

Copy link
Collaborator Author

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

Comment on lines +64 to +92
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 {})
Copy link
Collaborator

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?

Copy link
Collaborator Author

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...

Copy link
Collaborator

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

Copy link
Collaborator

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 🙃

@callumforrester
Copy link
Collaborator Author

@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 main right now.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Document Dev Environment Review and update docs Review CLI docs
2 participants