dev_write_ctrl.md 7.56 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
!!! 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!
    - As a rule of thumb: the retrun value of a custom `__repr__()` implementation
      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
121
122
123
124
125
126
127
128
129
130
The signature of `__info__()` should be `def __info__(self):` the return value
musst be a string.

```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
152
153
154
155
156
157
158
159
Example of a typical implementation of `.__info__()` method:
```python

def info(self):
    info_str = ""
    info_str += " bla bla\n"

    return info_str

def __info__(self):
160
161
162
163
164
165
    """Standard method called by BLISS Shell info helper."""
    try:
        info_string = self.info(menu=False)
    except Exception:
        log_error(
            self,
Cyril Guilloud's avatar
Cyril Guilloud committed
166
167
            "An error happend during execution of __info__(),
             use .info() to get it.",
168
169
170
        )

    return info_string
Cyril Guilloud's avatar
Cyril Guilloud committed
171
```
172
173

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

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

Piergiorgio Pancino's avatar
Piergiorgio Pancino committed
182
>>> from bliss.shell.standard import info
Linus Pithan's avatar
Linus Pithan committed
183
184
185
186
187
188

>>> class A(object):
...     def __repr__(self):
...          return "my repl"
...     def __info__(self):
...          return "my info"
Cyril Guilloud's avatar
Cyril Guilloud committed
189
...
Linus Pithan's avatar
Linus Pithan committed
190
191
192
193
194
195
>>> info(A())
'my info'

>>> class B(object):
...     def __repr__(self):
...          return "my repl"
Cyril Guilloud's avatar
Cyril Guilloud committed
196
...
Linus Pithan's avatar
Linus Pithan committed
197
198
199
200
201

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

202
## `__str__()` and `__repr__()`
Cyril Guilloud's avatar
Cyril Guilloud committed
203
204

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

* `__str__` should print a readable message
Linus Pithan's avatar
Linus Pithan committed
208
* `__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
209

210
211
212
213
214
215
216
217
218
219
* `__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
220
221


222
223
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
224
225
226