Skip to content

Commit 633d018

Browse files
committed
improvements on the data schema and property defintiion
1 parent 940c735 commit 633d018

File tree

17 files changed

+382
-285
lines changed

17 files changed

+382
-285
lines changed

docs/beginners-guide/articles/properties/arguments.md

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,72 +5,70 @@
55
### `allow_None`, `constant` & `readonly`
66

77
- if `allow_None` is `True`, property supports `None` apart from its own type
8-
- `readonly` (being `True`) makes the property read-only or execute the getter method
8+
- `readonly` (being `True`) makes the property read-only or execute only the getter method
99
- `constant` (being `True`), again makes the property read-only but can be set once if `allow_None` is `True`.
1010
This is useful to set the property once at `__init__()` but remain constant after that.
1111

12-
```py title="allow None, constant and readonly" linenums="1" hl_lines="7 15 19"
13-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:1:24"
12+
```py title="allow None, constant and readonly" linenums="1" hl_lines="11 19 27 28 36"
13+
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:1:39"
1414
```
1515

1616
=== "`allow_None=True`"
1717

1818
```py title="allow None, constant and readonly" linenums="1"
19-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:26:28"
19+
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:44:47"
2020
```
2121

2222
=== "`allow_None=False`"
2323

2424
```py title="allow None, constant and readonly" linenums="1"
25-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:31:32"
25+
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:49:51"
2626
```
2727

2828
=== "`readonly=True`"
2929

3030
```py title="allow None, constant and readonly" linenums="1"
31-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:35:35"
31+
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:53:54"
3232
```
3333

3434
=== "`constant=True`"
3535

3636
```py title="allow None, constant and readonly" linenums="1"
37-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:38:40"
37+
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:56:59"
3838
```
3939

4040
### `doc` and `label`
4141

4242
`doc` allows clients to fetch a docstring for the property. `label` can be used to show the property
43-
in a GUI for example.
43+
in a GUI with a readable name (for example).
4444

4545
```py title="allow None, constant and readonly" linenums="1" hl_lines="8-9"
46-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:1:6"
47-
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:11:14"
46+
--8<-- "docs/beginners-guide/code/properties/common_args_1.py:5:14"
4847
```
4948

5049
### `default`, `fget`, `fset` & `fdel`
5150

52-
To provide a getter-setter (& deleter) method is optional. If none given, when the property is set/written, the value
53-
is stored inside the instance's `__dict__` under the name `<given property name >_param_value`
54-
(for example, `serial_number_param_value` for `serial_number`). In layman's terms,
55-
`__dict__` is the internal map where the attributes of the object are stored by python.
51+
To provide a getter-setter (& deleter) method is optional.
5652

57-
When a value assignment was never called on the property, `default` is returned when reading the value. This is the purpose of the `default` argument. If a setter/deleter is given, getter is mandatory. In this case, `default` is also ignored & the getter is always executed.
53+
If none given, when the property is set/written, the value is stored inside the instance's `__dict__` under the name `<given property name>_param_value`
54+
(for example, `serial_number_param_value` for `serial_number`). In layman's terms, `__dict__` is the internal map where the attributes of the object are stored by python.
55+
56+
When a value assignment was never called on the property, `default` is returned when reading the value. If a setter/deleter is given, getter is mandatory. In this case, `default` is also ignored & the getter is always executed.
5857

5958
=== "with decorator"
6059

61-
```py linenums="1"
62-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:22:42"
63-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:46:48"
64-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:98:102"
60+
```py linenums="1" hl_lines="24 31"
61+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:24:50"
62+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:53:60"
63+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:135:140"
6564
```
6665

6766
=== "with fget-fset-fdel arguments"
6867

69-
```py linenums="1"
70-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:22:24"
71-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:29:34"
72-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:36:48"
73-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:98:102"
68+
```py linenums="1" hl_lines="26-27"
69+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:24:30"
70+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:32:37"
71+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:39:53"
7472
```
7573

7674
If default is desirable, one has to return it manually in the getter method by accessing the property [descriptor object directly](../#__codelineno-2-15).
@@ -82,12 +80,12 @@ instead of instance's `__dict__` (instance's attribute).
8280
Custom getter-setter-deleter are not compatible with this option currently. `class_member` takes precedence over fget-fset-fdel,
8381
which in turn has precedence over `default`.
8482

85-
```py title="class member" linenums="1" hl_lines="21-22"
86-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:7:20"
87-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:22:24"
88-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:51:57"
89-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:98:100"
90-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:103:106"
83+
```py title="class member" linenums="1" hl_lines="26"
84+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:7:22"
85+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:24:26"
86+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:64:73"
87+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:135:136"
88+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:141:143"
9189
```
9290

