docs(examples): Add examples in functions docstring

Description

Abstract

Add some docstring example to many functions

Motivation

Having functions with docstring explaining what they do, what are they arguments and their purpose and what they return is great, but having an example on how to use it with its behavior is a great help.

Rationale

We use the Examples section of the docstring. These examples are tested by pytest (via pytest and the doctest-modules argument) so we are sure the examples are working.

Info

Hash

74b050b5285baf8199876881b35345830944a806

Date

2020-09-26 12:03:01 +0200

Parents
  • docs(source): Make links between python files in source and git [04393b77]2020-09-25 23:40:13 +0200

Children
  • docs(source): Reduce white space in toc tree [2048c435]2020-09-26 12:29:40 +0200

Branches
Tags

(No tags)

Changes

isshub/domain/utils/entity.py

Type

Modified

Stats

+132 -0

@@ -23,6 +23,17 @@ def optional_field(field_type):
         An ``attrs`` attribute, with a default value set to ``None``, and a validator checking
         that this field is optional and, if set, of the correct type.

+    Examples
+    --------
+    >>> from isshub.domain.utils.entity import optional_field, validated, BaseModel
+    >>>
+    >>> @validated()
+    ... class MyModel(BaseModel):
+    ...     my_field: str = optional_field(str)
+    >>>
+    >>> from isshub.domain.utils.testing.validation import check_field_nullable
+    >>> check_field_nullable(MyModel, 'my_field', my_field='foo')
+
     """
     return attr.ib(
         default=None,
@@ -43,6 +54,17 @@ def required_field(field_type):
     Any
         An ``attrs`` attribute, and a validator checking that this field is of the correct type.

+    Examples
+    --------
+    >>> from isshub.domain.utils.entity import required_field, validated, BaseModel
+    >>>
+    >>> @validated()
+    ... class MyModel(BaseModel):
+    ...     my_field: str = required_field(str)
+    >>>
+    >>> from isshub.domain.utils.testing.validation import check_field_not_nullable
+    >>> check_field_not_nullable(MyModel, 'my_field', my_field='foo')
+
     """
     return attr.ib(validator=attr.validators.instance_of(field_type))

@@ -57,6 +79,31 @@ def validated():
     type
         The decorated class.

