dev_write_ctrl.md 7.2 KB
Newer Older
Cyril Guilloud's avatar
Cyril Guilloud committed
1
2
# Writing a BLISS controller

3
Here are some tips to help you writing a BLISS controller.
Cyril Guilloud's avatar
Cyril Guilloud committed
4

5
6
## @autocomplete_property decorator

7
8
In many controllers, the `@property` decorator is heavily used to protect certain
attributes of the instance or to limit the access to read-only. When using the
9
bliss command line interface the autocompletion will **not** suggest any
10
completion based on the return value of the method underneath the property.
11

12
This is a wanted behavior e.g. in case this would trigger hardware
13
communication. There are however also use cases where a *deeper* autocompletion
14
is wanted.
15

16
17
!!! note
     "↹" represents the action of pressing the "Tab" key of the keyboard.
18

19
20
21
22
23
Example: the `.counter` namespace of a controller. If implemented as
`@property`:
```
BLISS [1]: lima_simulator.counters. ↹
```
24

25
26
27
28
Would not show any autocompletion suggestions. To enable *deeper* autocompletion
a special decorator called `@autocomplete_property` must be used.
```python
from bliss.common.utils import autocomplete_property
Cyril Guilloud's avatar
Cyril Guilloud committed
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Lima(object):
    @autocomplete_property
    def counters(self):
        all_counters = [self.image]
        ...
```

Using this decorator would result in autocompletion suggestions:
```
BLISS [1]: lima_simulator.counters. ↹
                                   _roi1_
                                   _roi2_
                                   _bpm_
```
Linus Pithan's avatar
Linus Pithan committed
44

45
## The `__info__()` method for Bliss shell
Linus Pithan's avatar
Linus Pithan committed
46

47
48
49
50
51
52
!!! info

    - Any Bliss controller that is visible to the user in the command line
      should have an `__info__()` function implemented!
    - The return type of `__info__()` must be `str`, otherwhise it fails and
      `__repr__()` is used as fallback!
Cyril Guilloud's avatar
Cyril Guilloud committed
53
    - As a rule of thumb: the return value of a custom `__repr__()` implementation
54
55
56
57
58
59
60
      should not contain `\n` and should be inspired by the standard
      implementation of `__repr__()` in python.

In Bliss, `__info__()` is used by the command line interface (Bliss shell or Bliss
repl) to enquire information of the internal state of any object / controller in
case it is available.

61
62
63
64
65
That way, a user can get information how to use the object, detailed
**from the user perspective**. This is in contrast to the built-in python function
`__repr__()`, which should return a short summary of the concerned object from
the **developer perspective**. The Protocol that is put in place in the Bliss
shell is the following:
66
67
68
69
70
71

* if the return value of a statement entered into the Bliss shel is a python
  object with `__info__()` implemented this `__info__()` function will be called
  by the Bliss shell to display the output. As a fallback option (`__info__()`
  not implemented) the standard behavior of the interactive python interpreter
  involving `__repr__` is used. (For details about `__repr__` see next section.)
Linus Pithan's avatar
Linus Pithan committed
72
73
74

Here is an example for the lima controller that is using `__info__`:
```
75
LIMA_TEST_SESSION [3]: lima_simulator
Linus Pithan's avatar
Linus Pithan committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
              Out [3]: Simulator - Generator (Simulator) - Lima Simulator
                       
                       Image:
                       bin = [1 1]
                       flip = [False False]
                       height = 1024
                       roi = <0,0> <1024 x 1024>
                       rotation = rotation_enum.NONE
                       sizes = [   0    4 1024 1024]
                       type = Bpp32
                       width = 1024
                       
                       Acquisition:
                       expo_time = 1.0
                       mode = mode_enum.SINGLE
                       nb_frames = 1
                       status = Ready
                       status_fault_error = No error
                       trigger_mode = trigger_mode_enum.INTERNAL_TRIGGER
                       
                       ROI Counters:
                       [default]
                       
                       Name  ROI (<X, Y> <W x H>)
                       ----  ------------------
                         r1  <0, 0> <100 x 200>
```

104
105
106
107
108
109
110
111
112
The information given above is usefull from a **user point of view**. As a
**developer** one might want to work in the Bliss shell with live object e.g.

```python
LIMA [4]: my_detectors = {'my_lima':lima_simulator,'my_mca':simu1}
LIMA [5]: my_detectors
 Out [5]: {'my_lima': <Lima Controller for Simulator (Lima Simulator)>,
                        'my_mca': <bliss.controllers.mca.simulation.SimulatedMCA
                                   object at 0x7f2f535b5f60>}
Linus Pithan's avatar
Linus Pithan committed
113
114
```

