You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/index.md
+33-24Lines changed: 33 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,9 +6,10 @@
6
6
7
7
[](https://smarie.github.io/python-pyfields/)[](https://pypi.python.org/pypi/pyfields/)[](https://pepy.tech/project/pyfields)[](https://pepy.tech/project/pyfields)[](https://github.com/smarie/python-pyfields/stargazers)
8
8
9
+
!!! new `@autofields` feature, [check it out](#3-autofields) !
9
10
!!! success "`pyfields` is now automatically supported by `autoclass` ! See [here](https://smarie.github.io/python-autoclass/#pyfields-combo) for details."
10
11
11
-
`pyfields` provides a simple and elegant way to define fields in python classes. With `pyfields` you explicitly define all aspects of a field (default value, type, documentation...) in a single place, and can refer to it from other places.
12
+
`pyfields` provides a simple and elegant way to define fields in python classes. With `pyfields` you explicitly define all aspects of a field (default value/factory, type, validators, converters, documentation...) in a single place, and can refer to it from other places.
12
13
13
14
It is designed with **development freedom** as primary target:
14
15
@@ -30,23 +31,30 @@ It provides **many optional features** that will make your object-oriented devel
30
31
31
32
Finally, it offers an API that other libraries can leverage to get the list of fields. For example `autoclass` now leverages `pyfields` to automatically add hash/dict/eq/repr to your class.
32
33
33
-
If your first reaction is "what about `attrs` / `dataclasses` / `pydantic` / `characteristic` / `traits` / `traitlets` / ...", please have a look [here](why.md).
34
-
34
+
If your first reaction is "what about `attrs` / `dataclasses` / `pydantic` / `characteristic` / `traits` / `traitlets` / ...", well all of these inspired `pyfields` a great deal, but all of these have stronger constraints on the class - which I did not want. Please have a look [here](why.md) for a complete list of inspirators.
35
35
36
36
## Installing
37
37
38
38
```bash
39
39
> pip install pyfields
40
40
```
41
41
42
+
For advanced type checking capabilities, `pyfields` requires that `typeguard` or `pytypes` is installed. Note that type checking performance (speed) mostly depends on the choice of type checking library. Not installing any will be faster, but will not support all of the `typing` constructs. Let's install `typeguard` for now:
43
+
44
+
```bash
45
+
> pip install typeguard
46
+
```
47
+
42
48
## Usage
43
49
50
+
Below we show a few prototypical examples to illustrate how versatile `pyfields` is. See [usage](./usage.md) for a more detailed, step-by-step explanation of all features.
51
+
44
52
!!! warning "compliance with python 2's old-style classes"
45
-
All examples below assume python 3 and therefore show new-style classes without explicit inheritance of `object`, for readability. If you're using python 2 do not forget to explicitly use a new-style class otherwise some features will not be available (the ones where a setter on the field is required: validation, conversion, read-only).
53
+
All examples in this doc assume python 3 and therefore show new-style classes without explicit inheritance of `object`, for readability. If you use python 2 do not forget to explicitly use new-style classes otherwise some features will not be available (the ones where a setter on the field is required: validation, conversion, read-only).
46
54
47
55
### 1. Defining a field
48
56
49
-
A field is defined as a class member using the `field()` method. The idea (not new) is that you declare in a single place all aspects related to each field. For mandatory fields you do not need to provide any argument. For optional fields, you will typically provide a `default` value or a `default_factory` (we will see that later).
57
+
A field is defined as a class member using the `field()` method. The idea (not new) is that you declare in a single place all aspects related to each field. For mandatory fields you do not need to provide any argument. For optional fields, you will typically provide a `default` value or a `default_factory` (we will see that [later](#b-default-value-factory)).
50
58
51
59
For example let's create a `Wall` class with one *mandatory*`height` and one *optional*`color` field:
52
60
@@ -59,9 +67,9 @@ class Wall:
59
67
```
60
68
61
69
!!! info "Compliance with python < 3.6"
62
-
If you use python < `3.6` you know that PEP484 type hints can not be declared as shown above. However you can provide them as [type comments](https://www.python.org/dev/peps/pep-0484/#type-comments), or using the `type_hint` argument.
70
+
If you use python < `3.6` you know that PEP484 type hints can not be declared as shown above. However you can provide them as [type comments](https://www.python.org/dev/peps/pep-0484/#type-comments), or using the `type_hint` argument (recommended if you wish to use [type validation](#d-type-validation)).
63
71
64
-
#### Field vs. Python attribute
72
+
#### a - Field vs. Python attr
65
73
66
74
By default when you use `field()`, nothing more than a "lazy field" is created on your class. This field will only be activated when you access it on an instance. That means that you are free to implement `__init__` as you wish, or even to rely on the default `object` constructor to create instances:
67
75
@@ -84,7 +92,7 @@ Until it is accessed for the first time, a field is visible on an instance with
84
92
As soon as you access it, a field is replaced with a standard native python attribute, visible in `vars`:
85
93
86
94
```python
87
-
>>> w.color # optional field: tdefault value is used
95
+
>>> w.color # optional field: the default value is used on first 'read' access
88
96
'white'
89
97
90
98
>>>vars(w)
@@ -94,20 +102,20 @@ As soon as you access it, a field is replaced with a standard native python attr
94
102
Of course mandatory fields must be initialized:
95
103
96
104
```python
97
-
>>> w.height
105
+
>>> w.height# trying to read an uninitialized mandatory field
98
106
pyfields.core.MandatoryFieldInitError: \
99
107
Mandatory field 'height' has not been initialized yet on instance <...>.
100
108
101
-
>>> w.height =12
109
+
>>> w.height =12# initializing mandatory field explicitly
102
110
>>>vars(w)
103
111
{'color': 'white', 'height': 12}
104
112
```
105
113
106
-
Your IDE (e.g. PyCharm) should recognize the name and type of the field, so you can already refer to it easily in other code using autocompletion:
114
+
Your IDE (e.g. PyCharm) should recognize the name and type of the field, so you can already refer to it easily from other code using autocompletion:
We have seen above how to define an optional field by providing a default value. The behaviour with default values is the same than python's default: the same value is used for all objects. Therefore if your default value is a mutable object (e.g. a list) you should not use this mechanism, otherwise the same value will be shared by all instances that use the default:
113
121
@@ -149,7 +157,7 @@ Finally, you can use the following built-in helper functions to cover most commo
149
157
-`copy_field(<field_or_name>)` returns a factory that will create copies of the given object field
150
158
-`copy_attr(<attr_name>)` returns a factory that will create copies of the given object attribute (not necessary a field)
In practice if you have your own constructor or if you generate one using the methods [below](#2-adding-a-constructor), it will work without problem. But when debugging your constructor with an IDE that automatically calls "repr" on your object you might have to remember it and take extra care.
200
208
201
-
#### Type validation
209
+
#### d - Type validation
202
210
203
211
You can add type validation to a field by setting `check_type=True`.
204
212
@@ -223,7 +231,7 @@ By default the type used for validation is the one provided in the annotation. I
223
231
!!! success "PEP484 `typing` support"
224
232
Now type hints relying on the `typing` module (PEP484) are correctly checked using whatever 3d party type checking library is available (`typeguard` is first looked for, then `pytypes` as a fallback). If none of these providers are available, a fallback implementation is provided, basically flattening `Union`s and replacing `TypeVar`s before doing `is_instance`. It is not guaranteed to support all `typing` subtleties.
225
233
226
-
#### Nonable fields
234
+
#### e - Nonable fields
227
235
228
236
**Definition**
229
237
@@ -245,7 +253,7 @@ When a field is known to be *nonable*, all of its type checks and validators are
245
253
246
254
When a field is forced explicitly to `nonable=False`, by default nothing happens, this is just declarative. However as soon as the field has type checking or validation activated, then a `NoneError` will be raised when `None` is received.
247
255
248
-
#### Value validation
256
+
#### f - Value validation
249
257
250
258
You can add value (and type) validation to a field by providing `validators`. `pyfields` relies on `valid8` for validation, so the basic definition of a validation function is the same: it should be a `<callable>` with signature `f(value)`, returning `True` or `None` in case of success.
See `API reference` for details on [`@<field>.validator`](#ltfieldgtvalidator).
351
+
See `API reference` for details on [`@<field>.validator`](api_reference.md#ltfieldgtvalidator).
344
352
345
353
See `valid8` documentation for details about the [syntax](https://smarie.github.io/python-valid8/validation_funcs/c_simple_syntax/) and available [validation lib](https://smarie.github.io/python-valid8/validation_funcs/b_base_validation_lib/).
346
354
347
355
348
-
#### Converters
356
+
#### g - Converters
349
357
350
358
You can add converters to a field by providing `converters`.
351
359
@@ -414,7 +422,7 @@ print(details)
414
422
```
415
423
416
424
417
-
#### Native vs. Descriptor fields
425
+
#### h - Native vs. Descriptor
418
426
419
427
`field()` by default creates a so-called **native field**. This special construct is designed to be as fast as a normal python attribute after the first access, so that performance is not impacted. This high level of performance has a drawback: validation and conversion are not possible on a native field.
420
428
@@ -604,7 +612,7 @@ post init ! height=1, color=white, msg=hey
604
612
Note on the order of arguments in the resulting `__init__` signature: as you can see, `msg` appears between `height` and `color`in the signature. This corresponds to the
605
613
606
614
607
-
### 3. Making it even easier
615
+
### 3. `@autofields`
608
616
609
617
Do you think that the above is still too verbose to define a class ? You can use `@autofields` to create fields and the constructor for you :
610
618
@@ -635,12 +643,14 @@ Note that members that are already fields are not further transformed. Therefore
By default type checking is not enabled on the generated fields, but you can enable it with `@autofields(check_types=True)`. You can also disable constructor creation with `@autofields(make_init=False)`. See [API reference](https://smarie.github.io/python-pyfields/api_reference/#api) for details.
653
+
644
654
645
655
### 4. Misc.
646
656
@@ -668,8 +678,7 @@ Note that if your class is a dual class (meaning that it declares a slot named `
0 commit comments