Skip to content

Commit 031a885

Browse files
author
Sylvain MARIE
committed
Improved documentation slightly
1 parent dbdab09 commit 031a885

File tree

1 file changed

+33
-24
lines changed

1 file changed

+33
-24
lines changed

docs/index.md

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
[![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-pyfields/) [![PyPI](https://img.shields.io/pypi/v/pyfields.svg)](https://pypi.python.org/pypi/pyfields/) [![Downloads](https://pepy.tech/badge/pyfields)](https://pepy.tech/project/pyfields) [![Downloads per week](https://pepy.tech/badge/pyfields/week)](https://pepy.tech/project/pyfields) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-pyfields.svg)](https://github.com/smarie/python-pyfields/stargazers)
88

9+
!!! new `@autofields` feature, [check it out](#3-autofields) !
910
!!! success "`pyfields` is now automatically supported by `autoclass` ! See [here](https://smarie.github.io/python-autoclass/#pyfields-combo) for details."
1011

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.
1213

1314
It is designed with **development freedom** as primary target:
1415

@@ -30,23 +31,30 @@ It provides **many optional features** that will make your object-oriented devel
3031

3132
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.
3233

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.
3535

3636
## Installing
3737

3838
```bash
3939
> pip install pyfields
4040
```
4141

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+
4248
## Usage
4349

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+
4452
!!! 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).
4654

4755
### 1. Defining a field
4856

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)).
5058

5159
For example let's create a `Wall` class with one *mandatory* `height` and one *optional* `color` field:
5260

@@ -59,9 +67,9 @@ class Wall:
5967
```
6068

6169
!!! 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)).
6371

64-
#### Field vs. Python attribute
72+
#### a - Field vs. Python attr
6573

6674
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:
6775

@@ -84,7 +92,7 @@ Until it is accessed for the first time, a field is visible on an instance with
8492
As soon as you access it, a field is replaced with a standard native python attribute, visible in `vars`:
8593

8694
```python
87-
>>> w.color # optional field: tdefault value is used
95+
>>> w.color # optional field: the default value is used on first 'read' access
8896
'white'
8997

9098
>>> vars(w)
@@ -94,20 +102,20 @@ As soon as you access it, a field is replaced with a standard native python attr
94102
Of course mandatory fields must be initialized:
95103

96104
```python
97-
>>> w.height
105+
>>> w.height # trying to read an uninitialized mandatory field
98106
pyfields.core.MandatoryFieldInitError: \
99107
Mandatory field 'height' has not been initialized yet on instance <...>.
100108

101-
>>> w.height = 12
109+
>>> w.height = 12 # initializing mandatory field explicitly
102110
>>> vars(w)
103111
{'color': 'white', 'height': 12}
104112
```
105113

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:
107115

108116
![pycharm_autocomplete1.png](imgs/autocomplete1.png)
109117

110-
#### Default value factory
118+
#### b - Default value factory
111119

112120
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:
113121

@@ -149,7 +157,7 @@ Finally, you can use the following built-in helper functions to cover most commo
149157
- `copy_field(<field_or_name>)` returns a factory that will create copies of the given object field
150158
- `copy_attr(<attr_name>)` returns a factory that will create copies of the given object attribute (not necessary a field)
151159

152-
#### Read-only fields
160+
#### c - Read-only fields
153161

154162
You can define fields that can only be set once:
155163

@@ -198,7 +206,7 @@ pyfields.core.ReadOnlyFieldError:
198206
199207
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.
200208
201-
#### Type validation
209+
#### d - Type validation
202210
203211
You can add type validation to a field by setting `check_type=True`.
204212
@@ -223,7 +231,7 @@ By default the type used for validation is the one provided in the annotation. I
223231
!!! success "PEP484 `typing` support"
224232
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.
225233
226-
#### Nonable fields
234+
#### e - Nonable fields
227235
228236
**Definition**
229237
@@ -245,7 +253,7 @@ When a field is known to be *nonable*, all of its type checks and validators are
245253
246254
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.
247255
248-
#### Value validation
256+
#### f - Value validation
249257
250258
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.
251259
@@ -340,12 +348,12 @@ class Wall:
340348
raise InvalidWidth(width_value, height=self.height)
341349
```
342350
343-
See `API reference` for details on [`@<field>.validator`](#ltfieldgtvalidator).
351+
See `API reference` for details on [`@<field>.validator`](api_reference.md#ltfieldgtvalidator).
344352
345353
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/).
346354
347355
348-
#### Converters
356+
#### g - Converters
349357
350358
You can add converters to a field by providing `converters`.
351359
@@ -414,7 +422,7 @@ print(details)
414422
```
415423
416424
417-
#### Native vs. Descriptor fields
425+
#### h - Native vs. Descriptor
418426
419427
`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.
420428
@@ -604,7 +612,7 @@ post init ! height=1, color=white, msg=hey
604612
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
605613
606614
607-
### 3. Making it even easier
615+
### 3. `@autofields`
608616
609617
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 :
610618
@@ -635,12 +643,14 @@ Note that members that are already fields are not further transformed. Therefore
635643
from pyfields import field, copy_value, make_init
636644
637645
class Pocket:
638-
size = field(type_hint=int, check_type=True)
639-
items = field(type_hint=List[Item], check_type=True,
646+
size = field(type_hint=int)
647+
items = field(type_hint=List[Item],
640648
default_factory=copy_value([]))
641649
__init__ = make_init()
642650
```
643651
652+
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+
644654
645655
### 4. Misc.
646656
@@ -668,8 +678,7 @@ Note that if your class is a dual class (meaning that it declares a slot named `
668678
669679
## Main features / benefits
670680
671-
**TODO**
672-
681+
See [top of the page](./index.md)
673682
674683
## See Also
675684

0 commit comments

Comments
 (0)