9391
`class_member` can still be used with a default value if there is no custom fget-fset-fdel.
@@ -96,57 +94,60 @@ which in turn has precedence over `default`.
9694

9795
setting `remote` to False makes the inaccessible to a client but accessible to the object locally. This is still useful to type-restrict python attributes to provide an interface to other developers using your class, for example, when someone else inherits your `Thing`. For example, the `Thing`'s `logger` is implemented in this fashion:
9896

99-
```py title="local properties" linenums="1" hl_lines="8"
97+
```py title="local properties" linenums="1" hl_lines="11"
10098
import logging
101-
from hololinked.server.properties import ClassSelector
99+
from hololinked.core.properties import ClassSelector
102100

103-
class Thing:
101+
class Thing(metaclass=ThingMeta):
104102
"""Subclass from here to expose hardware or python objects on the network"""
105103

106-
logger = ClassSelector(class_=logging.Logger, default=None, allow_None=True,
107-
remote=False,
108-
doc="""logging.Logger instance to print log messages.
109-
Default logger with a IO-stream handler and network
110-
accessible handler is created if none supplied."""
111-
) # type: logging.Logger
104+
logger = ClassSelector(
105+
class_=logging.Logger,
106+
default=None,
107+
allow_None=True,
108+
remote=False, # does not make sense to expose the logger object, its not serializable
109+
doc="""logging.Logger instance to print log messages.
110+
Default logger with a IO-stream handler and network
111+
accessible handler is created if none supplied."""
112+
) # type: logging.Logger
112113
```
113114

114115
### `state`
115116

116117
When `state` is specifed, the property is writeable only when the `Thing`'s `StateMachine` is in that specified state (or
117118
in the list of allowed states):
118119

119-
```py title="state machine state" linenums="1" hl_lines="17"
120-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:22:24"
121-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:60:76"
120+
```py title="state machine state" linenums="1" hl_lines="22"
121+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:24:26"
122+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:77:100"
122123
```
123124

124-
This is also currently applicable only when set operations are called by clients. Local set operations are always executed irrespective of the state machine state. A get operation is always executed as well even from the clients irrespective of the state.
125+
This is also currently applicable only when set operations are called by clients. Local set operations are always executed irrespective of the state machine state. A get operation is always executed even from the clients irrespective of the state.
125126

126127
## `observable`
127128

128129
Observable properties push change events when the property is set or read. This is useful when one wants to monitor the
129130
property for changes without polling from the client. The payload of the change event is the new value of the property.
130131

131-
```py title="state machine state" linenums="1" hl_lines="19"
132-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:22:24"
133-
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:79:95"
132+
```py title="observable" linenums="1" hl_lines="29"
133+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:24:26"
134+
--8<-- "docs/beginners-guide/code/properties/common_args_2.py:104:133"
134135
```
135136

136137
## `metadata`
137138

138-
`metadata` is a dictionary that allows storing arbitrary metadata about the property. For example, one can store units of the physical
139+
`metadata` is a free-flow dictionary that allows storing arbitrary metadata about the property. For example, one can store units of the physical
139140
quantity.
140141

141142
## `db_init`, `db_commit` & `db_persist`
142143

143-
Properties can be stored in a file or a database and loaded from them when the `Thing` is stopped and restarted. This is useful especially to preserve the settings of the hardware when the server undergoes a restart, either through system restart or any other reason.
144+
Properties can be stored in a file or a database and loaded from them when the `Thing` is stopped and restarted. This is useful especially to preserve the settings of the hardware when the server undergoes a restart, either through system restart, server crash or any other reason.
144145

145-
- `db_init` only loads a property from database, when the value is changed, its not written back to the database.
146+
- `db_init` only loads a property from database. When the property value is changed, its not written back to the database.
146147
For this option, the value has to be pre-created in the database in some other fashion.
147148

148-
- `db_commit` only writes the value into the database when an assignment is called.
149+
- `db_commit` only writes the value into the database when an assignment/write operation is called.
149150

150-
- `db_persist` both stores and loads the property from the database.
151+
- `db_persist` stores and loads the property from the database. property value is assigned after `__init__()` method, therefore its recommended to ensure that the device connection is established at `__init__()` itself. Other, errors should be expected.
151152

152153
Supported databases are MySQL, Postgres & SQLite currently. Look at database how-to for supply database configuration.

docs/beginners-guide/articles/properties/extending.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,52 @@ Properties can also be extended to define custom types, validation and coercion
44

55
To create the property, inherit from the `Property` object and define the `__init__`:
66

7-
```py title='Subclassing Property' linenums="1"
7+
```py title='Subclassing Property' linenums="1" hl_lines="16"
88
--8<-- "docs/beginners-guide/code/properties/extending.py:2:2"
9-
--8<-- "docs/beginners-guide/code/properties/extending.py:4:20"
9+
--8<-- "docs/beginners-guide/code/properties/extending.py:4:27"
1010
```
1111

