Repository: CGnal/cgnal-core (language: python)

Issue:
Use darglint to automatically check for docstring completeness
Darglint (https://github.com/terrencepreilly/darglint) should be included in the lint suite (flake8 plugin) and all errors found by it should be fixed.


Discussion / hints:

Fix patch:
diff --git a/cgnal/core/config/__init__.py b/cgnal/core/config/__init__.py
index 933233a..6c099ae 100644
--- a/cgnal/core/config/__init__.py
+++ b/cgnal/core/config/__init__.py
@@ -29,7 +29,14 @@
 def path_constructor(
     loader: Union[Loader, FullLoader, UnsafeLoader], node: Node
 ) -> PathLike:
-    """Extract the matched value, expand env variable, and replace the match."""
+    """
+    Extract the matched value, expand env variable, and replace the match.
+
+    :param loader: not used
+    :param node: YAML node
+    :return: path
+    :raises SyntaxError: if the node value does not match the regex expression for a path-like string
+    """
     value = node.value
     match = path_matcher.match(value)
 
@@ -41,7 +48,13 @@ def path_constructor(
 
 
 def joinPath(loader: Union[Loader, FullLoader, UnsafeLoader], node: Node) -> PathLike:
-    """Join pieces of a file system path. Can be used as a custom tag handler."""
+    """
+    Join pieces of a file system path. Can be used as a custom tag handler.
+
+    :param loader: YAML file loader
+    :param node: YAML node
+    :return: path
+    """
     seq = loader.construct_sequence(node)
     return os.path.join(*seq)
 
@@ -135,8 +148,8 @@ def safeGetValue(self, name: Hashable) -> Any:
         """
         Get the value of a configuration node, gracefully returning None if the node does not exist.
 
-        :param Hashable name: name of the node
-        :return Any: value of the node, or None if the node does not exist
+        :param name: name of the node
+        :return: value of the node, or None if the node does not exist
         """
         return self.config.get(name, None)
 
@@ -144,8 +157,8 @@ def update(self, my_dict: dict) -> "BaseConfig":
         """
         Update the current configuration.
 
-        :param dict my_dict: dictionary containing the nodes of the configuration to be updated
-        :return BaseConfig: new configuration with the updated nodes
+        :param my_dict: dictionary containing the nodes of the configuration to be updated
+        :return: new configuration with the updated nodes
         """
         meta = union(
             self.config.meta,
@@ -167,7 +180,7 @@ def root(self) -> PathLike:
         """
         Return the root node value.
 
-        :return PathLike: root node value
+        :return: root node value
         """
         return self.getValue("root")
 
@@ -175,8 +188,8 @@ def getFolder(self, path: Hashable) -> PathLike:
         """
         Return the folder name.
 
-        :param Hashable path: name of the configuration node
-        :return PathLike: folder name
+        :param path: name of the configuration node
+        :return: folder name
         """
         return self.config["folders"][path]
 
@@ -184,8 +197,8 @@ def getFile(self, file: Hashable) -> PathLike:
         """
         Return the file name.
 
-        :param Hashable file: name of the configuration node
-        :return PathLike: file name
+        :param file: name of the configuration node
+        :return: file name
         """
         return self.config["files"][file]
 
@@ -198,7 +211,7 @@ def method(self) -> str:
         """
         Return the authentication method.
 
-        :return str: authentication method
+        :return: authentication method
         """
         return self.getValue("method")
 
@@ -207,7 +220,7 @@ def filename(self) -> PathLike:
         """
         Return the name of the file containing the authentication details.
 
-        :return PathLike: name of the file containing the authentication details
+        :return: name of the file containing the authentication details
         """
         return self.getValue("filename")
 
@@ -216,7 +229,7 @@ def user(self) -> str:
         """
         Return the user name.
 
-        :return str: user name
+        :return: user name
         """
         return self.getValue("user")
 
@@ -225,7 +238,7 @@ def password(self) -> str:
         """
         Return the password.
 
-        :return str: password
+        :return: password
         """
         return self.getValue("password")
 
@@ -238,7 +251,7 @@ def url(self) -> str:
         """
         Return the url of the authentication service.
 
-        :return str: url of the authentication service
+        :return: url of the authentication service
         """
         return self.getValue("url")
 
@@ -247,7 +260,7 @@ def check(self) -> str:
         """
         Return check.
 
-        :return str: check
+        :return: check
         """
         return self.getValue("check")
 
@@ -256,7 +269,7 @@ def decode(self) -> str:
         """
         Return decode.
 
-        :return str: decode
+        :return: decode
         """
         return self.getValue("decode")
 
@@ -269,7 +282,7 @@ def url(self) -> str:
         """
         Return the url of the check service.
 
-        :return str: url of the check service.
+        :return: url of the check service.
         """
         return self.getValue("url")
 
@@ -278,7 +291,7 @@ def login(self) -> str:
         """
         Return the login url.
 
-        :return str: login url
+        :return: login url
         """
         return self.getValue("login")
 
@@ -287,7 +300,7 @@ def logout(self) -> str:
         """
         Return the logout url.
 
-        :return str: logout url
+        :return: logout url
         """
         return self.getValue("logout")
 
@@ -300,7 +313,7 @@ def secured(self) -> bool:
         """
         Return the secured flag.
 
-        :return bool: secured flag
+        :return: secured flag
         """
         return self.getValue("secured")
 
@@ -309,7 +322,7 @@ def ap_name(self) -> str:
         """
         Return the ap name.
 
-        :return str: ap name
+        :return: ap name
         """
         return self.getValue("ap_name")
 
@@ -318,7 +331,7 @@ def jwt_free_endpoints(self) -> List[str]:
         """
         Return the jwt free endpoints.
 
-        :return List[str]: jwt free endpoints
+        :return: jwt free endpoints
         """
         return self.getValue("jwt_free_endpoints")
 
@@ -327,7 +340,7 @@ def auth_service(self) -> AuthService:
         """
         Return the authentication data.
 
-        :return AuthService: authentication data
+        :return: authentication data
         """
         return AuthService(self.sublevel("auth_service"))
 
@@ -336,7 +349,7 @@ def check_service(self) -> CheckService:
         """
         Return the check service configuration.
 
-        :return CheckService: check service configuration
+        :return: check service configuration
         """
         return CheckService(self.sublevel("check_service"))
 
@@ -345,6 +358,6 @@ def cors(self) -> str:
         """
         Return the cors.
 
-        :return str: cors
+        :return: cors
         """
         return self.getValue("cors")
diff --git a/cgnal/core/data/layer/__init__.py b/cgnal/core/data/layer/__init__.py
index 244b6e4..94ccff8 100644
--- a/cgnal/core/data/layer/__init__.py
+++ b/cgnal/core/data/layer/__init__.py
@@ -34,8 +34,8 @@ def computeKey(self, obj: T) -> Union[Hashable, Dict[str, ObjectId]]:
         """
         Compute the key of a given object.
 
-        :param T obj: object
-        :return Union[Hashable, Dict[str, ObjectId]]: hashable key
+        :param obj: object
+        :return: hashable key
         """
         ...
 
@@ -44,8 +44,8 @@ def get(self, obj: T) -> V:
         """
         Return the record from the persistance layer corresponding to a object.
 
-        :param T obj: object
-        :return V: value
+        :param obj: object
+        :return: value
         """
         ...
 
@@ -54,8 +54,8 @@ def parse(self, row: V) -> T:
         """
         Parse the record of the persistance layer into a object.
 
-        :param V row: record of the persistance layer
-        :return T: object
+        :param row: record of the persistance layer
+        :return: object
         """
         ...
 
@@ -68,9 +68,9 @@ def retrieve(self, condition: Any, sort_by: Any) -> Iterator[T]:
         """
         Retrieve objects under given filter conditions and sorting options.
 
-        :param Any condition: filter to be applied in the persistance layer
-        :param Any sort_by: sorting options
-        :yield Iterator[T]: iterator over the objects retrieved from the persistance layer
+        :param condition: filter to be applied in the persistance layer
+        :param sort_by: sorting options
+        :yield: iterator over the objects retrieved from the persistance layer
         """
         ...
 
@@ -81,7 +81,7 @@ def archive(
         """
         Archive an object in the persistance layer.
 
-        :param T obj: object to be archived
+        :param obj: object to be archived
         :return: archiver or update result
         """
         ...
@@ -90,8 +90,10 @@ def map(self, f: Callable[[T], V], *args: Any, **kwargs: Any) -> Iterator[V]:
         """
         Apply a tranformation to each object and return an iterator.
 
-        :param Callable[[T], V] f: function
-        :yield Iterator[V]: iterator over transformed objects
+        :param f: function
+        :param args: other arguments passed to the retrieve method
+        :param kwargs: other keyworded arguments passed to the retrieve method
+        :yield: iterator over transformed objects
         """
         for obj in self.retrieve(*args, **kwargs):  # type: T
             yield f(obj)
@@ -100,7 +102,9 @@ def foreach(self, f: Callable[[T], None], *args, **kwargs) -> None:
         """
         Apply a function to each object.
 
-        :param Callable[[T], None] f: function
+        :param f: function
+        :param args: other arguments passed to the retrieve method
+        :param kwargs: other keyworded arguments passed to the retrieve method
         """
         for obj in self.retrieve(*args, **kwargs):  # type: T
             f(obj)
@@ -109,9 +113,9 @@ def retrieveGenerator(self, condition: Any, sort_by: Any) -> IterGenerator[T]:
         """
         Wrap the retrieve method to return an iterator generator.
 
-        :param Any condition: filter condition
-        :param Any sort_by: sorting options
-        :return IterGenerator[T]: iterator generator
+        :param condition: filter condition
+        :param sort_by: sorting options
+        :return: iterator generator
         """
 
         def _iterator():
@@ -128,8 +132,8 @@ def to_df(self, query: str) -> pd.DataFrame:
         """
         Transform the underling data into a pandas dataframe.
 
-        :param str query: query
-        :return pd.DataFrame: pandas dataframe
+        :param query: query
+        :return: pandas dataframe
         """
         ...
 
@@ -138,7 +142,7 @@ def write(self, df: pd.DataFrame) -> None:
         """
         Write the pandas dataframe in the persistance layer.
 
-        :param pd.DataFrame df: pandas dataframe
+        :param df: pandas dataframe
         """
         ...
 
@@ -151,8 +155,8 @@ def table(self, table_name: str) -> Optional[TableABC]:
         """
         Return the table with a given name.
 
-        :param str table_name: name of the table
-        :return Optional[TableABC]: table or None if table not found
+        :param table_name: name of the table
+        :return: table or None if table not found
         """
         ...
 
@@ -168,7 +172,11 @@ def table(self) -> TableABC:
 
     @abstractmethod
     def push(self, df: pd.DataFrame) -> None:
-        """Push a pandas dataframe."""
+        """
+        Push a pandas dataframe.
+
+        :param df: dataframe
+        """
         ...
 
 
@@ -179,7 +187,7 @@ def table(self, table_name: str) -> None:
         """
         Raise an exception, since the database is empty.
 
-        :param str table_name: name of the table
+        :param table_name: name of the table
         :raises NoTableException: no table is present in an empty database
         """
         raise NoTableException(f"No table found with name {table_name}")
diff --git a/cgnal/core/data/layer/mongo/__init__.py b/cgnal/core/data/layer/mongo/__init__.py
index 9324530..266a5f1 100644
--- a/cgnal/core/data/layer/mongo/__init__.py
+++ b/cgnal/core/data/layer/mongo/__init__.py
@@ -9,17 +9,29 @@ class MongoConfig(BaseConfig):
 
     @property
     def host(self) -> str:
-        """Host name."""
+        """
+        Return ost name.
+
+        :return: host name
+        """
         return self.getValue("host")
 
     @property
     def port(self) -> int:
-        """Port."""
+        """
+        Return port.
+
+        :return: port
+        """
         return self.getValue("port")
 
     @property
     def db_name(self) -> str:
-        """Database name."""
+        """
+        Return database name.
+
+        :return: database name
+        """
         return self.getValue("db_name")
 
     def getCollection(self, name) -> str:
@@ -27,21 +39,33 @@ def getCollection(self, name) -> str:
         Return collection name at a given configuration node.
 
         :param name: configuration node name
-        :return str: collection name
+        :return: collection name
         """
         return self.config["collections"][name]
 
     @property
     def auth(self) -> AuthConfig:
-        """Authetication config."""
+        """
+        Return authetication config.
+
+        :return: authetication config
+        """
         return AuthConfig(self.sublevel("auth"))
 
     @property
     def admin(self) -> AuthConfig:
-        """Administrator authentication config."""
+        """
+        Return administrator authentication config.
+
+        :return: administrator authentication config
+        """
         return AuthConfig(self.sublevel("admin"))
 
     @property
     def authSource(self) -> Any:
-        """Return the authentication source."""
+        """
+        Return the authentication source.
+
+        :return: authentication source
+        """
         return self.safeGetValue("authSource")
diff --git a/cgnal/core/data/layer/mongo/archivers.py b/cgnal/core/data/layer/mongo/archivers.py
index 3053f2c..d9b3f03 100644
--- a/cgnal/core/data/layer/mongo/archivers.py
+++ b/cgnal/core/data/layer/mongo/archivers.py
@@ -57,7 +57,7 @@ def retrieve(
 
         :param condition: condition to satisfy. If {}, return all documents.
         :param sort_by: ordering to respect. If None, no ordering is given.
-        :return: iterator of (ordered) documents satisfying given condition
+        :yield: iterator of (ordered) documents satisfying given condition
         """
         jsons = self.collection.find(condition, no_cursor_timeout=True)
         if sort_by is not None:
@@ -128,7 +128,7 @@ def aggregate(
         :param pipeline: a list of aggregation pipeline stages
         :param allowDiskUse: Enables writing to temporary files. When set to `True`, aggregation stages can write data to
             the _tmp subdirectory of the --dbpath directory. The default is False.
-        :return: iterator with parsed aggregated documents
+        :yield: iterator with parsed aggregated documents
         """
         jsons = self.collection.aggregate(pipeline, allowDiskUse=allowDiskUse)
         for json in jsons:
diff --git a/cgnal/core/data/layer/pandas/archivers.py b/cgnal/core/data/layer/pandas/archivers.py
index a5c9376..cbbd66d 100644
--- a/cgnal/core/data/layer/pandas/archivers.py
+++ b/cgnal/core/data/layer/pandas/archivers.py
@@ -32,6 +32,7 @@ def __init__(self, dao: DAO[T, pd.Series]) -> None:
 
         :param dao: An instance of :class:`cgnal.data.layer.pandas.dao.DocumentDao/SeriesDAO/DataFrameDAO` that helps
             to retrieve/archive a pd.DataFrame row
+        :raises TypeError: if given DAO is not of the correct type
         """
         if not isinstance(dao, DAO):
             raise TypeError(
@@ -58,12 +59,15 @@ def data(self, value: pd.DataFrame) -> None:
         Set data property to given value.
 
         :param value: value to set
-        :return: None
         """
         self._data = value
 
     def commit(self) -> "PandasArchiver":
-        """Persist data stored in memory in the file."""
+        """
+        Persist data stored in memory in the file.
+
+        :return: self
+        """
         self._write()
         return self
 
@@ -165,7 +169,7 @@ def __init__(
         :param dao: An instance of :class:`cgnal.data.layer.pandas.dao.DocumentDao/SeriesDAO/DataFrameDAO`
             that helps to retrieve/archive a `pd.Dataframe` row.
         :param sep: str, default ';'. Delimiter to use
-
+        :raises TypeError: if filename is not a string
         """
         super(CsvArchiver, self).__init__(dao)
 
@@ -176,11 +180,7 @@ def __init__(
         self.sep = sep
 
     def _write(self) -> None:
-        """
-        Write object to a csv file.
-
-        :return: None
-        """
+        """Write object to a csv file."""
         self.data.to_csv(self.filename, sep=self.sep)
 
     def _read(self) -> pd.DataFrame:
@@ -206,6 +206,7 @@ def __init__(self, filename: PathLike, dao: DAO[T, pd.Series]) -> None:
         :param filename: str, path object or file like object. Any valid string path to a pickle file.
         :param dao: An instance of :class:`cgnal.data.layer.pandas.dao.DocumentDao/SeriesDAO/DataFrameDAO`
             that helps to retrieve/archive a `pd.Dataframe` row.
+        :raises TypeError: if filename is not a string
 
         """
         super(PickleArchiver, self).__init__(dao)
@@ -216,11 +217,7 @@ def __init__(self, filename: PathLike, dao: DAO[T, pd.Series]) -> None:
         self.filename = filename
 
     def _write(self) -> None:
-        """
-        Write object to a pickle file.
-
-        :return:
-        """
+        """Write object to a pickle file."""
         self.data.to_pickle(self.filename)
 
     def _read(self) -> pd.DataFrame:
@@ -254,11 +251,7 @@ def __init__(self, table: Table, dao: DAO[T, pd.Series]) -> None:
         self.table = table
 
     def _write(self) -> None:
-        """
-        Write Table object as a pickle file.
-
-        :return: None
-        """
+        """Write Table object as a pickle file."""
         self.table.write(self.data, overwrite=True)
 
     def _read(self) -> pd.DataFrame:
diff --git a/cgnal/core/data/layer/pandas/dao.py b/cgnal/core/data/layer/pandas/dao.py
index 6424536..ece493f 100644
--- a/cgnal/core/data/layer/pandas/dao.py
+++ b/cgnal/core/data/layer/pandas/dao.py
@@ -119,6 +119,7 @@ def inverseMapping(self) -> Dict[Hashable, Hashable]:
         Return the inverse mapping between field names.
 
         :return: dict with inverse mapping
+        :raises ValueError: mapping is not invertible because of duplicated values
         """
         if len(set(self.mapping.values())) != len(self.mapping):
             raise ValueError("Mapping is not invertible as there are duplicated values")
@@ -130,6 +131,7 @@ def computeKey(self, serie) -> Union[Hashable, Dict]:
 
         :param serie: pd.Series
         :return: dict representing the key
+        :raises ValueError: keys and series name have different dimensions
         """
         if serie.name is None:
             raise ValueError("Cannot persist Series without name (to be used as index)")
@@ -151,7 +153,7 @@ def get(self, serie: pd.Series) -> pd.Series:
         """
         Get a series as series object.
 
-        :param s: pd.Series
+        :param serie: pd.Series
         :return: pd.Series
         """
         row = serie.rename(self.inverseMapping)
diff --git a/cgnal/core/data/layer/pandas/databases.py b/cgnal/core/data/layer/pandas/databases.py
index 98d5c53..ae31980 100644
--- a/cgnal/core/data/layer/pandas/databases.py
+++ b/cgnal/core/data/layer/pandas/databases.py
@@ -72,9 +72,10 @@ def __init__(self, db: Database, table_name: str) -> None:
 
         :param db: database to which the table belongs
         :param table_name: name of the table
+        :raises TypeError: if the provided database is not an instance of Database
         """
         if not isinstance(db, Database):
-            raise ValueError(
+            raise TypeError(
                 f"The db should an instance of {'.'.join([Database.__module__, Database.__name__])}"
             )
 
@@ -94,6 +95,7 @@ def to_df(self, query: Optional[str] = None) -> pd.DataFrame:
         """
         Read pickle.
 
+        :param query: query
         :return: pd.DataFrame or pd.Series read from pickle
         """
         df = pd.read_pickle(self.filename)
@@ -114,7 +116,6 @@ def write(self, df: pd.DataFrame, overwrite: bool = False) -> None:
 
         :param df: input data
         :param overwrite: whether or not to overwrite existing file
-        :return: None
         """
         # self.data can fail with:
         #   - KeyError if it tries to read a non-pickle file
diff --git a/cgnal/core/data/model/core.py b/cgnal/core/data/model/core.py
index dff2cb2..9294322 100644
--- a/cgnal/core/data/model/core.py
+++ b/cgnal/core/data/model/core.py
@@ -27,14 +27,22 @@ class Serializable(ABC):
     """Abstract Class to be used to extend objects that can be serialised."""
 
     @abstractmethod
-    def write(self, filaname: PathLike) -> None:
-        """Write class to a file."""
+    def write(self, filename: PathLike) -> None:
+        """
+        Write class to a file.
+
+        :param filename: filename
+        """
         ...
 
     @classmethod
     @abstractmethod
     def load(cls, filename: PathLike) -> "Serializable":
-        """Load class from a file."""
+        """
+        Load class from a file.
+
+        :param filename: filename
+        """
         ...
 
 
@@ -46,8 +54,6 @@ def write(self, filename: PathLike) -> None:
         Write instance as pickle.
 
         :param filename: Name of the file where to save the instance
-
-        :return: None
         """
         with open(filename, "wb") as fid:
             pickle.dump(self, fid)
@@ -72,8 +78,6 @@ def write(self, filename: PathLike) -> None:
         Write instance as pickle.
 
         :param filename: Name of the file where to save the instance
-
-        :return: None
         """
         with open(filename, "wb") as fid:
             dill.dump(self, fid)
@@ -147,6 +151,7 @@ def __init__(self, items: IterGenerator):
         Return an instance of the class to be used for implementing lazy iterables.
 
         :param items: IterGenerator containing the generator of items
+        :raises TypeError: if items is not an instance of IterGenerator
         """
         if not isinstance(items, IterGenerator):
             raise TypeError(
@@ -185,7 +190,11 @@ def __init__(self, items: Sequence[T]):
         self._items = list(items)
 
     def __len__(self) -> int:
-        """Return the size of the list of elements."""
+        """
+        Return the size of the list of elements.
+
+        :return: size of the list of elements
+        """
         return len(self.items)
 
     @property
@@ -202,6 +211,7 @@ def __getitem__(self, item: int) -> T:
         Get the item by position index.
 
         :param item: integer representing the position.
+        :return: item
         """
         return self.items[item]
 
@@ -282,7 +292,11 @@ def generator():
         return self._lazyType(IterGenerator(generator))
 
     def __iter__(self) -> Iterator[T]:
-        """Return an iterator over the items."""
+        """
+        Return an iterator over the items.
+
+        :yield: items
+        """
         for item in self.items:
             yield item
 
@@ -291,7 +305,7 @@ def batch(self, size: int = 100) -> Iterator[C]:
         Return an iterator of batches of size *size*.
 
         :param size: dimension of the batch
-        :return: iterator of batches
+        :yield: iterator of batches
         """
         for batch in groupIterable(self.items, batch_size=size):
             yield self._cachedType(batch)
@@ -315,7 +329,6 @@ def foreach(self, f: Callable[[T], Any]):
         Execute the provided function on each element of the iterable.
 
         :param f: function to be executed for each element
-        :return: None
         """
         for doc in self.items:
             f(doc)
@@ -328,12 +341,20 @@ class BaseIterable(
 
     @property
     def _lazyType(self) -> "Type[LazyIterable]":
-        """Specify the type of LazyObject associated to this class."""
+        """
+        Specify the type of LazyObject associated to this class.
+
+        :return: LazyIterable type
+        """
         return LazyIterable
 
     @property
     def _cachedType(self) -> "Type[CachedIterable]":
-        """Specify the type of CachedObject associated to this class."""
+        """
+        Specify the type of CachedObject associated to this class.
+
+        :return: CachedIterable type
+        """
         return CachedIterable
 
 
@@ -414,7 +435,11 @@ def range(self, freq="H") -> List[Timestamp]:
         ...
 
     def __str__(self) -> str:
-        """Return string representation."""
+        """
+        Return string representation.
+
+        :return: string representation
+        """
         return " // ".join([f"{r.start}-{r.end}" for r in self])
 
     @property
@@ -454,6 +479,7 @@ def __init__(self, start: DatetimeScalar, end: DatetimeScalar) -> None:
 
         :param start: starting datetime for the range
         :param end: ending datetime for the range
+        :raises ValueError: if start > end
         """
         self._start = pd.to_datetime(start)
         self._end = pd.to_datetime(end)
@@ -485,7 +511,7 @@ def __iter__(self) -> Iterator["Range"]:
         """
         Return an iterator over continuous ranges.
 
-        :return: Iterator[Range]
+        :yield: Iterator[Range]
         """
         yield Range(self.start, self.end)
 
@@ -524,9 +550,10 @@ def __add__(self, other: BaseRange) -> Union["CompositeRange", "Range"]:
 
         :param other: other range to be merged
         :return: merged range
+        :raises TypeError: other is not of type Range
         """
         if not isinstance(other, Range):
-            raise ValueError(
+            raise TypeError(
                 f"add operator not defined for argument of type {type(other)}. Argument should be of "
                 f"type Range"
             )
@@ -600,7 +627,7 @@ def __iter__(self) -> Iterator["Range"]:
         """
         Return an iterator over continuous ranges.
 
-        :return: Iterator[Range]
+        :yield: Iterator[Range]
         """
         for range in self.ranges:
             yield range
@@ -627,9 +654,10 @@ def __add__(self, other: BaseRange) -> BaseRange:
 
         :param other: BaseRange, other range to be merged
         :return: BaseRange, merged range
+        :raises TypeError: other is not of type BaseRange
         """
         if not isinstance(other, BaseRange):
-            raise ValueError(
+            raise TypeError(
                 f"add operator not defined for argument of type {type(other)}. Argument should be of "
                 f"type BaseRange"
             )
diff --git a/cgnal/core/data/model/ml.py b/cgnal/core/data/model/ml.py
index 87440b1..b62d3c1 100644
--- a/cgnal/core/data/model/ml.py
+++ b/cgnal/core/data/model/ml.py
@@ -118,7 +118,7 @@ def _check_features(features: List[np.ndarray]) -> None:
         Check that features is list of lists.
 
         :param features: list of lists
-        :return: None
+        :raises TypeError: if features is not a list or one of the feature content is not a numpy array
         """
         if not isinstance(features, list):
             raise TypeError("features must be a list")
@@ -140,9 +140,6 @@ def __init__(
         :param label: labels of the sample (optional)
         :param name: id of the sample (optional)
 
-        :type features: list of lists
-        :type label: float, int or None
-        :type name: object
         """
         self._check_features(features)
         super(MultiFeatureSample, self).__init__(features, label, name)
@@ -160,17 +157,31 @@ class Dataset(
 
     @property
     def _lazyType(self) -> "Type[LazyDataset]":
-        """Specify the type of LazyObject associated to this class."""
+        """
+        Specify the type of LazyObject associated to this class.
+
+        :return: LazyDatasetType
+        """
         return LazyDataset
 
     @property
     def _cachedType(self) -> "Type[CachedDataset]":
-        """Specify the type of CachedObject associated to this class."""
+        """
+        Specify the type of CachedObject associated to this class.
+
+        :return: CachedDatasetType
+        """
         return CachedDataset
 
     @staticmethod
     def checkNames(x: Optional[Union[str, int, Any]]) -> Union[str, int]:
-        """Check that feature names comply with format and cast them to either string or int."""
+        """
+        Check that feature names comply with format and cast them to either string or int.
+
+        :param x: feature name
+        :return: name as int or str
+        :raises AttributeError: if x is none
+        """
         if x is None:
             raise AttributeError("With type 'dict' all samples must have a name")
         else:
@@ -202,6 +213,7 @@ def getFeaturesAs(self, type: AllowedTypes = "array") -> FeaturesType:
 
         :param type: type of return. Can be one of "pandas", "dict", "list" or "array
         :return: an object of the specified type containing the features
+        :raises ValueError: if the provided type is not one of the allowed ones
         """
         if type == "array":
             return np.array([sample.features for sample in self])
@@ -256,6 +268,7 @@ def getLabelsAs(self, type: AllowedTypes = "array") -> Optional[LabelsType]:
 
         :param type: type of return. Can be one of "pandas", "dict", "list" or "array
         :return: an object of the specified type containing the features
+        :raises ValueError: if the provided type is not one of the allowed ones
         """
         if type == "array":
             return np.array([sample.label for sample in self])
@@ -290,9 +303,10 @@ def union(self, other: "Dataset") -> "Dataset":
 
         :param other: Dataset
         :return: LazyDataset
+        :raises TypeError: other is not an instance of Dataset
         """
         if not isinstance(other, Dataset):
-            raise ValueError(
+            raise TypeError(
                 "Union can only be done between Datasets. Found %s" % str(type(other))
             )
 
@@ -324,7 +338,11 @@ def to_df(self) -> pd.DataFrame:
 
     @property
     def asPandasDataset(self) -> "PandasDataset":
-        """Cast object as a PandasDataset."""
+        """
+        Cast object as a PandasDataset.
+
+        :return: dataset
+        """
         return PandasDataset(self.getFeaturesAs("pandas"), self.getLabelsAs("pandas"))
 
 
@@ -440,13 +458,14 @@ def __init__(
 
         :param features: a dataframe or a series of features
         :param labels: a dataframe or a series of labels. None in case no labels are present.
+        :raises TypeError: if the labels or features are not DataFrames nor Series
         """
         if isinstance(features, pd.Series):
             self._features = features.to_frame()
         elif isinstance(features, pd.DataFrame):
             self._features = features
         else:
-            raise ValueError(
+            raise TypeError(
                 "Features must be of type pandas.Series or pandas.DataFrame"
             )
 
@@ -457,7 +476,7 @@ def __init__(
         elif labels is None:
             self._labels = labels
         else:
-            raise ValueError(
+            raise TypeError(
                 "Labels must be of type pandas.Series or pandas.DataFrame or None"
             )
 
@@ -466,7 +485,7 @@ def items(self) -> Iterator[Sample]:
         """
         Get features as an iterator of Samples.
 
-        :return: Iterator of objects of :class:`cgnal.data.model.ml.Sample`
+        :yield: Iterator of objects of :class:`cgnal.data.model.ml.Sample`
         """
         for index, row in dict(self._features.to_dict(orient="index")).items():
             try:
@@ -580,6 +599,7 @@ def dropna(self, **kwargs) -> "PandasDataset":
         """
         Drop NAs from feature and labels.
 
+        :param kwargs: keyworded arguments are passed to dropna
         :return: ``PandasDataset`` with features and labels without NAs
         """
         kwargs_feat = {
@@ -637,6 +657,7 @@ def getFeaturesAs(self, type: AllowedTypes = "array") -> FeaturesType:
 
         :param type: str, default is 'array', can be 'array','pandas','dict'
         :return: features according to the given type
+        :raises ValueError: provided type not allowed
         """
         if type == "array":
             return np.array(self._features)
@@ -678,6 +699,7 @@ def getLabelsAs(self, type: AllowedTypes = "array") -> LabelsType:
 
         :param type: str, default is 'array', can be 'array','pandas','dict'
         :return: labels according to the given type
+        :raises ValueError: provided type not allowed
         """
         if self._labels is None:
             return None
@@ -729,8 +751,8 @@ def union(self, other: "Dataset") -> "Dataset":
         """
         Return a union between datasets.
 
-        other: Dataset to be merged
-        return: Dataset resulting from the merge
+        :param other: Dataset to be merged
+        :return: Dataset resulting from the merge
         """
         if isinstance(other, self.__class__):
             features = pd.concat([self.features, other.features])
diff --git a/cgnal/core/data/model/text.py b/cgnal/core/data/model/text.py
index a22952c..3d30a94 100644
--- a/cgnal/core/data/model/text.py
+++ b/cgnal/core/data/model/text.py
@@ -32,7 +32,11 @@
 
 
 def generate_random_uuid() -> bytes:
-    """Create a random number with 12 digits."""
+    """
+    Create a random number with 12 digits.
+
+    :return: uuid
+    """
     return uuid.uuid1().bytes[:12]
 
 
@@ -50,7 +54,11 @@ def __init__(self, uuid: K, data: Dict[str, Any]):
         self.data = data
 
     def __str__(self) -> str:
-        """Return string description of the class."""
+        """
+        Return string description of the class.
+
+        :return: string rep
+        """
         return f"Id: {self.uuid}"
 
     def getOrThrow(self, key: str, default: Optional[Any] = None) -> Optional[Any]:
@@ -60,6 +68,7 @@ def getOrThrow(self, key: str, default: Optional[Any] = None) -> Optional[Any]:
         :param key: key to retrieve
         :param default: default value to return
         :return: retrieve element
+        :raises KeyError: if key not found and default not provided
         """
         try:
             return self.data[key]
@@ -137,7 +146,7 @@ def properties(self) -> Iterator[str]:
         """
         Yield data properties names.
 
-        :return: iterator with data properties names
+        :yield: iterator with data properties names
         """
         for prop in self.data.keys():
             yield prop
@@ -146,7 +155,7 @@ def items(self) -> Iterator[Tuple[str, Any]]:
         """
         Yield data items.
 
-        :return: iterator with tuples of data properties names and values
+        :yield: iterator with tuples of data properties names and values
         """
         for prop in self.properties:
             yield prop, self[prop]
@@ -157,12 +166,20 @@ class Documents(_IterableUtils[Document, "CachedDocuments", "LazyDocuments"], AB
 
     @property
     def _lazyType(self) -> "Type[LazyDocuments]":
-        """Specify the type of LazyObject associated to this class."""
+        """
+        Specify the type of LazyObject associated to this class.
+
+        :return: LazyDocuments type
+        """
         return LazyDocuments
 
     @property
     def _cachedType(self) -> "Type[CachedDocuments]":
-        """Specify the type of CachedObject associated to this class."""
+        """
+        Specify the type of CachedObject associated to this class.
+
+        :return: CachedDocuments type
+        """
         return CachedDocuments
 
 
@@ -171,7 +188,13 @@ class CachedDocuments(_CachedIterable[Document], DillSerialization, Documents):
 
     @staticmethod
     def _get_key(key: str, dict: Dict[str, Any]) -> Any:
-        """Return the property of the dictionary or nan."""
+        """
+        Return the property of the dictionary or nan.
+
+        :param key: key
+        :param dict: dict
+        :return: key
+        """
         try:
             out = dict
             for level in key.split("."):
diff --git a/cgnal/core/logging/__init__.py b/cgnal/core/logging/__init__.py
index 6253e67..56c73c5 100644
--- a/cgnal/core/logging/__init__.py
+++ b/cgnal/core/logging/__init__.py
@@ -31,12 +31,8 @@ class WithLoggingABC(ABC):
     @property
     @abstractmethod
     def logger(self) -> Logger:
-        """
-        Logger instance to be used to output logs within a class.
-
-        :return: None, outputs logs
-        """
-        pass
+        """Logger instance to be used to output logs within a class."""
+        raise NotImplementedError
 
 
 class LoggingConfig(BaseConfig):
@@ -44,20 +40,36 @@ class LoggingConfig(BaseConfig):
 
     @property
     def level(self) -> str:
-        """Returnn logging level."""
+        """
+        Returnn logging level.
+
+        :return: level
+        """
         return self.getValue("level")
 
     @property
     def filename(self) -> PathLike:
-        """Name of the file where logs are stored."""
+        """
+        Name of the file where logs are stored.
+
+        :return: filename
+        """
         return self.getValue("filename")
 
     @property
     def default_config_file(self) -> PathLike:
-        """Return default logging configuration file."""
+        """
+        Return default logging configuration file.
+
+        :return: default config file
+        """
         return self.getValue("default_config_file")
 
     @property
     def capture_warnings(self) -> bool:
-        """Flag that determines whether waring are captured."""
+        """
+        Flag that determines whether waring are captured.
+
+        :return: capture warnings
+        """
         return self.getValue("capture_warnings")
diff --git a/cgnal/core/logging/defaults.py b/cgnal/core/logging/defaults.py
index ba8d5cb..7e4c85e 100644
--- a/cgnal/core/logging/defaults.py
+++ b/cgnal/core/logging/defaults.py
@@ -87,7 +87,6 @@ def configFromFiles(
     :param config_files: list of configuration files
     :param capture_warnings: whether to capture warnings with logger
     :param catch_exceptions: name of the logger used to catch exceptions. If None do not catch exception with loggers.
-    :return: None
     """
     captureWarnings(capture_warnings)
 
diff --git a/cgnal/core/typing/__init__.py b/cgnal/core/typing/__init__.py
index 42312cb..899aff8 100644
--- a/cgnal/core/typing/__init__.py
+++ b/cgnal/core/typing/__init__.py
@@ -18,7 +18,7 @@ def __lt__(self, other: Any) -> bool:
         """
         Less than operator.
 
-        :param Any other: other operand
+        :param other: other operand
         :return bool: whether self is less than other
         """
         ...
diff --git a/cgnal/core/utils/cloud.py b/cgnal/core/utils/cloud.py
index 337d6b9..dc127c8 100644
--- a/cgnal/core/utils/cloud.py
+++ b/cgnal/core/utils/cloud.py
@@ -52,7 +52,7 @@ def get(self, filename: PathLike) -> None:
         Return file from the url.
 
         :param filename: name of file to be downloaded
-        :return: None
+        :raises FileNotFoundError: if file is not found
         """
         self.logger.info(f"Getting resource {filename} from {self.url}")
 
@@ -111,11 +111,7 @@ class HTTPRequestHandler(WithLogging, http.server.SimpleHTTPRequestHandler):
     """Performs POST operation."""
 
     def do_POST(self) -> None:
-        """
-        Post given resource.
-
-        :return: None
-        """
+        """Post given resource."""
         path = self.translate_path(self.path)
 
         dirname = os.path.dirname(path)
diff --git a/cgnal/core/utils/decorators.py b/cgnal/core/utils/decorators.py
index 4891f38..d7af4f2 100644
--- a/cgnal/core/utils/decorators.py
+++ b/cgnal/core/utils/decorators.py
@@ -50,7 +50,11 @@ class Cached(object):
 
     @property
     def _cache(self):
-        """Hidden property that stores the custom cache that can be exported and imported."""
+        """
+        Hidden property that stores the custom cache that can be exported and imported.
+
+        :return: cached data
+        """
         try:
             return self._cache_data
         except AttributeError:
@@ -78,11 +82,7 @@ def _wrap(obj: "Cached"):
         return property(_wrap)
 
     def clear_cache(self) -> None:
-        """
-        Clear cache of the object.
-
-        :return: None
-        """
+        """Clear cache of the object."""
         self._cache.clear()
 
     def save_pickles(self, path: PathLike) -> None:
@@ -90,7 +90,6 @@ def save_pickles(self, path: PathLike) -> None:
         Save pickle in given path.
 
         :param path: saving path
-        :return: None
         """
         path = create_dir_if_not_exists(path)
 
@@ -104,7 +103,7 @@ def save_element(filename: PathLike, obj: Any) -> None:
 
         :param filename: saving path
         :param obj: object to be saved
-        :return: None
+        :raises Exception: if any error occurs while saving the file
         """
         if isinstance(obj, dict):
             create_dir_if_not_exists(filename)
@@ -114,18 +113,16 @@ def save_element(filename: PathLike, obj: Any) -> None:
         else:
             try:
                 pd.to_pickle(obj, "%s.p" % filename)
-            except Exception:
-                raise ValueError("Cannot save input of type %s" % str(obj.__class__))
+            except Exception as ex:
+                raise type(ex)("Cannot save input of type %s" % str(obj.__class__))
 
     def load(self, filename: PathLike) -> None:
         """
         Load pickle at given path (or all pickles in given folder).
 
         :param filename: path to pickles
-        :return: None
         """
         self._cache.update(dict(self.load_element(filename, "")))
-        return None
 
     @classmethod
     def load_element(
@@ -136,7 +133,7 @@ def load_element(
 
         :param filename: name of the file/directory where the data should be taken from
         :param prefix: prefix to add to elements in filename in case it is a directory
-        :return: iterable of couples (path, loaded object)
+        :yield: iterable of couples (path, loaded object)
         """
         if os.path.isdir(filename):
             for path in glob(os.path.join(filename, "*")):
diff --git a/cgnal/core/utils/dict.py b/cgnal/core/utils/dict.py
index 8ba8fa7..72fe85b 100644
--- a/cgnal/core/utils/dict.py
+++ b/cgnal/core/utils/dict.py
@@ -151,7 +151,7 @@ def groupBy(
 
     :param lst: List
     :param key: function to be used as a key to perform groupby operation
-    :return: Iterator
+    :yield: Iterator
     """
     for k, it in groupby(sorted(lst, key=key), key=key):
         yield k, list(it)
diff --git a/cgnal/core/utils/email.py b/cgnal/core/utils/email.py
index 6cd72eb..103910a 100644
--- a/cgnal/core/utils/email.py
+++ b/cgnal/core/utils/email.py
@@ -54,7 +54,7 @@ def send_mail(
         :param subject: The subject of the email
         :param destination: The destination email address
         :param attachments: List with the files to send as attachments to the email
-        :return: None
+        :raises NotImplementedError: if given protocol is not implemented
         """
         msg = MIMEMultipart()
         msg["Subject"] = subject
@@ -69,24 +69,21 @@ def send_mail(
                 part["Content-Disposition"] = 'attachment; filename="%s"' % basename(f)
                 msg.attach(part)
 
-        try:
-            if self.auth_protocol == "SSL":
-                port = 465 if self.port is None else self.port
-                server: Union[smtplib.SMTP_SSL, smtplib.SMTP] = smtplib.SMTP_SSL(
-                    self.smtp_address, port=port
-                )
-            elif self.auth_protocol == "TLS":
-                port = 587 if self.port is None else self.port
-                server = smtplib.SMTP(self.smtp_address, port=port)
-                server.starttls()
-            elif self.auth_protocol == "None":
-                port = 25 if self.port is None else self.port
-                server = smtplib.SMTP(self.smtp_address, port=port)
-            else:
-                raise Exception(f"{self.auth_protocol} not implemented")
-            server.ehlo()
-            server.login(self.username, self.password)
-            server.sendmail(self.email_address, destination, msg.as_string())
-            server.close()
-        except Exception as e:
-            raise e
+        if self.auth_protocol == "SSL":
+            port = 465 if self.port is None else self.port
+            server: Union[smtplib.SMTP_SSL, smtplib.SMTP] = smtplib.SMTP_SSL(
+                self.smtp_address, port=port
+            )
+        elif self.auth_protocol == "TLS":
+            port = 587 if self.port is None else self.port
+            server = smtplib.SMTP(self.smtp_address, port=port)
+            server.starttls()
+        elif self.auth_protocol == "None":
+            port = 25 if self.port is None else self.port
+            server = smtplib.SMTP(self.smtp_address, port=port)
+        else:
+            raise NotImplementedError(f"{self.auth_protocol} not implemented")
+        server.ehlo()
+        server.login(self.username, self.password)
+        server.sendmail(self.email_address, destination, msg.as_string())
+        server.close()
diff --git a/cgnal/core/utils/fs.py b/cgnal/core/utils/fs.py
index bf9a7d1..8523047 100644
--- a/cgnal/core/utils/fs.py
+++ b/cgnal/core/utils/fs.py
@@ -11,7 +11,7 @@ def mkdir(path: PathLike) -> None:
     Create a dir, using a formulation consistent between 2.x and 3.x python versions.
 
     :param path: path to create
-    :return: None
+    :raises OSError: whenever OSError is raised by makedirs and it's not because the directory exists
     """
     try:
         os.makedirs(path)
diff --git a/requirements/requirements_ci.in b/requirements/requirements_ci.in
index 75e1e1a..6e0bfcd 100644
--- a/requirements/requirements_ci.in
+++ b/requirements/requirements_ci.in
@@ -1,4 +1,5 @@
 black>=21.12b0
+darglint>=1.8.1
 flake8>=4.0.1
 flake8-docstrings>=1.6
 flake8-rst-docstrings>=0.2.5
diff --git a/requirements/requirements_ci.txt b/requirements/requirements_ci.txt
index dda1881..61564bc 100644
--- a/requirements/requirements_ci.txt
+++ b/requirements/requirements_ci.txt
@@ -34,6 +34,8 @@ commonmark==0.9.1
     # via recommonmark
 coverage[toml]==6.3.2
     # via pytest-cov
+darglint==1.8.1
+    # via -r requirements/requirements_ci.in
 deprecated==1.2.13
     # via -r requirements/requirements.in
 dill==0.3.4
diff --git a/setup.cfg b/setup.cfg
index 8b1a8b7..4360981 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,7 +26,10 @@ ignore =
 # See https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length for more details
 max-line-length = 80
 max-complexity = 18
-select = B,C,D,E,F,W,T4,B9,RST
+select = B,C,D,E,F,W,T4,B9,RST,DAR
+
+[darglint]
+docstring_style=sphinx
 
 [sphinx]
 package_dir = cgnal/core


Test patch:
diff --git a/cgnal/core/tests/core.py b/cgnal/core/tests/core.py
index c2063bd..9a35d13 100644
--- a/cgnal/core/tests/core.py
+++ b/cgnal/core/tests/core.py
@@ -18,7 +18,6 @@ def compareLists(self, first: list, second: list, strict: bool = False) -> None:
         :param first: first list
         :param second: second list
         :param strict: whether to check element by element (strict) or only dimensions (non-strict)
-        :return: None
         """
         if strict:
             for item1, item2 in zip(first, second):
@@ -33,7 +32,6 @@ def compareDicts(self, first: dict, second: dict, strict: bool = False) -> None:
         :param first: first dictionary
         :param second: second dictionary
         :param strict: whether to check element by element (strict) or only dimensions (non-strict)
-        :return: None
         """
         for key in set(first.keys()).union(second.keys()):
             if isinstance(first[key], dict):
@@ -52,7 +50,7 @@ def compareDataFrames(
         :param first: first dataframe
         :param second: second dataframe
         :param msg: message
-        :return: None
+        :raises failureException: if the dataframe are different
         """
         try:
             pd.testing.assert_frame_equal(first, second)
@@ -66,7 +64,7 @@ def compareSeries(self, first: pd.Series, second: pd.Series, msg: str) -> None:
         :param first: first series
         :param second: second series
         :param msg: message
-        :return: None
+        :raises failureException: if the dataframe are different
         """
         try:
             pd.testing.assert_series_equal(first, second)
@@ -80,7 +78,7 @@ def compareArrays(self, first: np.ndarray, second: np.ndarray, msg: str) -> None
         :param first: first array
         :param second: second array
         :param msg: message
-        :return: None
+        :raises failureException: if the dataframe are different
         """
         try:
             np.testing.assert_almost_equal(first, second, decimal=7)
@@ -88,11 +86,7 @@ def compareArrays(self, first: np.ndarray, second: np.ndarray, msg: str) -> None
             raise self.failureException("Input arrays are different") from e
 
     def setUp(self) -> None:
-        """
-        Set up the class to add custom comparing/assertion functionalities.
-
-        :return: None
-        """
+        """Set up the class to add custom comparing/assertion functionalities."""
         self.addTypeEqualityFunc(pd.DataFrame, self.compareDataFrames)
         self.addTypeEqualityFunc(pd.Series, self.compareSeries)
         self.addTypeEqualityFunc(np.ndarray, self.compareArrays)