115
In this case, it is desirable that the python objects themselves are clearly
116
117
118
represented, which is exactly the role of `__repr__` (in this example the
`lima_simulator` has a custom `__repr__` while in `simu1` there is no `__repr__`
implemented so the bulid in python implementation is used).
Linus Pithan's avatar
Linus Pithan committed
119

120
The signature of `__info__()` should be `def __info__(self):` the return value
Valentin Valls's avatar
Valentin Valls committed
121
must be a string.
122
123
124
125
126
127
128
129
130

```python
BLISS [1]: class A(object):
      ...:     def __repr__(self):
      ...:         return "my repl"
      ...:     def __str__(self):
      ...:         return "my str"
      ...:     def __info__(self):
      ...:         return "my info"
Linus Pithan's avatar
Linus Pithan committed
131

132
BLISS [2]: a=A()
Linus Pithan's avatar
Linus Pithan committed
133

134
BLISS [3]: a
Linus Pithan's avatar
Linus Pithan committed
135
136
  Out [3]: my info

137
BLISS [4]: [a]
Linus Pithan's avatar
Linus Pithan committed
138
139
140
  Out [4]: [my repl]
```

141
142
143
144
145
!!! warning

    If, for any reason, there is an exception raised inside `__info__`, the
    fallback option will be used and `__repr__` is evaluated in this case.

146
    And **this will hide the error**. So, *any* error must be treated
Cyril Guilloud's avatar
Cyril Guilloud committed
147
    before returning.
Linus Pithan's avatar
Linus Pithan committed
148

149

150
151
152
153
154
155
156
157
    Example:
    ```python
        def __info__(self):
            info_str = "bla \n"
            info_str += "bli \n"

            return info_str
    ```
158

159
The equivalent of `repr(obj)` or `str(obj)` is also available in
Piergiorgio Pancino's avatar
Piergiorgio Pancino committed
160
`bliss.shell.standard` as `info(obj)` which can be used also outside the Bliss
161
shell.
Linus Pithan's avatar
Linus Pithan committed
162
163

```
Cyril Guilloud's avatar
Cyril Guilloud committed
164
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
Linus Pithan's avatar
Linus Pithan committed
165
166
167
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.

Piergiorgio Pancino's avatar
Piergiorgio Pancino committed
168
>>> from bliss.shell.standard import info
Linus Pithan's avatar
Linus Pithan committed
169
170
171
172
173
174

>>> class A(object):
...     def __repr__(self):
...          return "my repl"
...     def __info__(self):
...          return "my info"
Cyril Guilloud's avatar
Cyril Guilloud committed
175
...
Linus Pithan's avatar
Linus Pithan committed
176
177
178
179
180
181
>>> info(A())
'my info'

>>> class B(object):
...     def __repr__(self):
...          return "my repl"
Cyril Guilloud's avatar
Cyril Guilloud committed
182
...
Linus Pithan's avatar
Linus Pithan committed
183
184
185
186
187

>>> info(B())
'my repl'
```

188
## `__str__()` and `__repr__()`
Cyril Guilloud's avatar
Cyril Guilloud committed
189
190

If implemented in a Python class, `__repr__` and `__str__` methods are
Linus Pithan's avatar
Linus Pithan committed
191
build-in functions Python to return information about an object instantiating this class.
Cyril Guilloud's avatar
Cyril Guilloud committed
192
193

* `__str__` should print a readable message
194
* `__repr__` should print a __short__ message about the object that is unambiguous (e.g. name of an identifier, class name, etc.).
Cyril Guilloud's avatar
Cyril Guilloud committed
195

196
197
198
199
200
201
202
203
204
205
* `__str__` is called:
    - when the object is passed to the print() function (e.g. `print(my_obj)`).
    - wheh the object is used in string operations (e.g. `str(my_obj)` or
      `'{}'.format(my_obj)` or `f'some text {my_obj}'`)
* `__repr__` method is called:
    - when user type the name of the object in an interpreter session (a python
      shell).
    - when displaying containers like lists and dicts (the result of `__repr__`
      is used to represent the objects they contain)
    - when explicitly asking for it in the print() function. (e.g. `print("%r" % my_object)`)
Cyril Guilloud's avatar
Cyril Guilloud committed
206
207


208
209
By default when no `__str__` or `__repr__` methods are defined, the `__repr__`
returns the name of the class (Length) and `__str__` calls `__repr__`.