12-
It is possible to use the `__set__()` to carry out type validation & coercion:
12+
It is possible to use the `__set__()` to carry out type validation & coercion. This method is automatically invoked by python:
1313

14-
```py title='Validation with __set__()' linenums="1"
14+
```py title='Validation with __set__()' linenums="1" hl_lines="9-10"
1515
--8<-- "docs/beginners-guide/code/properties/extending.py:1:7"
16-
--8<-- "docs/beginners-guide/code/properties/extending.py:39:58"
16+
--8<-- "docs/beginners-guide/code/properties/extending.py:46:71"
1717
```
1818

19-
Basically, check the types, manipulate your data if necessary and pass it to the parent. It is necessary to use the `instance_descriptor` decorator as shown above to allow `class_member` option to function correctly. If the `Property` will not be a `class_member`, this decorator can be skipped.
19+
- check metadata options like `readonly`, `constant` etc.
20+
- check the type of the input value
21+
- manipulate your data if necessary
22+
- pass it to the parent.
2023

21-
Further, the parent class [`Property` takes care](https://github.com/VigneshVSV/hololinked/blob/main/hololinked/server/property.py) of allocating an instance variable, checking `constant`, `readonly`, pushing change events, writing the value to the database etc. To avoid double checking of certain options like `readonly` and `constant`, its better to carry out the validation and coercion within the method `validate_and_adapt()` instead of `__set__`:
24+
It is necessary to use the `instance_descriptor` decorator as shown above to allow `class_member` option to function correctly. If the `Property` will not be a `class_member`, this decorator can be skipped.
25+
26+
Further, the parent class [`Property` takes care](https://github.com/VigneshVSV/hololinked/blob/main/hololinked/core/property.py) of allocating an instance variable, checking `constant`, `readonly`, pushing change events, writing the value to the database etc. Therefore, to avoid redundancy, its recommended to implement a `validate_and_adapt()` method instead of `__set__`:
2227

2328
```py title='Validation and Adaption' linenums="1"
24-
--8<-- "docs/beginners-guide/code/properties/extending.py:4:6"
25-
--8<-- "docs/beginners-guide/code/properties/extending.py:20:37"
29+
--8<-- "docs/beginners-guide/code/properties/extending.py:6:8"
30+
--8<-- "docs/beginners-guide/code/properties/extending.py:29:46"
2631
```
2732

28-
The `__set__()` method automatically invokes `validate_and_adapt()`, therefore the new value or validated value can be returned from this method.
33+
The `__set__()` method automatically invokes `validate_and_adapt()`, and a return value is expected.
2934

3035
To use the `JPEG` property in a `Thing` class, follow the normal procedure of property instantiation:
3136

3237
```py title="Instantiating Custom Property" linenums="1"
33-
--8<-- "docs/beginners-guide/code/properties/extending.py:62:76"
38+
--8<-- "docs/beginners-guide/code/properties/extending.py:74:94"
3439
```
3540

36-
In this particular example, since we dont want the `JPEG` to be set externally by a client, we create a local `Property` which carries out the image manipulation and an externally visible `readonly` Property that can fetch the processed image.
41+
In this particular example, since we dont want the `JPEG` to be set externally by a client, we create a local `Property` which carries out the image manipulation and an externally visible `readonly` Property that can supply the processed image to the client.
3742

3843
The difference between using a custom setter/`fset` method and overloading the `Property` is that, one can accept certain options specific to the `Property` in the `__init__` of the
3944
`Property`:
4045

4146
```py title="Reusing Custom Property" linenums="1"
42-
--8<-- "docs/beginners-guide/code/properties/extending.py:64:65"
43-
--8<-- "docs/beginners-guide/code/properties/extending.py:78:86"
47+
--8<-- "docs/beginners-guide/code/properties/extending.py:74:76"
48+
--8<-- "docs/beginners-guide/code/properties/extending.py:96:109"
4449
```
4550

46-
!!! Note
47-
48-
This is a contrived example and may not lead to optimized Property APIs for the client. Please adapt them suitably or rethink their implementation.
49-
5051
One may also use slots to store the attributes of the `Property`. Most properties predefined in this package use slots:
5152

5253
```py title="Using slots" linenums="1"
53-
--8<-- "docs/beginners-guide/code/properties/extending.py:90:"
54+
--8<-- "docs/beginners-guide/code/properties/extending.py:112:"
5455
```

docs/beginners-guide/articles/properties/index.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ To make a property take any python value, use the base class `Property`:
2222
--8<-- "docs/beginners-guide/code/properties/untyped.py:49:53"
2323
```
2424

