diff --git a/.codeclimate.yml b/.codeclimate.yml
index c579fd98302bbc230dea126b5ed41dbecb5f532a..1cb06657e4f8399818264cf16f07648e1fa632b7 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -8,6 +8,19 @@ plugins:
     enabled: true
   pylint:
     enabled: true
+    checks:
+      missing-module-docstring:
+        enabled: false
+      missing-class-docstring:
+        enabled: false
+    missing-function-docstring:
+        enabled: false
+    global-statement:
+        enabled: false
+    invalid-name:
+        enabled: false
+    too-many-arguments:
+        enabled: false
   radon:
     enabled: true
 exclude_paths:
diff --git a/README.md b/README.md
index 7e965e8dfc2abd09b8222616a80a86375d780380..43d9aca7cc7769e015bb04f28b084b7748eddadb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 satella
 ========
-![example workflow](https://github.com/piotrmaslanka/satella/actions/workflows/ci.yml/badge.svg)[![Code Climate](https://codeclimate.com/github/piotrmaslanka/satella/badges/gpa.svg)](https://codeclimate.com/github/piotrmaslanka/satella)
+![Workflow](https://github.com/piotrmaslanka/satella/actions/workflows/ci.yml/badge.svg)[![Code Climate](https://codeclimate.com/github/piotrmaslanka/satella/badges/gpa.svg)](https://codeclimate.com/github/piotrmaslanka/satella)
 [![Issue Count](https://codeclimate.com/github/piotrmaslanka/satella/badges/issue_count.svg)](https://codeclimate.com/github/piotrmaslanka/satella)
 [![PyPI](https://img.shields.io/pypi/pyversions/satella.svg)](https://pypi.python.org/pypi/satella)
 [![PyPI version](https://badge.fury.io/py/satella.svg)](https://badge.fury.io/py/satella)
@@ -9,7 +9,7 @@ satella
 [![License](https://img.shields.io/pypi/l/satella)](https://github.com/piotrmaslanka/satella)
 [![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/pylint-dev/pylint)
 
-Satella is an almost-zero-requirements Python 3.5+ library for writing server applications. It has arisen out of my
+Satella is an almost-zero-requirements Python 3.7+ library for writing server applications. It has arisen out of my
 requirements to have some classes or design patterns handy, and kinda wish-they-were-in-the-stdlib ones. especially
 those dealing with mundane but useful things. It also runs on PyPy, and most of it runs on Windows (the part not dealing
 with forking processes, you see).
@@ -18,7 +18,9 @@ Satella uses [semantic versioning 2.0](https://semver.org/spec/v2.0.0.html).
 
 Satella contains, among other things:
 
-* things to help you manage your [application's configuration](satella/configuration)
+* things to help you manage your [application's configuration](satella/configuration) that allows
+    you to both load a configuration and specify it's schema using only
+    Python dictionaries
 * a fully equipped [metrics library](satella/instrumentation/metrics)
     * alongside a fully metricized [ThreadPoolExecutor](satella/instrumentation/metrics/structures/threadpool.py)
     * and an exporter to [Prometheus](satella/instrumentation/metrics/exporters/prometheus.py) or really any
@@ -29,7 +31,8 @@ Satella contains, among other things:
         * [FastAPI](https://github.com/Dronehub/fastapi-satella-metrics)
         * [Django](https://github.com/piotrmaslanka/django-satella-metrics)
         * [Flask](https://github.com/piotrmaslanka/flask-satella-metrics)
-* helpful [exception handlers](satella/exception_handling)
+* helpful [exception handlers](satella/exception_handling) as well as capacity to dump all stack frames
+    along with their local variables for each thread
 * monitoring [CPU usage](satella/instrumentation/cpu_time/collectors) on the system and by your own process
 * common programming [idioms and structures](satella/coding)
 
diff --git a/pyproject.toml b/pyproject.toml
index ea9de4e3f60ab4ded5042a8ed605491f20d38c44..5f4771da2d7d218e28f11b24263b51df31dcbc64 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,7 +24,7 @@ classifiers = [
     "License :: OSI Approved :: MIT License",
     "Topic :: Software Development :: Libraries"
 ]
-dependencies = ["psutil"]
+dependencies = ["psutil>=5.9.8"]
 
 [tool.setuptools.dynamic]
 version = { attr = "satella.__version__" }
diff --git a/satella/cassandra/common.py b/satella/cassandra/common.py
index 448f63edaf5cd05c0fe0c891fbb398b4511e4593..acb3224d0c8a64a02b262899685601f66984efe1 100644
--- a/satella/cassandra/common.py
+++ b/satella/cassandra/common.py
@@ -1,5 +1,5 @@
 try:
     from cassandra.cluster import ResponseFuture
 except ImportError:
-    class ResponseFuture:
+    class ResponseFuture:           # pylint: disable=too-few-public-methods
         pass
diff --git a/satella/cassandra/future.py b/satella/cassandra/future.py
index 8ffa1d1a96515155a48a1f53137be504407e28d2..b0316ce79fdedd83aae6d252e9144d68e526f3c0 100644
--- a/satella/cassandra/future.py
+++ b/satella/cassandra/future.py
@@ -18,6 +18,6 @@ def wrap_future(future: ResponseFuture) -> Future:
 
     fut = Future()
     fut.set_running_or_notify_cancel()
-    future.add_callback(lambda result: fut.set_result(result))
-    future.add_errback(lambda exception: fut.set_exception(exception))
+    future.add_callback(fut.set_result)
+    future.add_errback(fut.set_exception)
     return fut
diff --git a/satella/cassandra/parallel.py b/satella/cassandra/parallel.py
index 05ad59cda32051b685f2b776ca96d5ac5688f425..fc8def3e5938d3368e8e84bb2c6eda8badbe7818 100644
--- a/satella/cassandra/parallel.py
+++ b/satella/cassandra/parallel.py
@@ -37,7 +37,7 @@ def parallel_for(cursor, query: tp.Union[tp.List[str], str, 'Statement', tp.List
     """
     warnings.warn('This is deprecated and will be removed in Satella 3.0', DeprecationWarning)
     try:
-        from cassandra.query import Statement
+        from cassandra.query import Statement       # pylint: disable=import-outside-toplevel
         query_classes = (str, Statement)
     except ImportError:
         query_classes = str
@@ -46,11 +46,8 @@ def parallel_for(cursor, query: tp.Union[tp.List[str], str, 'Statement', tp.List
         query = itertools.repeat(query)
 
     futures = []
-    for query, args in zip(query, arguments):
-        if args is None:
-            future = cursor.execute_async(query)
-        else:
-            future = cursor.execute_async(query, args)
+    for loc_query, args in zip(query, arguments):
+        future = cursor.execute_async(loc_query) if args is None else cursor.execute_async(loc_query, args)
         futures.append(future)
 
     for future in futures:
diff --git a/satella/coding/expect_exception.py b/satella/coding/expect_exception.py
index 0caea5e798ca7a73b1bee59aba571b5d1943f293..d54ea94841b6a5e7664a81168d1d4eb9a4892011 100644
--- a/satella/coding/expect_exception.py
+++ b/satella/coding/expect_exception.py
@@ -34,8 +34,7 @@ class expect_exception:
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         if exc_type is None:
-            raise self.else_raise(*self.else_raise_args,
-                                  **self.else_raise_kwargs)
+            raise self.else_raise(*self.else_raise_args, **self.else_raise_kwargs)
         elif not isinstance(exc_val, self.exc_to_except):
             return False
         return True
diff --git a/satella/coding/misc.py b/satella/coding/misc.py
index 45fc3c11625872e66d6d854ef69f9c709490270e..1152e5e1fc369d2c23277bde603f03ea071f1cc2 100644
--- a/satella/coding/misc.py
+++ b/satella/coding/misc.py
@@ -62,7 +62,7 @@ def contains(needle, haystack) -> bool:
 
 class Closeable:
     """
-    A class that needs to clean up it's own resources.
+    A class that needs to clean up its own resources.
 
     It's destructor calls .close(). Use like this:
 
@@ -168,8 +168,7 @@ def chain_callables(callable1: tp.Callable, callable2: tp.Callable) -> tp.Callab
         try:
             res = callable2(res)
         except TypeError as e:
-            if 'positional arguments but' in e.args[0] \
-                    and 'was given' in e.args[0] and 'takes' in e.args[0]:
+            if 'positional arguments but' in e.args[0] and 'was given' in e.args[0] and 'takes' in e.args[0]:
                 res = callable2()
             else:
                 raise
@@ -195,8 +194,7 @@ def source_to_function(src: tp.Union[tp.Callable, str]) -> tp.Callable[[tp.Any],
         q = dict(globals())
         exec('_precond = lambda x: ' + src, q)
         return q['_precond']
-    else:
-        return src
+    return src
 
 
 def update_attr_if_none(obj: object, attr: str, value: tp.Any,
@@ -224,14 +222,13 @@ def update_attr_if_none(obj: object, attr: str, value: tp.Any,
             if val is None:
                 setattr(obj, attr, value)
         except AttributeError:
-            if on_attribute_error:
-                setattr(obj, attr, value)
-            else:
+            if not on_attribute_error:
                 raise
+            setattr(obj, attr, value)
     return obj
 
 
-class _BLANK:
+class _BLANK:       # pylint: disable=too-few-public-methods
     pass
 
 
@@ -257,8 +254,7 @@ def update_key_if_true(dictionary: tp.Dict, key: tp.Hashable, value: tp.Any,
     return dictionary
 
 
-def get_arguments(function: tp.Callable, *args, **kwargs) -> \
-        tp.Dict[str, tp.Any]:
+def get_arguments(function: tp.Callable, *args, **kwargs) -> tp.Dict[str, tp.Any]:
     """
     Return local variables that would be defined for given function if called with
     provided arguments.
@@ -276,6 +272,7 @@ def get_arguments(function: tp.Callable, *args, **kwargs) -> \
     return _get_arguments(function, False, *args, **kwargs)
 
 
+# pylint: disable=too-many-locals
 @rethrow_as(IndexError, TypeError)
 def _get_arguments(function: tp.Callable, special_behaviour: bool, *args, **kwargs):
     """
@@ -286,13 +283,11 @@ def _get_arguments(function: tp.Callable, special_behaviour: bool, *args, **kwar
     local_vars = {}
 
     positionals = [param for param in reversed(params) if
-                   param.kind in (Parameter.POSITIONAL_OR_KEYWORD,
-                                  Parameter.POSITIONAL_ONLY,
-                                  Parameter.VAR_POSITIONAL)]
+                   param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL)]
     args = list(reversed(args))
 
     arguments_left = set(param.name for param in params)
-    while len(positionals):
+    while positionals:
         arg = positionals.pop()
         arg_kind = arg.kind
         arg_name = arg.name
@@ -306,8 +301,7 @@ def _get_arguments(function: tp.Callable, special_behaviour: bool, *args, **kwar
                 try:
                     if arg.default != Parameter.empty:
                         raise AttributeError()
-                    else:
-                        break
+                    break
                 except (AttributeError, TypeError):
                     v = arg.default
             local_vars[arg_name] = v
@@ -321,30 +315,28 @@ def _get_arguments(function: tp.Callable, special_behaviour: bool, *args, **kwar
         keyword_name = keyword.name
         if keyword.kind == Parameter.VAR_KEYWORD and not special_behaviour:
             local_vars[keyword_name] = kwargs
-        else:
+            continue
+        try:
+            v = kwargs.pop(keyword_name)
+        except KeyError:
             try:
-                v = kwargs.pop(keyword_name)
-            except KeyError:
-                try:
-                    if Parameter.empty == keyword.default:
-                        if special_behaviour:
-                            v = None
-                        else:
-                            raise TypeError('Not enough keyword arguments')
-                    else:
-                        v = keyword.default
-                except (AttributeError, TypeError):
-                    continue  # comparison was impossible
+                if Parameter.empty == keyword.default:
+                    if not special_behaviour:
+                        raise TypeError('Not enough keyword arguments')
+                    v = None
+                else:
+                    v = keyword.default
+            except (AttributeError, TypeError):
+                continue  # comparison was impossible
 
-            local_vars[keyword_name] = v
+        local_vars[keyword_name] = v
 
     for param in params:
         param_name = param.name
         if param_name not in local_vars:
-            if special_behaviour:
-                local_vars[param_name] = None
-            else:
+            if not special_behaviour:
                 raise TypeError('Not enough keyword arguments')
+            local_vars[param_name] = None
 
     return local_vars
 
@@ -373,8 +365,7 @@ def call_with_arguments(function: tp.Callable, arguments: tp.Dict[str, tp.Any])
                 continue
             elif param.default == Parameter.empty:
                 raise TypeError('Argument %s not found' % (param_name,))
-            else:
-                continue
+            continue
 
         if param_kind == Parameter.POSITIONAL_ONLY or param_kind == Parameter.POSITIONAL_OR_KEYWORD:
             args.append(arguments.pop(param_name))
diff --git a/satella/coding/recast_exceptions.py b/satella/coding/recast_exceptions.py
index 6c2b34b2f7ef44b4d9ce5663519363abee75ed41..738ced9fe63ea26df01470d9b3c73e9fb082278a 100644
--- a/satella/coding/recast_exceptions.py
+++ b/satella/coding/recast_exceptions.py
@@ -31,8 +31,7 @@ def silence_excs(*exc_types: ExceptionClassType, returns=None,
 
     :raises ValueError: you gave both returns and returns_factory. You can only pass one of them!
     """
-    return rethrow_as(exc_types, None, returns=returns,
-                      returns_factory=returns_factory)
+    return rethrow_as(exc_types, None, returns=returns, returns_factory=returns_factory)
 
 
 class log_exceptions:
@@ -59,8 +58,7 @@ class log_exceptions:
         log on all exceptions
     :param swallow_exception: if True, exception will be swallowed
     """
-    __slots__ = ('logger', 'severity', 'format_string', 'locals', 'exc_types',
-                 'swallow_exception')
+    __slots__ = 'logger', 'severity', 'format_string', 'locals', 'exc_types', 'swallow_exception'
 
     def __init__(self, logger: logging.Logger,
                  severity: int = logging.ERROR,
@@ -81,24 +79,20 @@ class log_exceptions:
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         if exc_type is not None:
-            if not self.analyze_exception(exc_val, (), {}):
-                return False
-            else:
-                return self.swallow_exception
+            return False if not self.analyze_exception(exc_val, (), {}) else self.swallow_exception
         return False
 
     def analyze_exception(self, e, args, kwargs) -> bool:
         """Return whether the exception has been logged"""
-        if isinstance(e, self.exc_types):
-            format_dict = {'args': args,
-                           'kwargs': kwargs}
-            if self.locals is not None:
-                format_dict.update(self.locals)
-            format_dict['e'] = e
-            self.logger.log(self.severity, self.format_string.format(**format_dict),
-                            exc_info=e)
-            return True
-        return False
+        if not isinstance(e, self.exc_types):
+            return False
+        format_dict = {'args': args, 'kwargs': kwargs}
+        if self.locals is not None:
+            format_dict.update(self.locals)
+        format_dict['e'] = e
+        self.logger.log(self.severity, self.format_string.format(**format_dict),
+                        exc_info=e)
+        return True
 
     def __call__(self, fun):
         if inspect.isgeneratorfunction(fun):
@@ -107,10 +101,8 @@ class log_exceptions:
                 # noinspection PyBroadException
                 try:
                     yield from fun(*args, **kwargs)
-                except Exception as e:
-                    if not self.analyze_exception(e, args, kwargs):
-                        raise
-                    elif not self.swallow_exception:
+                except Exception as e:      # pylint: disable=broad-except
+                    if not self.analyze_exception(e, args, kwargs) or not self.swallow_exception:
                         raise
 
             return inner
@@ -120,10 +112,8 @@ class log_exceptions:
                 # noinspection PyBroadException
                 try:
                     return fun(*args, **kwargs)
-                except Exception as e:
-                    if not self.analyze_exception(e, args, kwargs):
-                        raise
-                    elif not self.swallow_exception:
+                except Exception as e:      # pylint: disable=broad-except
+                    if not self.analyze_exception(e, args, kwargs) or not self.swallow_exception:
                         raise
 
             return inner
@@ -175,12 +165,12 @@ class reraise_as:
         def inner(*args, **kwargs):
             try:
                 return fun(*args, **kwargs)
-            except Exception as e:
+            except Exception as e:          # pylint: disable=broad-except
                 if isinstance(e, self.source):
                     if self.target_exc is not None:
                         raise self.target_exc(*self.args, **self.kwargs) from e
-                else:
-                    raise
+                    return
+                raise
 
         return inner
 
@@ -234,8 +224,7 @@ class rethrow_as:
         is used as as decorator
     :raises ValueError: you specify both returns and returns_factory
     """
-    __slots__ = 'mapping', 'exception_preprocessor', 'returns', '__exception_remapped', \
-                'returns_factory'
+    __slots__ = 'mapping', 'exception_preprocessor', 'returns', '__exception_remapped', 'returns_factory'
 
     def __init__(self, *pairs: ExceptionList,
                  exception_preprocessor: tp.Optional[tp.Callable[[Exception], str]] = repr,
@@ -276,8 +265,7 @@ class rethrow_as:
                 elif self.returns_factory is not None:
                     return self.returns_factory()
                 return
-            else:
-                return v
+            return v
 
         return inner
 
@@ -292,8 +280,7 @@ class rethrow_as:
                     self.__exception_remapped.was_raised = True
                     if to is None:
                         return True
-                    else:
-                        raise to(self.exception_preprocessor(exc_val))
+                    raise to(self.exception_preprocessor(exc_val))
 
 
 def raises_exception(exc_class: tp.Union[ExceptionClassType, tp.Tuple[ExceptionClassType, ...]],
@@ -305,8 +292,7 @@ def raises_exception(exc_class: tp.Union[ExceptionClassType, tp.Tuple[ExceptionC
         clb()
     except exc_class:
         return True
-    else:
-        return False
+    return False
 
 
 def catch_exception(exc_class: tp.Union[ExceptionClassType, tp.Tuple[ExceptionClassType, ...]],
diff --git a/satella/json.py b/satella/json.py
index 9a74d00065d1efc4f5b1a01e58933a421456334c..073412df2b619a4fc6c6fe7f07935d9f438a5009 100644
--- a/satella/json.py
+++ b/satella/json.py
@@ -1,6 +1,7 @@
 import enum
 import json
 import typing as tp
+import warnings
 from abc import ABCMeta, abstractmethod
 
 from satella.coding.typing import NoneType
@@ -42,17 +43,16 @@ class JSONEncoder(json.JSONEncoder):
             try:
                 v = super().default(o)
             except TypeError:
-                dct = {}
+                v = {}
                 try:
-                    for k, v in o.__dict__.items():
-                        dct[k] = self.default(v)
+                    for k, val in o.__dict__.items():
+                        v[k] = self.default(val)
                 except AttributeError:  # o has no attribute '__dict__', try with slots
                     try:
                         for slot in o.__slots__:
-                            dct[slot] = self.default(getattr(o, slot))
+                            v[slot] = self.default(getattr(o, slot))
                     except AttributeError:  # it doesn't have __slots__ either?
                         v = '<an instance of %s>' % (o.__class__.__name__,)
-                v = dct
         return v
 
 
@@ -73,8 +73,10 @@ def write_json_to_file(path: str, value: JSONAble, **kwargs) -> None:
 
     :param path: path to the file
     :param value: JSON-able content
-    :param kwargs: will be passed to ujson/json's dump
+    :param kwargs: Legacy argument do not use it, will raise a warning upon non-empty. This never did anything.
     """
+    if kwargs:
+        warnings.warn('Do not use kwargs, it has no effect', DeprecationWarning)
     with open(path, 'w') as f_out:
         f_out.write(JSONEncoder().encode(value))