From e76c7e00bad7b9ee94ccd74b3c72f7b179a1521f Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Mon, 15 Jul 2024 15:40:51 +0000 Subject: [PATCH 1/4] Update README --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 18017c2c..5405e37d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +FastCS Logo + [![CI](https://github.com/epics-containers/pvi/actions/workflows/ci.yml/badge.svg)](https://github.com/epics-containers/pvi/actions/workflows/ci.yml) [![Coverage](https://codecov.io/gh/epics-containers/pvi/branch/main/graph/badge.svg)](https://codecov.io/gh/epics-containers/pvi) [![PyPI](https://img.shields.io/pypi/v/pvi.svg)](https://pypi.org/project/pvi) @@ -6,13 +8,10 @@ # PVI PVI (PV Interface) is a framework for specifying the interface to an EPICS -driver in a single YAML file. The initial target is asyn port driver based -drivers, but it could be extended to streamDevice and other driver types at a -later date. - -It allows the asyn parameter interface to be specified in a single place, -and removes boilerplate code in the driver CPP, template files, documentation, -and low level opis. +driver. PVI can be used either as a library or an application. PVI Devices can be +defined either in code or a YAML file. It can be used to generate UIs (adl, edl, bob) or +a template appending info tags to existing records to define an NTTable of the PVs in an +IOC. Source | :---: | :---: @@ -20,13 +19,12 @@ PyPI | `pip install pvi` Documentation | Releases | ---- - -Note: This module is currently a proposal only, so all details are subject to -change at any point. The documentation is written in the present tense, but only -prototype code is written. +## Projects Using PVI ---- +- [ibek](https://github.com/epics-containers/ibek) - IOC Builder for EPICS and + Kubernetes +- [FastCS](https://github.com/DiamondLightSource/FastCS) - Control system agnostic + framework for building device support in Python for both EPICS and Tango From fa9c841b6f1bb8943257f2a005050adf80c1af87 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Tue, 16 Jul 2024 12:54:53 +0000 Subject: [PATCH 2/4] Update existing docs --- docs/explanations/original-design.md | 2 +- docs/how-to/write-a-formatter.md | 144 ++++++++++++--------------- src/pvi/_format/dls.py | 4 +- src/pvi/typed_model.py | 4 +- 4 files changed, 72 insertions(+), 82 deletions(-) diff --git a/docs/explanations/original-design.md b/docs/explanations/original-design.md index 36c8f520..80ddd15b 100644 --- a/docs/explanations/original-design.md +++ b/docs/explanations/original-design.md @@ -176,7 +176,7 @@ I suggest creating adl and edl files initially, following the example of makeAdl.py in ADGenICam, then expanding to support opi, bob and ui files natively. This would avoid needing screen converters installed -# Drivers +## Drivers The generated header file contains the string parameters, and defines the parameters to make the interface. In this example we have a header file pilatusDetectorParamSet.h: diff --git a/docs/how-to/write-a-formatter.md b/docs/how-to/write-a-formatter.md index 4de1ad58..5384025c 100644 --- a/docs/how-to/write-a-formatter.md +++ b/docs/how-to/write-a-formatter.md @@ -5,53 +5,59 @@ your own use cases. ## Overview -The formatters role is to take a device.yaml file and turn this into a screen file that -can be used by the display software. Inside of the device.yaml file is a list of -components that specify its name, a widget type and any additional properties that can be -assigned to that widget (such as a pv name). During formatting, the device.yaml file is -deserialised into component objects, which are later translated into widgets: +The formatters role is to take a `Device` - defined either in code or in a +`pvi.device.yaml` file - and turn this into a screen file that can be used by the +display software. The `Device` has a list of components that specify its name, a widget +type and any additional properties that can be assigned to that widget (such as a pv +name). During formatting, `Component` objects of the `Device` are translated into +widgets to be written to a UI file. ```{literalinclude} ../../src/pvi/device.py :pyobject: Component ``` +There are various types of `Component`. The simplest is a read-only signal. + ```{literalinclude} ../../src/pvi/device.py :pyobject: SignalR ``` -To make a screen from this, we need a template file. This contains a blank representation -of each supported widget for each of the supported file formats (bob, edl etc...). Below -is an example of a 'text entry' widget for a .bob file: +To add structure, there is a `Group` component, which itself has a list of `Components`. + +```{literalinclude} ../../src/pvi/device.py +:pyobject: Group +``` + +To make a screen from this, we need a template UI file. This contains a blank +representation of each supported widget for each of the supported file formats (bob, edl +etc...). Below is an example of a `textentry` widget for a .bob file. ```{literalinclude} ../../src/pvi/_format/dls.bob -:lines: 57-73 +:language: xml +:lines: 46-54 ``` By extracting and altering the template widgets with the information provided by the components, we can create a screen file. -## Create a formatter subclass +## Create a Formatter subclass To start, we will need to create our own formatter class. These inherit from an abstract -'Formatter' class that is defined in base.py. Inside, we need to define one mandatory -'format' function, which will be used to create our screen file: +`Formatter` class that is defined in base.py. Inside, we need to define one mandatory +`format` function, which will be used to create our screen file: ```{literalinclude} ../../src/pvi/_format/base.py -:pyobject: Formatter +:pyobject: Formatter.format ``` -The format function takes in a device: a list of components obtained from our -deserialised device.yaml file, A prefix: the pv prefix of the device, and a path: the -output destination for the generated screen file. - With a formatter defined, we now can start to populate this by defining the screen dependencies. -## Define the Screen Layout Properties +## Define the ScreenLayout properties Each screen requires a number of layout properties that allow you to customise the size -and placement of widgets. These are stored within a 'ScrenLayout' dataclass that can -be imported from utils.py. Within the dataclass are the following configurable parameters: +and placement of widgets. These are stored within `ScreenLayout` dataclass with the +following configurable parameters: ```{literalinclude} ../../src/pvi/_format/screen.py :pyobject: ScreenLayout @@ -64,22 +70,23 @@ screen format function) will be available to adjust inside of the formatter.yaml Anything else, should be considered as defaults for the formatter: ```{literalinclude} ../../src/pvi/_format/dls.py -:end-before: SW DOCS REF +:language: python :start-after: LP DOCS REF +:end-before: SW DOCS REF ``` In the example above, everything has been made adjustable from the formatter.yaml except -the properties relating to groups. This is becuase they are more dependant on the file +the properties relating to groups. This is because they are more dependant on the file format used rather than the users personal preference. For clarity, the example below shows how the formatter.yaml can be used to set the layout properties. Note that these are optional as each property is defined with a -default value: +default value. ```{literalinclude} ../../formatters/dls.bob.pvi.formatter.yaml ``` -## Assign a Template File +## Assign a template file As previously stated, a template file provides the formatter with a base model of all of the supported widgets that it can then overwrite with component data. Currently, @@ -87,90 +94,71 @@ pvi supports templates for edl, adl and bob files, which can be referenced from \_format directory with the filename 'dls' + the file formats suffix (eg. dls.bob). Inside of the format function, we need to provide a reference to the template file that -can then be used to identify what each widget should look like: +can then be used to identify what each widget should look like. ```python3 template = BobTemplate(str(Path(__file__).parent / "dls.bob")) ``` -% Documentation does not explain what the WidgetTemplate function does, -% nor its subclasses BobTemplate, EdlTemplate & AdlTemplate. +## Divide the template into widgets -## Divide the Template into Widgets +With a template defined, we now need to assign each part of it to a supported widget +formatter. This is achieved by instantiating a WidgetFormatterFactory composed of +WidgetFormatters created from the UI template. WidgetFormatters are created by searching +the UI template for the given search term and a set of properties in the template to +replace with widget fields. -With a template defined, we now need to assign each part of it to a supported widget. -This is achieved using the ScreenWidgets dataclass (from utils.py). With this, we can -assign each of the widget classes to a snippet of the template using the -WidgetFactory.from_template method: +:::{note} +The `WidgetFormatter`s are generic types that must be parameterised depending on the +specific UI. Commonly this would use `str` for formatting text to a file directly. In +this case we use `_Element`, which will serialised to text with the `lxml` library. +::: ```{literalinclude} ../../src/pvi/_format/dls.py -:end-before: MAKE_WIDGETS DOCS REF +:language: python :start-after: SW DOCS REF +:end-before: MAKE_WIDGETS DOCS REF ``` +```{warning} This function uses a unique search term to locate and extract a widget from the template. -As such, the search term MUST be unique to avoid extracing multiple or irrelevant +As such, the search term MUST be unique to avoid extracting multiple or irrelevant widgets from the template. +``` ## Define screen and group widget functions -Two widgets that are not handled by ScreenWidgets are the screen title and group object. -This is because the style of these widgets differ greatly for each file type. For -instance, with edl and adl files, groups are represented by a rectangle and title placed -behind a collection of widgets. Conversely, bob files handle groups using its dedicated -group object, which places widgets as children under the group object. Becuase of this, -we need to define two functions: one for the additional screen widgets (such as the title), -and one to represent the group widgets. - -We then need to define two functions that can be used to create multiple instances of -these widgets. In this example, we provide two arguments: The 'bounds', to set the -widgets size and position, and the 'title' to populate the label with. +Additionally, formatters for the title and a group on a screen must be defined along +with functions to create multiple components, for example a rectangle with a label on +top. In this example, we provide two arguments: The `bounds`, to set the widgets size +and position, and the `title` to populate the label with. ```{literalinclude} ../../src/pvi/_format/dls.py -:end-before: SCREEN_INI DOCS REF +:language: python :start-after: MAKE_WIDGETS DOCS REF +:end-before: SCREEN_INI DOCS REF ``` -## Construct a Screen Object +## Construct a ScreenFormatter -Provided that you have defined the LayoutProperties, template, ScreenWidgets and the -screen title and group object functions, we are now ready to define a screen object. +These formatters can be used to define a `ScreenFormatterFactory` ```{literalinclude} ../../src/pvi/_format/dls.py -:end-before: SCREEN_FORMAT DOCS REF +:language: python :start-after: SCREEN_INI DOCS REF +:end-before: SCREEN_FORMAT DOCS REF ``` -Note that screen_cls and group_cls are defined separately here as GroupFactories. This is -because they take in the make_widgets function, which has the possibility of returning -multiple widgets. (In edl files for example, we return a rectangle and label widget to -represent a group.) - -The screen object itself contains two key functions: The 'screen' function takes a -deserialised device.yaml file and converts each of its components into widgets. It then -calculates the size and position of these widgets to generate a uniform screen layout. -On the output of this, we can call a (screen.)format function that populates these widgets -with the extracted properties from the device.yaml, and converts them into the chosen file -format: +which can be used to instantiate a `ScreenFormatter` by passing a set of `Components` +and a title. This can then create `WidgetFormatters` for each `Component` for the +specific UI type the factory was parameterised with. ```{literalinclude} ../../src/pvi/_format/dls.py -:end-before: SCREEN_WRITE DOCS REF +:language: python :start-after: SCREEN_FORMAT DOCS REF +:end-before: SCREEN_WRITE DOCS REF ``` -## Generate the Screen file - -After calling format on the screen object, you will be left with a list of strings that -represent each widget in your chosen file format. The final step is to create a -screen file by unpacking the list and writing each widget to the file: - -```{literalinclude} ../../src/pvi/_format/dls.py -:start-after: SCREEN_WRITE DOCS REF -``` - -And thats it. With this you can now create your own custom formatters. Below you can -find a complete example formatter, supporting both edl and bob file formats for DLS: - -```{literalinclude} ../../src/pvi/_format/dls.py -:pyobject: DLSFormatter -``` +In this case the `write_bob` function calls into the `lxml` library to format the +`_Element` instances to text. For `str` formatters this would call +`pathlib.Path.write_file`. diff --git a/src/pvi/_format/dls.py b/src/pvi/_format/dls.py index 562a3f65..2b97e183 100644 --- a/src/pvi/_format/dls.py +++ b/src/pvi/_format/dls.py @@ -399,8 +399,10 @@ def create_screen_title_formatter( write_bob(sub_screen_formatter, sub_screen_path) +# SCREEN_WRITE DOCS REF: Generate the screen file + + def write_bob(screen_formatter: GroupFormatter[_Element], path: Path): - # SCREEN_WRITE DOCS REF: Generate the screen file # The root:'Display' is always the first element in texts texts = screen_formatter.format() element_tree = fromstring(tostring(texts[0]), None) diff --git a/src/pvi/typed_model.py b/src/pvi/typed_model.py index f264df90..b72475a1 100644 --- a/src/pvi/typed_model.py +++ b/src/pvi/typed_model.py @@ -48,7 +48,7 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: The `model_rebuild` method must be called to propagate this to the core schema of the models, once all `TypedModel` child classes have been defined. The - `model_json_schema` method is overidden in this class to make sure this is + `model_json_schema` method is overridden in this class to make sure this is applied automatically before generating schema. """ @@ -97,7 +97,7 @@ def _get_type_name(x: TypedModel | dict[str, Any]) -> str | None: This is a callable for pydantic Discriminator to discriminate between types in a tagged union of `TypedModel` child classes. - If given an instance of `TypeModel` then this method is being called to + If given an instance of `TypedModel` then this method is being called to serialize an instance. The type field of the entry for this instance should be its class name. From 7d093b3d3fad801d1336ffafc28044241f642032 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Thu, 8 Aug 2024 16:55:27 +0000 Subject: [PATCH 3/4] Add some docs --- docs/explanations.md | 2 + docs/explanations/pvi-pv.md | 93 +++++++++++++++++++++++++ docs/how-to.md | 1 + docs/reference/api.md | 30 +++++++- docs/tutorials.md | 4 ++ docs/tutorials/create-pvi-device.md | 47 +++++++++++++ docs/tutorials/format-device-ui.md | 19 +++++ docs/tutorials/generate-pvi-template.md | 16 +++++ docs/tutorials/pvi-as-a-library.md | 27 +++++++ 9 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 docs/explanations/pvi-pv.md create mode 100644 docs/tutorials/create-pvi-device.md create mode 100644 docs/tutorials/format-device-ui.md create mode 100644 docs/tutorials/generate-pvi-template.md create mode 100644 docs/tutorials/pvi-as-a-library.md diff --git a/docs/explanations.md b/docs/explanations.md index 73ab289b..1d3c3376 100644 --- a/docs/explanations.md +++ b/docs/explanations.md @@ -6,5 +6,7 @@ Explanations of how it works and why it works that way. :maxdepth: 1 :glob: +explanations/pvi-pv +explanations/original-design explanations/* ``` diff --git a/docs/explanations/pvi-pv.md b/docs/explanations/pvi-pv.md new file mode 100644 index 00000000..9d1d3e24 --- /dev/null +++ b/docs/explanations/pvi-pv.md @@ -0,0 +1,93 @@ +# PVI IOC Introspection + +PVI can be used to add info tags to existing EPICS database to create a V4 PV with an +NTTable of the PVs within the IOC. This can be done with the `format_template` function +by passing a `Device` instance, a PV prefix and an output path to write the template to. +Or, using the CLI command `pvi generate-template` with a `pvi.device.yaml`. The PV +prefix is the prefix of the PVI PV itself. Usually this would match the prefix of the +actual PVs, as defined in the `Device` signals, but it doesn't have to. + +## Template Format + +This will generate a new template containing records for all of the signals in the +`Device` that inserts an info tag to the existing record. + +``` +record("*", "$(P)$(R)Gain") { + info(Q:group, { + "$(P)$(R)PVI": { + "pvi.Gain.w": { + "+channel": "NAME", + "+type": "plain", + } + } + }) +} + +record("*", "$(P)$(R)Gain_RBV") { + info(Q:group, { + "$(P)$(R)PVI": { + "pvi.Gain.r": { + "+channel": "NAME", + "+type": "plain", + } + } + }) +} +... +record("*", "$(P)$(R)UniqueId_RBV") { + info(Q:group, { + "$(P)$(R)PVI": { + "pvi.UniqueId.r": { + "+channel": "NAME", + "+type": "plain", + } + } + }) +} +... +record("*", "$(P)$(R)WaitForPlugins") { + info(Q:group, { + "$(P)$(R)PVI": { + "pvi.WaitForPlugins.w": { + "+channel": "NAME", + "+type": "plain", + } + } + }) +} +``` + +These info tags are then collected and served as a V4 PV by QSRV. This info tag adds an +entry into the V4 PV `$(P)$(R)PVI` with the name `pvi.GainX.w` where `w` is the access +mode (`r`, `w`, `rw`, `x`). Each `Device` in the IOC will produce its own PVI PV, +differentiated by the `R` macro in this case. + +For more information on the syntax of the info tags, see the [QSRV documentation][QSRV]. + +## PVI NTTable + +With an IOC running the PVI PV can be accessed using PVAccess, for example the `pvget` +CLI tool in [PVAccessCPP], or the [p4p] python library. + +```shell +❯ pvget SIM:DET:PVI +SIM:DET:PVI structure + structure record + structure _options + boolean atomic true + structure pvi + structure Gain + string r SIM:DET:Gain_RBV + string w SIM:DET:Gain +... + structure UniqueId + string r SIM:DET:UniqueId_RBV +... + structure WaitForPlugins + string w SIM:DET:WaitForPlugins +``` + +[QSRV]: https://epics-base.github.io/pva2pva/qsrv_page.html +[PVAccessCPP]: https://github.com/epics-base/pvAccessCPP +[p4p]: https://github.com/mdavidsaver/p4p diff --git a/docs/how-to.md b/docs/how-to.md index 6b161417..ce53dbac 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -6,5 +6,6 @@ Practical step-by-step guides for the more experienced user. :maxdepth: 1 :glob: +how-to/write-a-formatter how-to/* ``` diff --git a/docs/reference/api.md b/docs/reference/api.md index b7976535..a3ffb2bb 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -1,6 +1,34 @@ # API -This is the internal API reference for pvi +Full API docs are linked below. Some key points of interest are: + +- [Device](#pvi.device.Device) +- Device Components + - [DeviceRef](#pvi.device.DeviceRef) + - [SignalR](#pvi.device.SignalR) + - [SignalW](#pvi.device.SignalW) + - [SignalRW](#pvi.device.SignalRW) + - [SignalX](#pvi.device.SignalX) + - [SignalRef](#pvi.device.SignalRef) + - [Group](#pvi.device.Group) +- Signal Widgets + - [ButtonPanel](#pvi.device.ButtonPanel) + - [ComboBox](#pvi.device.ComboBox) + - [LED](#pvi.device.LED) + - [SubScreen](#pvi.device.SubScreen) + - [TextRead](#pvi.device.TextRead) + - [TextWrite](#pvi.device.TextWrite) +- UI Formatting + - [Formatter](#pvi._format.base.Formatter) + - [UITemplate](#pvi._format.widget.UITemplate) + - [BobTemplate](#pvi._format.bob.BobTemplate) + - [WidgetFormatter](#pvi._format.widget.WidgetFormatter) + - [Bounds](#pvi._format.utils.Bounds) + + +## Full Reference API + +To browse the full generated reference API: ```{eval-rst} .. toctree:: diff --git a/docs/tutorials.md b/docs/tutorials.md index 1fe66c54..bf3db5bc 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -6,5 +6,9 @@ Tutorials for installation and typical usage. New users start here. :maxdepth: 1 :glob: +tutorials/installation +tutorials/create-pvi-device +tutorials/format-device-ui +tutorials/generate-pvi-template tutorials/* ``` diff --git a/docs/tutorials/create-pvi-device.md b/docs/tutorials/create-pvi-device.md new file mode 100644 index 00000000..2c7d34ab --- /dev/null +++ b/docs/tutorials/create-pvi-device.md @@ -0,0 +1,47 @@ +# Create a PVI Device from a database template + +A PVI device yaml can be created from an existing database template with the `convert +device` subcommand. The simplest case takes a header file for the driver a template. For +example, to create a PVI device for the ADSimDetector driver: + +```bash +pvi convert device ./ --header simDetector.h --template simDetector.template +``` + +which will create a `simDetector.pvi.device.yaml` in the current directory. + +:::{note} +See `pvi convert device --help` for a full list of options. +::: + +The `Device` will contain a list of components and groups of components describing the +PVs in the given templates, their types and the widgets to display for control and +readback on a UI. + +This `Device` can then be edited to make any adjustments. The `pvi regroup` command can +be used to add structure to the `Device` based on a set of `.adl` UIs: + +```bash +pvi regroup simDetector.pvi.device.yaml simDetector.adl simDetectorSetup.adl +``` + +:::{note} +See `pvi regroup --help` for a full list of options. +::: + +This will modify the yaml in place to add grouping based on what PV appears on what UI +file. The `Device` can also be edited by hand. A common use case is to change the widget +type from `TextRead`/`TextWrite` to something more specific. + +Once a Device has been edited, the `pvi reconvert` command can be used to make additions +without overwriting and modifications, either against a update template from the +upstream support module, or an additional template that was missed in the initial +conversion. + +```bash +pvi reconvert simDetector.pvi.device.yaml simDetector.template simDetectorExtras.template +``` + +:::{note} +See `pvi reconvert --help` for a full list of options. +::: \ No newline at end of file diff --git a/docs/tutorials/format-device-ui.md b/docs/tutorials/format-device-ui.md new file mode 100644 index 00000000..7175478e --- /dev/null +++ b/docs/tutorials/format-device-ui.md @@ -0,0 +1,19 @@ +# Format a UI from a Device + +A UI can be formatted from a PVI `Device`. Formatters currently exist for adl, edl and +bob files. This is selected based on the file extension of the given output path and +formatter. Formatters are in the `formatters` directory in the root of the pvi repo. For +example, to create a bob UI from the simDetector.pvi.device.yaml created in +[](create-pvi-device): + +```bash +pvi format simDetector.bob simDetector.pvi.device.yaml dls.bob.pvi.formatter.yaml +``` + +The format command will search for parents of the given `Device` (from the `parent` +field) and include those PVs on the UI. If the `Device` yaml files for the parents exist +in another directory, the directory can be provided with the `--yaml-path` option. + +:::{note} + See `pvi format --help` for a full list of options. +::: diff --git a/docs/tutorials/generate-pvi-template.md b/docs/tutorials/generate-pvi-template.md new file mode 100644 index 00000000..bc27759c --- /dev/null +++ b/docs/tutorials/generate-pvi-template.md @@ -0,0 +1,16 @@ +# Generate a PVI template from a Device + +A database template can be generated from a PVI `Device` that adds an info tag to every +record: + +```bash +pvi generate-template simDetector.pvi.device.yaml SIM pvi.template +``` + +:::{note} +See `pvi generate-template --help` for a full list of options. +::: + +The info tags can be constructed into an NTTable and served over PVAccess by +loading the template into an IOC using pvxs. For more details see the +[docs](../explanations/pvi-pv). diff --git a/docs/tutorials/pvi-as-a-library.md b/docs/tutorials/pvi-as-a-library.md new file mode 100644 index 00000000..63f9c0d3 --- /dev/null +++ b/docs/tutorials/pvi-as-a-library.md @@ -0,0 +1,27 @@ +# Using PVI as a library + +All subcommands in the CLI are also available as an API by importing pvi into a python +application. In addition, `Device` instances can be created dynamically to then use with +any of these APIs. Here is a trivial example: + +```python +from pathlib import Path + +from pvi.device import Device, SignalR, SignalRW, SignalX +from pvi._format.template import format_template +from pvi._format.dls import DLSFormatter + +signals = [ + SignalR(name="Status", read_pv="Status_RBV"), + SignalRW(name="Config", write_pv="Config", read_pv="Config_RBV"), + SignalX(name="Command", write_pv="Go", value="1"), +] +device = Device(label="Simple Device", children=signals) + +format_template(device, "PREFIX", Path("pvi.template")) +DLSFormatter().format(device, Path("simple.bob")) +``` + +which will create the `pvi.template` and `simple.bob` in the current working directory. + +See the tests in the pvi repo for further example usage of this API. From 08710fa2a5c193c2580d7a0e5acbb19696e6f393 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Fri, 9 Aug 2024 13:40:20 +0000 Subject: [PATCH 4/4] Run docs code blocks in markdown --- docs/how-to/write-a-formatter.md | 2 +- pyproject.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/how-to/write-a-formatter.md b/docs/how-to/write-a-formatter.md index 5384025c..5a9f0c08 100644 --- a/docs/how-to/write-a-formatter.md +++ b/docs/how-to/write-a-formatter.md @@ -96,7 +96,7 @@ pvi supports templates for edl, adl and bob files, which can be referenced from Inside of the format function, we need to provide a reference to the template file that can then be used to identify what each widget should look like. -```python3 +```python3 notest template = BobTemplate(str(Path(__file__).parent / "dls.bob")) ``` diff --git a/pyproject.toml b/pyproject.toml index 3077a4ad..f451179a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ dev = [ "pyright", "pytest", "pytest-cov", + "pytest-markdown-docs", "ruff", "sphinx-autobuild", "sphinx-copybutton", @@ -70,7 +71,7 @@ strict = ["src/**"] [tool.pytest.ini_options] # Run pytest with all our checkers, and don't spam us with massive tracebacks on error addopts = """ - --tb=native -vv --doctest-modules --doctest-glob="*.rst" + --tb=native -vv --doctest-modules --doctest-glob="*.md" --markdown-docs """ # https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings filterwarnings = "error"