+    Examples
+    --------
+    >>> from isshub.domain.utils.entity import required_field, validated, BaseModel
+    >>>
+    >>> @validated()
+    ... class MyModel(BaseModel):
+    ...     my_field: str = required_field(str)
+    >>>
+    >>> MyModel.__slots__
+    ('my_field',)
+    >>>
+    >>> instance = MyModel()
+    Traceback (most recent call last):
+        ...
+    TypeError: __init__() missing 1 required positional argument: 'my_field'
+    >>> instance = MyModel(my_field='foo')
+    >>> instance.my_field
+    'foo'
+    >>> instance.validate()
+    >>> instance.my_field = None
+    >>> instance.validate()
+    Traceback (most recent call last):
+        ...
+    TypeError: ("'my_field' must be <class 'str'> (got None that is a <class 'NoneType'>)...
+
     """
     return attr.s(slots=True)

@@ -74,6 +121,36 @@ def field_validator(field):
     Callable
         The decorated method.

+    Examples
+    --------
+    >>> from isshub.domain.utils.entity import field_validator, required_field, BaseModel
+    >>>
+    >>> @validated()
+    ... class MyModel(BaseModel):
+    ...    my_field: str = required_field(str)
+    ...
+    ...    @field_validator(my_field)
+    ...    def validate_my_field(self, field, value):
+    ...        if value != 'foo':
+    ...            raise ValueError(f'{self.__class__.__name__}.my_field must be "foo"')
+    >>>
+    >>> instance = MyModel(my_field='bar')
+    Traceback (most recent call last):
+        ...
+    ValueError: MyModel.my_field must be "foo"
+    >>> instance = MyModel(my_field='foo')
+    >>> instance.my_field
+    'foo'
+    >>> instance.my_field = 'bar'
+    >>> instance.validate()
+    Traceback (most recent call last):
+        ...
+    ValueError: MyModel.my_field must be "foo"
+    >>> instance.my_field = 'foo'
+    >>> instance.validate()
+    >>> instance.my_field
+    'foo'
+
     """
     return field.validator

@@ -91,6 +168,22 @@ def validate_instance(instance):
     TypeError, ValueError
         If a field in the `instance` is not valid.

+    Examples
+    --------
+    >>> from isshub.domain.utils.entity import required_field, validate_instance, BaseModel
+    >>>
+    >>> @validated()
+    ... class MyModel(BaseModel):
+    ...    my_field: str = required_field(str)
+    >>>
+    >>> instance = MyModel(my_field='foo')
+    >>> validate_instance(instance)
+    >>> instance.my_field = None
+    >>> validate_instance(instance)
+    Traceback (most recent call last):
+        ...
+    TypeError: ("'my_field' must be <class 'str'> (got None that is a <class 'NoneType'>)...
+
     """
     attr.validate(instance)

@@ -114,6 +207,45 @@ def validate_positive_integer(value, none_allowed, display_name):
     ValueError
         If `value` is not a positive integer (ie > 0), or ``None`` if `none_allowed` is ``True``.

+    Examples
+    --------
+    >>> from isshub.domain.utils.entity import field_validator, required_field, BaseModel
+    >>>
+    >>> @validated()
+    ... class MyModel(BaseModel):
+    ...    my_field: int = required_field(int)
+    ...
+    ...    @field_validator(my_field)
+    ...    def validate_my_field(self, field, value):
+    ...        validate_positive_integer(
+    ...            value=value,
+    ...            none_allowed=False,
+    ...            display_name=f"{self.__class__.__name__}.my_field",
+    ...        )
+    >>>
+    >>> instance = MyModel(my_field='foo')
+    Traceback (most recent call last):
+        ...
+    TypeError: ("'my_field' must be <class 'int'> (got 'foo' that is a <class 'str'>)...
+    >>> instance = MyModel(my_field=-2)
+    Traceback (most recent call last):
+        ...
+    ValueError: MyModel.my_field must be a positive integer
+    >>> instance = MyModel(my_field=0)
+    Traceback (most recent call last):
+        ...
+    ValueError: MyModel.my_field must be a positive integer
+    >>> instance = MyModel(my_field=1.1)
+    Traceback (most recent call last):
+        ...
+    TypeError: ("'my_field' must be <class 'int'> (got 1.1 that is a <class 'float'>)...
+    >>> instance = MyModel(my_field=1)
+    >>> instance.my_field = -2
+    >>> instance.validate()
+    Traceback (most recent call last):
+        ...
+    ValueError: MyModel.my_field must be a positive integer
+
     """
     if none_allowed and value is None:
         return

isshub/domain/utils/testing/validation.py

Type

Modified

Stats

+18 -12

@@ -1,9 +1,8 @@
 """Validation helpers for BDD tests for isshub entities."""

-from typing import Any, List, Optional, Tuple, Type
+from typing import Any, Callable, List, Optional, Tuple, Type

 import pytest
-from factory import Factory

 from isshub.domain.utils.entity import BaseModel

@@ -49,7 +48,7 @@ def check_field(obj: BaseModel, field_name: str) -> None:


 def check_field_value(
-    factory: Type[Factory],
+    factory: Callable[..., BaseModel],
     field_name: str,
     value: Any,
     exception: Optional[Type[Exception]],
@@ -59,7 +58,7 @@ def check_field_value(

     Parameters
     ----------
-    factory : Type[Factory]
+    factory : Callable[...,BaseModel]
         The factory to use to create the object to test
     field_name : str
         The name of the field to check
@@ -78,10 +77,13 @@ def check_field_value(
         or if no exception is raised if `exception` is not ``None`` (or the wrong exception).

     """
+    factory_kwargs_copy = factory_kwargs.copy()
+    factory_kwargs_copy.pop(field_name, None)
+
     if exception:
         # When creating an instance
         with pytest.raises(exception):
-            factory(**{field_name: value}, **factory_kwargs)
+            factory(**{field_name: value}, **factory_kwargs_copy)
         # When updating the value
         obj = factory(**factory_kwargs)
         setattr(obj, field_name, value)
@@ -89,7 +91,7 @@ def check_field_value(
             obj.validate()
     else:
         # When creating an instance
-        factory(**{field_name: value}, **factory_kwargs)
+        factory(**{field_name: value}, **factory_kwargs_copy)
         # When updating the value
         obj = factory(**factory_kwargs)
         setattr(obj, field_name, value)
@@ -97,13 +99,13 @@ def check_field_value(


 def check_field_not_nullable(
-    factory: Type[Factory], field_name: str, **factory_kwargs: Any
+    factory: Callable[..., BaseModel], field_name: str, **factory_kwargs: Any
 ) -> None:
     """Assert that an object cannot have a specific field set to ``None``.

     Parameters
     ----------
-    factory : Type[Factory]
+    factory : Callable[...,BaseModel]
         The factory to use to create the object to test
     field_name : str
         The name of the field to check
@@ -118,8 +120,10 @@ def check_field_not_nullable(

     """
     # When creating an instance
+    factory_kwargs_copy = factory_kwargs.copy()
+    factory_kwargs_copy.pop(field_name, None)
     with pytest.raises(TypeError):
-        factory(**{field_name: None}, **factory_kwargs)
+        factory(**{field_name: None}, **factory_kwargs_copy)

     # When updating the value
     obj = factory(**factory_kwargs)
@@ -129,13 +133,13 @@ def check_field_not_nullable(


 def check_field_nullable(
-    factory: Type[Factory], field_name: str, **factory_kwargs: Any
+    factory: Callable[..., BaseModel], field_name: str, **factory_kwargs: Any
 ) -> None:
     """Assert that an object can have a specific field set to ``None``.

     Parameters
     ----------
-    factory : Type[Factory]
+    factory : Callable[...,BaseModel]
         The factory to use to create the object to test
     field_name : str
         The name of the field to check
@@ -150,8 +154,10 @@ def check_field_nullable(

     """
     # When creating an instance
+    factory_kwargs_copy = factory_kwargs.copy()
+    factory_kwargs_copy.pop(field_name, None)
     try:
-        factory(**{field_name: None}, **factory_kwargs)
+        factory(**{field_name: None}, **factory_kwargs_copy)
     except TypeError:
         pytest.fail(f"DID RAISE {TypeError}")