25-
One can also pass the property value to the parent's `__init__` to auto-set or auto-invoke the setter at `__init__`:
25+
One can also pass the property value to the `Thing` parent's `__init__` to auto-set or auto-invoke the setter at `__init__`:
2626

2727
```py title="init" linenums="1" hl_lines="13-17"
2828
--8<-- "docs/beginners-guide/code/properties/untyped.py:5:12"
@@ -34,12 +34,12 @@ especially when applying the property directly onto the hardware.
3434
The descriptor object (instance of `Property`) that holds the property metadata and performs the get-set operations can be
3535
accessed by the instance under `self.properties.descriptors["<property name>"]`:
3636

37-
```py title="Custom Typed Property" linenums="1" hl_lines="20-22"
37+
```py title="Custom Typed Property" linenums="1" hl_lines="15 20-22 24"
3838
--8<-- "docs/beginners-guide/code/properties/untyped.py:1:7"
3939
--8<-- "docs/beginners-guide/code/properties/untyped.py:14:48"
4040
```
4141

42-
The value of the property must be serializable to be read by the clients. Read the [serializer section](#serialization) for further details & customization. To make a property only locally accessible, set `remote=False`, i.e. such a property will not accessible on the network.
42+
The value of the property must be serializable to be read by the clients. Read the [serializer section](#serialization) for further details & customization. To make a property only locally accessible, set `remote=False`, i.e. such a property will not accessible on the network nevertheless the descriptor behaviour can still be leveraged.
4343

4444
### Predefined Typed Properties
4545

@@ -73,20 +73,20 @@ An example:
7373
```
7474

7575
For typed properties, before the setter is invoked, the value is internally validated.
76-
The return value of getter method is never validated and is left to the developer's or the client object's caution.
76+
The return value of getter method is never validated and is left to the developer's or the client's caution.
7777

7878
## Schema Constrained Property
7979

8080
For complicated data structures, one can use `pydantic` or JSON schema based type definition and validation. Set the `model` argument to define the type:
8181

8282
=== "pydantic"
8383

84-
```py title="Properties Using Schema - pydantic" linenums="1" hl_lines="7 42-43"
85-
--8<-- "docs/beginners-guide/code/properties/schema.py:1:43"
84+
```py title="Properties Using Schema - pydantic" linenums="1" hl_lines="7 54-56"
85+
--8<-- "docs/beginners-guide/code/properties/schema.py:1:58"
8686
```
8787

8888
=== "JSON Schema"
8989

90-
```py title="Properties Using Schema - JSON schema" linenums="1" hl_lines="32-33"
91-
--8<-- "docs/beginners-guide/code/properties/schema.py:46"
90+
```py title="Properties Using Schema - JSON schema" linenums="1" hl_lines="5 31"
91+
--8<-- "docs/beginners-guide/code/properties/schema.py:60"
9292
```

0 commit comments

Comments
 (0)