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

Here you can find somt tips about the wrinting of a BLISS controller.

5
6
## @autocomplete_property decorator

7
8
9
10
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
bliss command line interface the autocompletion will **not** suggeste any
completion based on the return value of the method underneath the property.
11

12
13
14
This is a wanted behavior e.g. in case this would trigger hardware
communication. There are however also usecases where a *deeper* autocompletion
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
61
62
63
64
65
66
67
68
69
70
71
      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.

This is used to have simple way to get (detailed) information that is needed
from a **user point of view** to use the object. This is in contrast to the
build-in python function `__repr__()`, which should return a short summary of the
concerned object from the **developer point of view**. The Protocol that is put
in place in the Bliss shell is the following:

* 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
116
117
118
In this case it is desirable that the python objects themselves are clearly
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.

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

149

Cyril Guilloud's avatar
Cyril Guilloud committed
150
151
Example of a typical implementation of `.__info__()` method (no more need of
exception management like previously):
Cyril Guilloud's avatar
Cyril Guilloud committed
152
153
```python

Cyril Guilloud's avatar
Cyril Guilloud committed
154
155
def __info__(self):
    """Standard method called by BLISS Shell info helper."""
Cyril Guilloud's avatar
Cyril Guilloud committed
156
157
158
159
160
    info_str = ""
    info_str += " bla bla\n"

    return info_str
```
161
162

The equivalent of `repr(obj)` or `str(obj)` is also availabe in
Piergiorgio Pancino's avatar
Piergiorgio Pancino committed
163
`bliss.shell.standard` as `info(obj)` which can be used also outside the Bliss
164
shell.
Linus Pithan's avatar
Linus Pithan committed
165
166

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

Piergiorgio Pancino's avatar
Piergiorgio Pancino committed
171
>>> from bliss.shell.standard import info
Linus Pithan's avatar
Linus Pithan committed
172
173
174
175
176
177

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

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

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

191
## `__str__()` and `__repr__()`
Cyril Guilloud's avatar
Cyril Guilloud committed
192
193

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

* `__str__` should print a readable message
Linus Pithan's avatar
Linus Pithan committed
197
* `__repr__` should print a __short__ message obout the objec that is unambigous (e.g. name of an identifier, class name, etc).
Cyril Guilloud's avatar
Cyril Guilloud committed
198

199
200
201
202
203
204
205
206
207
208
* `__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
209
210


211
212
By default when no `__str__` or `__repr__` methods are defined, the `__repr__`
returns the name of the class (Length) and `__str__` calls `__repr__`.
Linus Pithan's avatar
Linus Pithan committed
213
214
215