Repository: candango/firenado (language: python)

Issue:
Remove util module
Let's get everything inside of util placed in a proper module or in a new project to be used as dependency by Firenado.

Creating this ticket because I want stop being bullied by Justin and JRob during the I ❤️ code sessions. 😛    
Use tornado 6.3.x
Tornado [v6.3.3](https://github.com/tornadoweb/tornado/releases/tag/v6.3.3) is out.

Let's prepare to for 6.3.3. Here are some things we need to get done:

- [x] #400
- [x] #414
- [x] #419  
Remove WebUtilHandler
Move all methods from WebUtilHandler to TornadoComponents.


Discussion / hints:

Let's keep an eye on this one https://github.com/tornadoweb/tornado/issues/3156 and see if fits here.


Fix patch:
diff --git a/.gitignore b/.gitignore
index 02a1976..a78bf95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,11 @@ __pycache__
 
 database.sqlite
 
-# Bower
-bower_components
+# Esbuild/yarn
+.yarn
+.yarnrc.yml
+yarn.lock
+node_modules
 
 # C extensions
 *.so
diff --git a/docs/guide/services.rst b/docs/guide/services.rst
index 8067577..81d377f 100644
--- a/docs/guide/services.rst
+++ b/docs/guide/services.rst
@@ -1,20 +1,23 @@
 Services
 ========
 
-The way firenado organizes the logic to be executed in several parts of an
-application is defining services.
+Firenado helps you to write services using the data source structure provided
+defined in the application configuration, and a simple mechanism to hook those
+services to another services, handlers, or any data connected class.
 
-Those services can be injected with the decorator
-``firenado.service.served_by``. This decorator will add an instance of a
-service to a method of any data connected object. Examples of data connected
-classes are ``firenado.tornadoweb.TornadoHandler`` and any descendent of
-``firenado.service.FirenadoService``.
+A service are injected to a data connected class using the
+``firenado.service.with_service`` decorator, that will add an instance of a
+service to the object during the method execution.
 
-Creating a service and decorating a handler:
+Examples of data connected classes are ``firenado.tornadoweb.TornadoHandler``
+and any descendant of ``firenado.service.DataConnectedMixin``.
+
+How to create a service and decorate a handler with it:
 
 .. code-block:: python
 
-   from firenado import service, tornadoweb
+   from firenado.tornadoweb import TornadoHandler
+   from firenado.service import with_service
    # Importing a package with some services
    import another_service_package
 
@@ -26,21 +29,22 @@ Creating a service and decorating a handler:
            self.consumer.write("Something was done")
 
 
-   class MyHandlerBeingServed(tornadoweb.TornadoHandler):
+   class MyHandlerBeingServed(TornadoHandler):
        # A good way to keep the reference is keeping the type hint
        my_service: MyService
        service_from_another_package: another_service_package.AnotherService
 
-       @service.served_by(MyService)
+       @with_service(MyService)
        # you can also set the attribute/property name to be used
-       @service.served_by(another_service_package.AnotherService,
-           attribute_name="service_from_another_package"
-       )
+       @with_service(another_service_package.AnotherService,
+           attribute_name="service_from_another_package")
        def get(self):
-           # The anotation service.served_by added self.my_service
-           # here. The attribute/property name will be converted from the
-           # cammel cased class to dashed separated.
+           # The decorator with_service will add self.my_service during the
+           # method execution. The attribute/property name will be converted to
+           # the snake cased service class name equivalent.
            self.my_service.do_something()
+           # This service was added using the attribute_name parameter of the
+           # with_service decorator
            self.service_from_another_package.do_another_thing()
 
 You can also add services to another services using the decorator:
diff --git a/firenado/__init__.py b/firenado/__init__.py
index 172c2f3..219e616 100644
--- a/firenado/__init__.py
+++ b/firenado/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
 """The Firenado Framework"""
 
 __author__ = "Flávio Gonçalves Garcia <piraz@candango.org>"
-__version__ = (0, 2, 17)
+__version__ = (0, 9, 0, "a1")
 __licence__ = "Apache License V2.0"
 
 
diff --git a/firenado/_pexpect_async_patch.py b/firenado/_pexpect_async_patch.py
new file mode 100644
index 0000000..96ce204
--- /dev/null
+++ b/firenado/_pexpect_async_patch.py
@@ -0,0 +1,120 @@
+"""Implementation of coroutines using ``async def``/``await`` keywords.
+These keywords replaced ``@asyncio.coroutine`` and ``yield from`` from
+Python 3.5 onwards.
+
+This is a monkey patch that will live on Firenado until pexpect get that fixed
+on a release.
+"""
+import asyncio
+import errno
+import signal
+from sys import version_info as py_version_info
+
+from pexpect import EOF
+
+if py_version_info >= (3, 7):
+    # get_running_loop, new in 3.7, is preferred to get_event_loop
+    _loop_getter = asyncio.get_running_loop
+else:
+    # Deprecation warning since 3.10
+    _loop_getter = asyncio.get_event_loop
+
+
+async def expect_async(expecter, timeout=None):
+    # First process data that was previously read - if it maches, we don't need
+    # async stuff.
+    idx = expecter.existing_data()
+    if idx is not None:
+        return idx
+    if not expecter.spawn.async_pw_transport:
+        pattern_waiter = PatternWaiter()
+        pattern_waiter.set_expecter(expecter)
+        transport, pattern_waiter = await _loop_getter().connect_read_pipe(
+            lambda: pattern_waiter, expecter.spawn
+        )
+        expecter.spawn.async_pw_transport = pattern_waiter, transport
+    else:
+        pattern_waiter, transport = expecter.spawn.async_pw_transport
+        pattern_waiter.set_expecter(expecter)
+        transport.resume_reading()
+    try:
+        return await asyncio.wait_for(pattern_waiter.fut, timeout)
+    except asyncio.TimeoutError as exc:
+        transport.pause_reading()
+        return expecter.timeout(exc)
+
+
+async def repl_run_command_async(repl, cmdlines, timeout=-1):
+    res = []
+    repl.child.sendline(cmdlines[0])
+    for line in cmdlines[1:]:
+        await repl._expect_prompt(timeout=timeout, async_=True)
+        res.append(repl.child.before)
+        repl.child.sendline(line)
+
+    # Command was fully submitted, now wait for the next prompt
+    prompt_idx = await repl._expect_prompt(timeout=timeout, async_=True)
+    if prompt_idx == 1:
+        # We got the continuation prompt - command was incomplete
+        repl.child.kill(signal.SIGINT)
+        await repl._expect_prompt(timeout=1, async_=True)
+        raise ValueError("Continuation prompt found - input was incomplete:")
+    return "".join(res + [repl.child.before])
+
+
+class PatternWaiter(asyncio.Protocol):
+    transport = None
+
+    def set_expecter(self, expecter):
+        self.expecter = expecter
+        self.fut = asyncio.Future()
+
+    def found(self, result):
+        if not self.fut.done():
+            self.fut.set_result(result)
+            self.transport.pause_reading()
+
+    def error(self, exc):
+        if not self.fut.done():
+            self.fut.set_exception(exc)
+            self.transport.pause_reading()
+
+    def connection_made(self, transport):
+        self.transport = transport
+
+    def data_received(self, data):
+        spawn = self.expecter.spawn
+        s = spawn._decoder.decode(data)
+        spawn._log(s, "read")
+
+        if self.fut.done():
+            spawn._before.write(s)
+            spawn._buffer.write(s)
+            return
+
+        try:
+            index = self.expecter.new_data(s)
+            if index is not None:
+                # Found a match
+                self.found(index)
+        except Exception as exc:
+            self.expecter.errored()
+            self.error(exc)
+
+    def eof_received(self):
+        # N.B. If this gets called, async will close the pipe (the spawn
+        # object) for us
+        try:
+            self.expecter.spawn.flag_eof = True
+            index = self.expecter.eof()
+        except EOF as exc:
+            self.error(exc)
+        else:
+            self.found(index)
+
+    def connection_lost(self, exc):
+        if isinstance(exc, OSError) and exc.errno == errno.EIO:
+            # We may get here without eof_received being called, e.g on Linux
+            self.eof_received()
+        elif exc is not None:
+            self.error(exc)
diff --git a/firenado/bin/firenado-cli.py b/firenado/bin/firenado-cli.py
index fd886a1..592117a 100644
--- a/firenado/bin/firenado-cli.py
+++ b/firenado/bin/firenado-cli.py
@@ -1,7 +1,6 @@
 #!/usr/bin/env python
-# -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/components/toolbox/uimodules.py b/firenado/components/toolbox/uimodules.py
index 0511e5b..7447fb9 100644
--- a/firenado/components/toolbox/uimodules.py
+++ b/firenado/components/toolbox/uimodules.py
@@ -1,6 +1,4 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2020 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/conf/firenado.yml b/firenado/conf/firenado.yml
index ce6f1ef..dc82ff4 100644
--- a/firenado/conf/firenado.yml
+++ b/firenado/conf/firenado.yml
@@ -39,7 +39,7 @@ session:
     - name: json
       class: firenado.session.JsonSessionEncoder
     - name: pickle
-      class: firenado.session.PickeSessionEncoder
+      class: firenado.session.PickleSessionEncoder
   id_generators:
     - name: default
       function: firenado.session.generate_session_id
diff --git a/firenado/config.py b/firenado/config.py
index d50c984..64e5db0 100644
--- a/firenado/config.py
+++ b/firenado/config.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/data.py b/firenado/data.py
index ff102b1..e88c07c 100644
--- a/firenado/data.py
+++ b/firenado/data.py
@@ -1,6 +1,4 @@
-# -*- coding: UTF-8 -*-
-#
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flávio Gonçalves Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -39,7 +37,7 @@ def wrapper(self, *args, **kwargs):
 
 class DataConnectedMixin(object):
     """ Data connected objects has data sources. This mixin prepares an
-    object to to have data sources set to it and retrieved from it.
+    object to have data sources set to it and retrieved from it.
 
     Example:
     >>> class MyClass(..., DataConnectedMixin):
@@ -52,7 +50,7 @@ def data_sources(self):
         return get_data_sources(self, '__data_sources')
 
     def get_data_source(self, name):
-        """ Returns a data source by it's name.
+        """ Returns a data source by its name.
         """
         return self.data_sources[name]
 
@@ -136,10 +134,11 @@ def process_config(self, conf):
 
 
 class SqlalchemyConnector(Connector):
-    """ Connects a sqlalchemy engine to a data connected instance. Sqlalchemy
-    support a big variety of relational database backends. The connection
-    returned by this handler contains a engine and session created by
-    sqlalchemy and the database backend name.
+    from sqlalchemy.orm import Session
+    """ Connects a sqlalchemy engine to a data connected instance.
+    Sqlalchemy support a big variety of relational database backends. The
+    connection returned by this handler contains an engine and session created
+    by sqlalchemy and the database backend name.
     """
 
     def __init__(self, data_connected):
@@ -149,7 +148,6 @@ def __init__(self, data_connected):
             'backend': None,
             'session': {
                 'autoflush': True,
-                'autocommit': False,
                 'expire_on_commit': True,
                 'info': None
             }
@@ -198,9 +196,6 @@ def configure(self, name, conf):
             if "autoflush" in conf['session']:
                 self.__connection['session']['autoflush'] = conf['session'][
                     'autoflush']
-            if "autocommit" in conf['session']:
-                self.__connection['session']['autocommit'] = conf['session'][
-                    'autocommit']
             if "expire_on_commit" in conf['session']:
                 self.__connection['session']['expire_on_commit'] = conf[
                     'session']['expire_on_commit']
@@ -208,8 +203,8 @@ def configure(self, name, conf):
                 self.__connection['session']['info'] = conf['session']['info']
 
         if "url" not in conf:
-            logger.error("It is not possible to create sqlalchemy engine for "
-                         "%s datasource. Configuration: %s.", self.__name,
+            logger.error("It is not possible to create sqlalchemy engine"
+                         "for %s datasource. Configuration: %s.", self.__name,
                          conf)
         self.__engine = create_engine(conf['url'], **engine_params)
 
@@ -233,7 +228,7 @@ def ping_connection(connection, branch):
                 # the SELECT of a scalar value without a table is
                 # appropriately formatted for the backend
                 logger.debug("Testing sqlalchemy connection.")
-                connection.scalar(select([1]))
+                connection.scalar(select(1))
             except exc.DBAPIError as err:
                 logger.warning(err)
                 logger.warning("Firenado will try to reestablish the data "
@@ -273,26 +268,23 @@ def connect_engine(self):
             logger.fatal("Error trying to connect to database: %s", op_error)
             sys.exit(errno.ECONNREFUSED)
 
-    def get_a_session(self, **kwargs) -> "sqlalchemy.orm.Session":
+    def get_a_session(self, **kwargs) -> Session:
         """ Return a session bind to the datasource engine.
 
         Default parameters based on: https://bit.ly/3MjWDzF
         :param dict kwargs:
         :key bool autoflush: Default to True
-        :key bool autocommit: Default to False
         :key bool expire_on_commit: Default to False
         :key dict info: Default to None
-        :return "sqlalchemy.orm.session.Session":
+        :return Session:
         """
         autoflush = kwargs.get("autoflush", True)
-        autocommit = kwargs.get("autocommit", False)
         expire_on_commit = kwargs.get("expire_on_commit", True)
         info = kwargs.get("info")
 
-        from firenado.util.sqlalchemy_util import Session
-        Session.configure(bind=self.__engine, autoflush=autoflush,
-                          autocommit=autocommit,
-                          expire_on_commit=expire_on_commit, info=info)
+        from sqlalchemy.orm import sessionmaker
+        Session = sessionmaker(bind=self.__engine, autoflush=autoflush,
+                               expire_on_commit=expire_on_commit, info=info)
         return Session()
 
     @property
@@ -350,7 +342,7 @@ def process_config(self, conf):
 
 
 def config_to_data_source(name, conf, data_connected):
-    """ Convert a data source conf to it's respective data source. We need
+    """ Convert a data source conf to its respective data source. We need
     a data connected to use while instantiating the data source.
     :param name: Datasource name
     :param conf: A data source confuration item
diff --git a/firenado/launcher.py b/firenado/launcher.py
index 2658912..7bb89d4 100644
--- a/firenado/launcher.py
+++ b/firenado/launcher.py
@@ -79,6 +79,36 @@ def shutdown(self):
         raise NotImplementedError()
 
 
+def expect_list_mp(self, pattern_list, timeout=-1, searchwindowsize=-1,
+                   async_=False, **kw):
+    from pexpect import Expecter, searcher_re
+    '''This takes a list of compiled regular expressions and returns the
+    index into the pattern_list that matched the child output. The list may
+    also contain EOF or TIMEOUT(which are not compiled regular
+    expressions). This method is similar to the expect() method except that
+    expect_list() does not recompile the pattern list on every call. This
+    may help if you are trying to optimize for speed, otherwise just use
+    the expect() method.  This is called by expect().
+
+
+    Like :meth:`expect`, passing ``async_=True`` will make this return an
+    asyncio coroutine.
+    '''
+    if timeout == -1:
+        timeout = self.timeout
+    if 'async' in kw:
+        async_ = kw.pop('async')
+    if kw:
+        raise TypeError("Unknown keyword arguments: {}".format(kw))
+
+    exp = Expecter(self, searcher_re(pattern_list), searchwindowsize)
+    if async_:
+        from ._pexpect_async_patch import expect_async
+        return expect_async(exp, timeout)
+    else:
+        return exp.expect_loop(timeout)
+
+
 class ProcessLauncher(FirenadoLauncher):
     try:
         import pexpect
@@ -141,6 +171,8 @@ async def launch(self):
             if self.logfile is not None:
                 parameters['logfile'] = self.logfile
             self.process = pexpect.spawn(**parameters)
+            self.process.expect_list = expect_list_mp.__get__(self.process,
+                                                              pexpect.spawn)
             warnings.simplefilter("ignore")
             await self.process.expect(
                 [r"[Firenado server started successfully].*"], async_=True)
diff --git a/firenado/management/management.py b/firenado/management/management.py
index ac456ee..f94dc5e 100644
--- a/firenado/management/management.py
+++ b/firenado/management/management.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/management/tasks.py b/firenado/management/tasks.py
index e8ef191..1706a1d 100644
--- a/firenado/management/tasks.py
+++ b/firenado/management/tasks.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
 from cartola import fs
 import firenado.conf
 from firenado.management import ManagementTask
-from firenado.util import argparse_util
 import logging
 import os
 import sys
@@ -26,10 +25,10 @@
 
 
 class CreateProjectTask(ManagementTask):
-    """
-    Creates a new project from scratch
-    """
     def run(self, namespace):
+        """
+        Creates a new project from scratch
+        """
         from tornado import template
         if len(sys.argv) > 2:
             module = namespace.module
@@ -94,6 +93,7 @@ class InstallProjectTask(ManagementTask):
     """ Triggers the install method of all components registered in the
     application.
     """
+
     def run(self, namespace):
         # TODO: Resolve module if doesn't exists
         if firenado.conf.app['pythonpath']:
@@ -116,10 +116,10 @@ def add_arguments(self, parser):
         parser.add_argument("-P", "--port", type=int)
         parser.add_argument("-s", "--socket", default=None)
 
-    """ Runs a Firenado Tornado Application based
-    on the it's project configuration
-    """
     def run(self, namespace):
+        """ Runs a Firenado Tornado Application based
+        on the it's project configuration
+        """
         # TODO throw a custom error when type is not found
         from firenado.config import get_class_from_config
         parameters = {}
@@ -163,9 +163,9 @@ def run(self, namespace):
 
 
 class GenerateUuidTask(ManagementTask):
-    """ Generates an uuid4 string
-    """
     def run(self, namespace):
+        """ Generates an uuid4 string
+        """
         from uuid import uuid4
         logger.debug("Displaying a random uuid4 string.")
         print(uuid4())
diff --git a/firenado/schedule.py b/firenado/schedule.py
index dd3ab33..ab4a798 100644
--- a/firenado/schedule.py
+++ b/firenado/schedule.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/security.py b/firenado/security.py
index 5b6e6d0..67394ab 100644
--- a/firenado/security.py
+++ b/firenado/security.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/service.py b/firenado/service.py
index b1a61ee..f60f3b5 100644
--- a/firenado/service.py
+++ b/firenado/service.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from .sqlalchemy import with_session
 import functools
 import importlib
-import inspect
 import logging
 
+
 logger = logging.getLogger(__name__)
 
 
@@ -75,6 +76,15 @@ def data_connected(self):
 
 
 def served_by(service, attribute_name=None):
+    import warnings
+    warnings.warn(
+        "The \"firenado.service.served_by\" function is depreciated. "
+        "Please use firenado.service.with_service instead.",
+        DeprecationWarning, 2)
+    return with_service(service, attribute_name)
+
+
+def with_service(service, attribute_name=None):
     """ Decorator that connects a service to a service consumer.
     """
 
@@ -121,57 +131,9 @@ def wrapper(self, *args, **kwargs):
 
 
 def sessionned(*args, **kwargs):
-    """ This decorator will add an existing session to the method being
-    decorated or create a new session to be used by the method."""
-    service = None
-    if len(args) > 0:
-        service = args[0]
-
-    def method_wrapper(method):
-        @functools.wraps(method)
-        def wrapper(self, *method_args, **method_kwargs):
-            session = method_kwargs.get("session")
-            close = kwargs.get("close", False)
-            close = method_kwargs.get("close", close)
-            if not session:
-                # If a new session is created we must close it after running
-                # the method
-                close = True
-                data_source = kwargs.get("data_source")
-                data_source = method_kwargs.get("data_source", data_source)
-                if not data_source:
-                    hasattr(self, "default_data_source")
-                    if hasattr(self, "default_data_source"):
-                        if inspect.ismethod(self.default_data_source):
-                            data_source = self.default_data_source()
-                        else:
-                            data_source = self.default_data_source
-                try:
-                    if isinstance(data_source, str):
-                        ds = self.get_data_source(data_source)
-                        method_kwargs['data_source'] = data_source
-                        session = ds.session
-                    else:
-                        session = data_source.session
-                    method_kwargs['session'] = session
-                except KeyError:
-                    logger.exception("There is no datasource defined with "
-                                     "index \"%s\" related to the service.",
-                                     data_source)
-            result = method(self, *method_args, **method_kwargs)
-            if close:
-                if not session:
-                    logger.warning("No session was resolved.")
-                logger.debug("Closing session %s.", session)
-                session.close()
-            return result
-        return wrapper
-    # If the decorator has no parameter, I mean no parentesis, we need to wrap
-    # the service variable again , instead of the service instance, we need to
-    # deal with the method being decorated but as a <function> instace.
-    if inspect.isfunction(service):
-        @functools.wraps(None)
-        def func_wrapper(_function):
-            return method_wrapper(_function)
-        return func_wrapper(service)
-    return method_wrapper
+    import warnings
+    warnings.warn(
+        "The \"firenado.service.sessioned\" module is depreciated. "
+        "Please use firenado.sqlalchemy.with_session  instead.",
+        DeprecationWarning, 2)
+    return with_session(*args, **kwargs)
diff --git a/firenado/session.py b/firenado/session.py
index 211a07f..b62b595 100644
--- a/firenado/session.py
+++ b/firenado/session.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flávio Gonçalves Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,21 +14,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from cartola import fs, security
-import firenado.conf
-from firenado.config import get_class_from_config
 import functools
 import logging
 import os
+
 import tornado
 import tornado.ioloop
 import tornado.web
+from cartola.fs import read as fs_read
+from cartola.fs import touch as fs_touch
+from cartola.fs import write as fs_write
+from cartola.security import random_string
+
+import firenado.conf
+from firenado.config import get_class_from_config
 
 logger = logging.getLogger(__name__)
 
 
 class SessionEngine(object):
-    """SessionEngine provides session support to an application.
+    """Provides session support to an application.
     """
 
     def __init__(self, session_aware_instance):
@@ -102,14 +107,15 @@ async def store_session(self, request_handler):
         on the application configuration. """
         if firenado.conf.session['enabled']:
             session_id = request_handler.session.id
-            # If session was destroyed than we gonna handle it differently
+            # If session was destroyed than we're going to handle it
+            # differently
             # If session id is None than ignored (it is probably a redirect
             # leftover from security)
             if session_id is not None:
                 if not request_handler.session.is_destroyed():
                     encoded_session_data = self.encode_session_data(
                         request_handler.session.get_data())
-                    params = request_handler.session.get_params
+                    params = request_handler.session.get_params()
                     if request_handler.session.is_changed():
                         self.session_handler.write_stored_session(
                             session_id,
@@ -247,13 +253,16 @@ class SessionDestroyedError(tornado.web.HTTPError):
 
     def __init__(self, status_code=500, log_message=None, *args, **kwargs):
         message = ("The session is already destroyed. It is necessary to renew"
-                   " the session before using it again.")
+                   " the session before using it again."
+                   if log_message is None else log_message)
         super(SessionDestroyedError, self).__init__(
             status_code, message, *args, **kwargs)
 
 
 class SessionHandler:
 
+    life_time: int
+
     def __init__(self, engine):
         self.engine = engine
         self.settings = {}
@@ -323,7 +332,7 @@ def __generate_session_id():
         Retrieves the session id generator function from the configuration.
 
         It is possible for a developer create a new session id generator
-        function and and use it here.
+        function and use it here.
 
         Returns:
             A probably unique session id.
@@ -402,13 +411,13 @@ def create_session(self, session_id, data):
         if os.path.exists(firenado.conf.session['file']['path']):
             session_file = os.path.join(self.path,
                                         self.__get_filename(session_id))
-            fs.touch(session_file)
+            fs_touch(session_file)
             self.write_stored_session(session_id, data)
 
     def read_stored_session(self, session_id):
         import binascii
         session_file = os.path.join(self.path, self.__get_filename(session_id))
-        timed_data = fs.read(session_file)
+        timed_data = fs_read(session_file)
         return binascii.unhexlify(timed_data.split("--")[0])
 
     def write_stored_session(self, session_id, data, **kwargs):
@@ -417,7 +426,7 @@ def write_stored_session(self, session_id, data, **kwargs):
         timed_data = "%s--%s" % (binascii.hexlify(data).decode("ascii"),
                                  int(time.time()))
         session_file = os.path.join(self.path, self.__get_filename(session_id))
-        fs.write(session_file, timed_data)
+        fs_write(session_file, timed_data)
 
     def destroy_stored_session(self, session_id):
         logger.debug("Destroying session %s.", session_id)
@@ -449,7 +458,7 @@ def purge_expired_sessions(self):
             for filename in filenames:
                 file_path = os.path.join(self.path, filename)
                 sess_id = filename.split(".")[0].split("_")[-1]
-                timed_data = fs.read(file_path)
+                timed_data = fs_read(file_path)
                 last_write = timed_data.split("--")[1]
                 age = int(time.time()) - int(last_write)
                 if age > self.life_time:
@@ -480,8 +489,9 @@ def __get_filename(self, session_id):
 
 class RedisSessionHandler(SessionHandler):
     """
-    Session handler that deals with file data stored  in a redis database.
+    A session handler that deals with data stored in a redis database.
     """
+
     def __init__(self, engine):
         SessionHandler.__init__(self, engine)
         self.data_source = None
@@ -570,7 +580,7 @@ def decode(self, data):
         return data
 
 
-class PickeSessionEncoder(SessionEncoder):
+class PickleSessionEncoder(SessionEncoder):
 
     def encode(self, data):
         import pickle
@@ -599,4 +609,4 @@ def generate_session_id():
     Returns:
         A random string containing digits, upper and lower characters
     """
-    return security.random_string(64)
+    return random_string(64)
diff --git a/firenado/sqlalchemy.py b/firenado/sqlalchemy.py
new file mode 100644
index 0000000..ab63111
--- /dev/null
+++ b/firenado/sqlalchemy.py
@@ -0,0 +1,147 @@
+# Copyright 2015-2023 Flávio Gonçalves Garcia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import functools
+from inspect import isfunction, ismethod
+import logging
+from sqlalchemy import inspect, func, text
+
+logger = logging.getLogger(__name__)
+
+
+def base_to_dict(base, columns=None):
+    """ Returns a dict inherited from Base. If keys is provided it will only
+    add to the dict the columns provided.
+    :param base: An orm object inherited from Base
+    :param columns: Columns to be added to the dict
+    :return dict: A dictionary representing the Base object.
+    """
+    if columns is None:
+        return {c.key: getattr(base, c.key)
+                for c in inspect(base).mapper.column_attrs}
+    return {c.key: getattr(base, c.key)
+            for c in inspect(base).mapper.column_attrs if c.key in columns}
+
+
+def fast_count(query):
+    """
+    based on https://gist.github.com/hest/8798884
+    :return:
+    """
+    count_statement = query.statement.with_only_columns(
+        [func.count()]).order_by(None)
+    count = query.session.execute(count_statement).scalar()
+    return count
+
+
+def run_script(script_path, session, handle_command=None, handle_line=None):
+    """ Run a script file using a valid sqlalchemy session.
+
+    Based on https://bit.ly/2CToAhY.
+    See also sqlalchemy transaction control: https://bit.ly/2yKso0A
+
+    :param script_path: The path where the script is located
+    :param session: A sqlalchemy session to execute the sql commands from the
+    script
+    :param handle_command: Function to handle a valid command
+    :param handle_line: Function to handle a valid line
+    :return:
+    """
+    logger.debug("Opening script %s.", script_path)
+    with open(script_path, "r") as stream:
+        sql_command = ""
+        for line in stream:
+            # Ignore commented lines
+            if not line.startswith("--") and line.strip("\n"):
+                # Append line to the command string
+                if handle_line is not None:
+                    logger.debug("Calling the handle line function for: %s.",
+                                 line)
+                    line = handle_line(line)
+                sql_command = "%s%s" % (sql_command, line.strip("\n"))
+                # If the command string ends with ";", it is a full statement
+                if sql_command.endswith(";"):
+                    # Try to execute statement and commit it
+                    try:
+                        if handle_command is not None:
+                            logger.debug("Calling the handle command function "
+                                         "for: %s.", sql_command)
+                            sql_command = handle_command(sql_command)
+                        session.execute(text(sql_command))
+                    # Assert in case of error
+                    except Exception as e:
+                        session.rollback()
+                        raise e
+                    # Finally, clear command string
+                    finally:
+                        sql_command = ""
+    session.commit()
+
+
+def with_session(*args, **kwargs):
+    """ This decorator will add an existing sqlalchemy session to the method
+    being decorated or create a new sqlalchemy session to be used by the
+    method."""
+    service = None
+    if len(args) > 0:
+        service = args[0]
+
+    def method_wrapper(method):
+        @functools.wraps(method)
+        def wrapper(self, *method_args, **method_kwargs):
+            session = method_kwargs.get("session")
+            close = kwargs.get("close", False)
+            close = method_kwargs.get("close", close)
+            if not session:
+                # If a new session is created we must close it after running
+                # the method
+                close = True
+                data_source = kwargs.get("data_source")
+                data_source = method_kwargs.get("data_source", data_source)
+                if not data_source:
+                    hasattr(self, "default_data_source")
+                    if hasattr(self, "default_data_source"):
+                        if ismethod(self.default_data_source):
+                            data_source = self.default_data_source()
+                        else:
+                            data_source = self.default_data_source
+                try:
+                    if isinstance(data_source, str):
+                        ds = self.get_data_source(data_source)
+                        method_kwargs['data_source'] = data_source
+                        session = ds.session
+                    else:
+                        session = data_source.session
+                    method_kwargs['session'] = session
+                except KeyError:
+                    logger.exception("There is no datasource defined with "
+                                     "index \"%s\" related to the service.",
+                                     data_source)
+            result = method(self, *method_args, **method_kwargs)
+            if close:
+                if not session:
+                    logger.warning("No session was resolved.")
+                logger.debug("Closing session %s.", session)
+                session.close()
+            return result
+        return wrapper
+    # If the decorator has no parameter, I mean no parentesis, we need to wrap
+    # the service variable again , instead of the service instance, we need to
+    # deal with the method being decorated but as a <function> instace.
+    if isfunction(service):
+        @functools.wraps(None)
+        def func_wrapper(_function):
+            return method_wrapper(_function)
+        return func_wrapper(service)
+    return method_wrapper
diff --git a/firenado/tornadoweb.py b/firenado/tornadoweb.py
index f85525f..509a808 100644
--- a/firenado/tornadoweb.py
+++ b/firenado/tornadoweb.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ def get_request(url, **kwargs):
     return request
 
 
-class TornadoErrorHandler(object):
+class TornadoErrorHandler:
 
     def __init__(self, host):
         self._host = host
@@ -197,7 +197,7 @@ def __load_components(self):
                 self.components[key].initialize()
 
 
-class TornadoComponent(object):
+class TornadoComponent:
     """ Firenado applications are organized in components. A component could be
     an application or something that can be distributed as an add-on or a
     plugin.
@@ -343,7 +343,7 @@ def authenticated(self):
 
 
 class ComponentHandler(SessionHandler):
-    """ This mixing will define a handler with components and session.
+    """ This mixin will define a handler with components and session.
     ComponentHandler is the base of what a Firenado handler should be and it
     will be used to unify TornadoHandler and TornadoWebSocketHandler logic.
     Other functionalities will be implemented in TemplateHandler that assumes
@@ -433,6 +433,11 @@ def before_request(self):
         """
         pass
 
+    def get_rooted_path(self, path):
+        from .util.url_util import rooted_path
+        root = firenado.conf.app['url_root_path']
+        return rooted_path(root, path)
+
 
 class TemplateHandler:
     """ Deals with all aspects related to templates. This mixin will assume
@@ -509,29 +514,12 @@ def create_template_loader(self, template_path):
             template_path, component=self.component, **kwargs)
 
 
-class WebUtilHandler:
-    """ Holds everything else related to web utilities.
-    """
-
-    def is_mobile(self):
-        from mobiledetect import MobileDetect
-        if 'User-Agent' in self.request.headers:
-            return MobileDetect(
-                useragent=self.request.headers['User-Agent']
-            ).is_mobile()
-        return False
-
-    def get_rooted_path(self, path):
-        from .util.url_util import rooted_path
-        root = firenado.conf.app['url_root_path']
-        return rooted_path(root, path)
-
-
-class TornadoHandler(ComponentHandler, TemplateHandler, WebUtilHandler,
+class TornadoHandler(ComponentHandler, TemplateHandler,
                      tornado.web.RequestHandler):
     """ Base request handler to be used on a Firenado application.
     It provides session and handles component paths.
     """
+
     def __init__(self, application, request, **kwargs):
         ComponentHandler.__init__(self, **kwargs)
         TemplateHandler.__init__(self)
@@ -540,7 +528,6 @@ def __init__(self, application, request, **kwargs):
 
 
 class TornadoWebSocketHandler(ComponentHandler, TemplateHandler,
-                              WebUtilHandler,
                               tornado.websocket.WebSocketHandler):
 
     def __init__(self, application, request, **kwargs):
@@ -553,6 +540,7 @@ def __init__(self, application, request, **kwargs):
 class FirenadoComponentLoader(Loader):
     """ A template loader that loads from a single root directory.
     """
+
     def __init__(self, root_directory, component=None, **kwargs):
         # TODO: Check if we should alter/use the root_directory value
         # here or on the resolve_path method.
diff --git a/firenado/uimodules.py b/firenado/uimodules.py
index fedca4c..4632016 100644
--- a/firenado/uimodules.py
+++ b/firenado/uimodules.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/util/argparse_util.py b/firenado/util/argparse_util.py
index 50f452b..d30ea6e 100644
--- a/firenado/util/argparse_util.py
+++ b/firenado/util/argparse_util.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/firenado/util/sqlalchemy_util.py b/firenado/util/sqlalchemy_util.py
index 371b6a5..f01062b 100644
--- a/firenado/util/sqlalchemy_util.py
+++ b/firenado/util/sqlalchemy_util.py
@@ -1,6 +1,4 @@
-# -*- coding: UTF-8 -*-
-#
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,82 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import logging
-from sqlalchemy import inspect, func, text
-from sqlalchemy.orm import declarative_base, sessionmaker
-
-# Named as sqlalchemy_util to avoid conflict with the original sqlalchemy
-# module.
-
-Base = declarative_base()
-Session = sessionmaker()
-logger = logging.getLogger(__name__)
-
-
-def base_to_dict(base, columns=None):
-    """ Returns a dict inherited from Base. If keys is provided it will only
-    add to the dict the columns provided.
-    :param base: An orm object inherited from Base
-    :param columns: Columns to be added to the dict
-    :return dict: A dictionary representing the Base object.
-    """
-    if columns is None:
-        return {c.key: getattr(base, c.key)
-                for c in inspect(base).mapper.column_attrs}
-    return {c.key: getattr(base, c.key)
-            for c in inspect(base).mapper.column_attrs if c.key in columns}
-
-
-def fast_count(query):
-    """
-    based on https://gist.github.com/hest/8798884
-    :return:
-    """
-    count_statement = query.statement.with_only_columns(
-        [func.count()]).order_by(None)
-    count = query.session.execute(count_statement).scalar()
-    return count
-
-
-def run_script(script_path, session, handle_command=None, handle_line=None):
-    """ Run a script file using a valid sqlalchemy session.
+import warnings
 
-    Based on https://bit.ly/2CToAhY.
-    See also sqlalchemy transaction control: https://bit.ly/2yKso0A
+warnings.warn("The \"firenado.util.sqlalchemy_util\" module is depreciated. "
+              "Please use firenado.sqlalchemy  instead. This module will be "
+              "removed on Firenado 0.9.1.", DeprecationWarning,
+              2)
 
-    :param script_path: The path where the script is located
-    :param session: A sqlalchemy session to execute the sql commands from the
-    script
-    :param handle_command: Function to handle a valid command
-    :param handle_line: Function to handle a valid line
-    :return:
-    """
-    logger.debug("Opening script %s.", script_path)
-    with open(script_path, "r") as stream:
-        sql_command = ""
-        for line in stream:
-            # Ignore commented lines
-            if not line.startswith("--") and line.strip("\n"):
-                # Append line to the command string
-                if handle_line is not None:
-                    logger.debug("Calling the handle line function for: %s.",
-                                 line)
-                    line = handle_line(line)
-                sql_command = "%s%s" % (sql_command, line.strip("\n"))
-                # If the command string ends with ";", it is a full statement
-                if sql_command.endswith(";"):
-                    # Try to execute statement and commit it
-                    try:
-                        if handle_command is not None:
-                            logger.debug("Calling the handle command function "
-                                         "for: %s.", sql_command)
-                            sql_command = handle_command(sql_command)
-                        session.execute(text(sql_command))
-                    # Assert in case of error
-                    except Exception as e:
-                        session.rollback()
-                        raise e
-                    # Finally, clear command string
-                    finally:
-                        sql_command = ""
-    session.commit()
+from ..sqlalchemy import (base_to_dict, fast_count, run_script)
diff --git a/firenado/util/url_util.py b/firenado/util/url_util.py
index 8816ec6..9ccd5d2 100644
--- a/firenado/util/url_util.py
+++ b/firenado/util/url_util.py
@@ -1,6 +1,4 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2017 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/requirements/basic.txt b/requirements/basic.txt
index 981a244..a945bfc 100644
--- a/requirements/basic.txt
+++ b/requirements/basic.txt
@@ -1,3 +1,2 @@
 cartola>=0.16
-pymobiledetect==1.3.2
-tornado==6.2
+tornado==6.3.2
diff --git a/requirements/sqlalchemy.txt b/requirements/sqlalchemy.txt
index c7b02ed..dc54a15 100644
--- a/requirements/sqlalchemy.txt
+++ b/requirements/sqlalchemy.txt
@@ -1,1 +1,1 @@
-sqlalchemy>=1.4.0
+sqlalchemy>=2.0.15
diff --git a/setup.cfg b/setup.cfg
index eb5d3b0..608bba1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,3 @@
 [metadata]
-license_file = LICENSE
+license_files = LICENSE
 description_file = README.md
diff --git a/setup.py b/setup.py
index b4a7688..b87a499 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -59,7 +59,7 @@ def resolve_requires(requirements_file):
     maintainer=firenado.get_author(),
     maintainer_email=firenado.get_author_email(),
     install_requires=resolve_requires("requirements/basic.txt"),
-    python_requires=">= 3.7",
+    python_requires=">= 3.8",
     extras_require={
         'all': resolve_requires("requirements/all.txt"),
         'redis': resolve_requires("requirements/redis.txt"),
@@ -78,10 +78,10 @@ def resolve_requires(requirements_file):
         "License :: OSI Approved :: Apache Software License",
         "Operating System :: OS Independent",
         "Programming Language :: Python",
-        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: 3 :: Only",
         "Topic :: Internet :: WWW/HTTP",
         "Topic :: Internet :: WWW/HTTP :: Dynamic Content",


Test patch:
diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
index ef4cf2c..0cf66af 100644
--- a/.github/workflows/run_tests.yml
+++ b/.github/workflows/run_tests.yml
@@ -8,7 +8,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.7", "3.8", "3.9", "3.10"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
 
     steps:
       - uses: actions/checkout@v3
@@ -26,4 +26,4 @@ jobs:
           PYTHONPATH=$PYTHONPATH:. python tests/runtests.py
       - name: Run python behave tests
         run: |
-          ./scripts/run_behave.sh
\ No newline at end of file
+          ./scripts/run_behave.sh
diff --git a/examples/testapp/bower.json b/examples/testapp/bower.json
deleted file mode 100644
index b88bf25..0000000
--- a/examples/testapp/bower.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "name": "skell",
-  "version": "1.0.0",
-  "homepage": "https://github.com/candango/firenado",
-  "authors": [
-    "Flavio Garcia<piraz@candango.org>"
-  ],
-  "description": "Aplication that shows the Firenado basic features.",
-  "license": "Apache 2.0",
-  "ignore": [
-    "**/.*",
-    "node_modules",
-    "bower_components",
-    "test",
-    "tests"
-  ],
-  "dependencies": {
-    "jquery": "~2.1.4",
-    "canjs": "~2.2.9",
-    "bootstrap": "~3.3.5",
-    "font-awesome": "~4.2.0",
-    "waypoints": "~4.0.1"
-  }
-}
diff --git a/examples/testapp/conf/firenado.yml b/examples/testapp/conf/firenado.yml
index 0b6582d..b2c27dc 100644
--- a/examples/testapp/conf/firenado.yml
+++ b/examples/testapp/conf/firenado.yml
@@ -45,7 +45,7 @@ components:
   #- id: admin
   #  enabled: true
   - id: static_maps
-    enabled: true
+    enabled: false
   - id: info
     enabled: true
   - id: toolbox
diff --git a/examples/testapp/models.py b/examples/testapp/models.py
index f17069d..5ae21d5 100644
--- a/examples/testapp/models.py
+++ b/examples/testapp/models.py
@@ -1,23 +1,41 @@
-from firenado.util.sqlalchemy_util import Base
+# Copyright 2015-2023 Flávio Gonçalves Garcia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
-from sqlalchemy import Column, String
-from sqlalchemy.types import Integer, DateTime
+from datetime import datetime
+from sqlalchemy import String
+from sqlalchemy.types import DateTime
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 from sqlalchemy.sql import text
 
 
+class Base(DeclarativeBase):
+    pass
+
+
 class UserBase(Base):
+    __tablename__ = "users"
 
-    __tablename__ = 'users'
-    mysql_engine = 'MyISAM'
-    mysql_charset = 'utf8'
+    mysql_engine = "MyISAM"
+    mysql_charset = "utf8"
 
-    id = Column('id', Integer, primary_key=True)
-    username = Column('username', String(150), nullable=False)
-    first_name = Column('first_name', String(150), nullable=False)
-    last_name = Column('last_name', String(150), nullable=False)
-    password = Column('password', String(150), nullable=False)
-    email = Column('email', String(150), nullable=False)
-    created = Column('created', DateTime, nullable=False,
-                     server_default=text('now()'))
-    modified = Column('modified', DateTime, nullable=False,
-                     server_default=text('now()'))
+    id: Mapped[int] = mapped_column(primary_key=True)
+    username: Mapped[str] = mapped_column(String(150), nullable=False)
+    first_name: Mapped[str] = mapped_column(String(150), nullable=False)
+    last_name: Mapped[str] = mapped_column(String(150), nullable=False)
+    password: Mapped[str] = mapped_column(String(150), nullable=False)
+    email: Mapped[str] = mapped_column(String(150), nullable=False)
+    created: Mapped[datetime] = mapped_column(DateTime, nullable=False,
+                                              server_default=text("now()"))
+    modified: Mapped[datetime] = mapped_column(DateTime, nullable=False,
+                                               server_default=text("now()"))
diff --git a/examples/testapp/services.py b/examples/testapp/services.py
index 4446db0..b6e794f 100644
--- a/examples/testapp/services.py
+++ b/examples/testapp/services.py
@@ -1,6 +1,4 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2020 Flavio Garcia
+# Copyright 2015-2023 Flávio Gonçalves Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,8 +13,10 @@
 # limitations under the License.
 
 from .models import UserBase
-import datetime
-from firenado import service
+from firenado.service import FirenadoService, with_service
+from firenado.sqlalchemy import with_session
+from sqlalchemy import select
+from sqlalchemy.orm import Session
 
 
 def password_digest(pass_phrase):
@@ -26,12 +26,35 @@ def password_digest(pass_phrase):
     return m.hexdigest()
 
 
-class LoginService(service.FirenadoService):
+class UserService(FirenadoService):
+
+    @with_session(data_source="test")
+    def create(self, user_data, **kwargs):
+        session: Session = kwargs.get("session")
+        user = UserBase()
+        user.username = user_data['username']
+        user.first_name = user_data['first_name']
+        user.last_name = user_data['last_name']
+        user.password = password_digest(user_data['password'])
+        user.email = user_data['email']
+        session = self.get_data_source('test').session
+        session.add(user)
+        session.commit()
+        return user
+
+    @with_session(data_source="test")
+    def by_username(self, username, **kwargs):
+        session: Session = kwargs.get("session")
+        stmt = select(UserBase).where(UserBase.username == username)
+        user = session.scalars(stmt).one()
+        return user
+
 
-    def __init__(self, handler, data_source=None):
-        service.FirenadoService.__init__(self, handler, data_source)
+class LoginService(FirenadoService):
 
-    @service.served_by("testapp.services.UserService")
+    user_service: UserService
+
+    @with_service(UserService)
     def is_valid(self, username, password):
         """ Checks if challenge username and password matches
         username and password defined on the service constructor..
@@ -51,25 +74,3 @@ def is_valid(self, username, password):
         return False
 
 
-class UserService(service.FirenadoService):
-
-    def create(self, user_data):
-        created_utc = datetime.datetime.utcnow()
-        user = UserBase()
-        user.username = user_data['username']
-        user.first_name = user_data['first_name']
-        user.last_name = user_data['last_name']
-        user.password = password_digest(user_data['password'])
-        user.email = user_data['email']
-        db_session = self.get_data_source('test').session
-        db_session.add(user)
-        db_session.commit()
-        db_session.close()
-        return user
-
-    def by_username(self, username):
-        session = self.get_data_source('test').session
-        user = session.query(UserBase).filter(
-            UserBase.username == username).one_or_none()
-        session.close()
-        return user
diff --git a/examples/testapp/static/build.mjs b/examples/testapp/static/build.mjs
new file mode 100644
index 0000000..d2e6eef
--- /dev/null
+++ b/examples/testapp/static/build.mjs
@@ -0,0 +1,31 @@
+import * as esbuild from "esbuild"
+import { copy } from "esbuild-plugin-copy"
+import { entryPoints } from "./entryPoints.mjs"
+
+await esbuild.build({
+    entryPoints: entryPoints,
+    bundle: true,
+    minify: true,
+    write: true,
+    treeShaking: true,
+    sourcemap: true,
+    logLevel: "info",
+    outdir: "dist",
+    legalComments: "none",
+    allowOverwrite: true,
+    plugins: [
+        copy({
+            assets: [
+                {
+                    from: ["./node_modules/bootstrap/dist/js/*.min.js"],
+                    to: ["./bootstrap/js"],
+                },
+                {
+                    from: ["./node_modules/bootstrap/dist/css/*.min.css"],
+                    to: ["./bootstrap/css"],
+                },
+            ],
+        }),
+    ],
+})
+
diff --git a/examples/testapp/static/entryPoints.mjs b/examples/testapp/static/entryPoints.mjs
new file mode 100644
index 0000000..d52b532
--- /dev/null
+++ b/examples/testapp/static/entryPoints.mjs
@@ -0,0 +1,3 @@
+export const entryPoints = [
+    { out: "index.js", in: "src/index.js" },
+]
diff --git a/examples/testapp/static/package.json b/examples/testapp/static/package.json
new file mode 100644
index 0000000..afbec1a
--- /dev/null
+++ b/examples/testapp/static/package.json
@@ -0,0 +1,24 @@
+{
+    "name": "testappjs",
+    "version": "0.0.1",
+    "description": "Test App Js Project",
+    "main": "index.js",
+    "scripts": {
+        "esbuild": "rm -rf build && node build.mjs",
+        "test": "echo \"Error: no test specified\" && exit 1",
+        "yarn": "npm run yarn:clean && npm run yarn:v2 && npm run yarn:install",
+        "yarn:install": "yarn install",
+        "yarn:clean": "rm -rf yarn.lock .yarn .yarnrc.yml .next node_modules",
+        "yarn:v2": "yarn set version berry && yarn config set nodeLinker node-modules"
+    },
+    "author": "Flavio Garcia",
+    "license": "Apache-2.0",
+    "dependencies": {
+        "bootstrap": "5.2.3"
+    },
+    "devDependencies": {
+        "esbuild": "0.17.19",
+        "esbuild-plugin-copy": "2.1.1"
+    },
+    "packageManager": "yarn@3.5.1"
+}
diff --git a/examples/testapp/static/src/index.js b/examples/testapp/static/src/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/examples/testapp/templates/base.html b/examples/testapp/templates/base.html
index 0517078..bc0ced8 100644
--- a/examples/testapp/templates/base.html
+++ b/examples/testapp/templates/base.html
@@ -7,7 +7,7 @@
       <title>Firenado Test Application</title>
 
       <!-- Bootstrap -->
-      <link href="{% module RootedPath('/assets/bootstrap/css/bootstrap.min.css') %}" rel="stylesheet">
+      <link href="{% module RootedPath('/static/dist/bootstrap/css/bootstrap.min.css') %}" rel="stylesheet">
 
       <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
       <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@@ -24,7 +24,7 @@
         <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
         <script src="{% module RootedPath('/assets/jquery/jquery.min.js') %}"></script>
         <!-- Include all compiled plugins (below), or include individual files as needed -->
-        <script src="{% module RootedPath('/assets/bootstrap/js/bootstrap.min.js') %}"></script>
+        <script src="{% module RootedPath('/static/dist/bootstrap/js/bootstrap.min.js') %}"></script>
         <script src="{% module RootedPath('/assets/waypoints/jquery.waypoints.js') %}"></script>
         <script src="{% module RootedPath('/assets/waypoints/waypoints.debug.js') %}"></script>
         <script language="JavaScript">
diff --git a/firenado/testing.py b/firenado/testing.py
index 72e5829..ec0532a 100644
--- a/firenado/testing.py
+++ b/firenado/testing.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/components_test.py b/tests/components_test.py
index e6778ad..bf91d3e 100644
--- a/tests/components_test.py
+++ b/tests/components_test.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flávio Gonçalves Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
 
 
 class StaticMapsTestCase(unittest.TestCase):
-    """ Case that tests an Firenado application after being loaded from its
+    """ Case that tests a Firenado application after being loaded from its
     configuration file.
     """
 
diff --git a/tests/conf_test.py b/tests/conf_test.py
index 79dc74a..0e2a295 100644
--- a/tests/conf_test.py
+++ b/tests/conf_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/config_test.py b/tests/config_test.py
index d66df75..096ae0f 100644
--- a/tests/config_test.py
+++ b/tests/config_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/data_test.py b/tests/data_test.py
index e11f6a3..8cd3571 100644
--- a/tests/data_test.py
+++ b/tests/data_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/features/environment.py b/tests/features/environment.py
index f55c024..ba2971a 100644
--- a/tests/features/environment.py
+++ b/tests/features/environment.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2020 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/features/steps/launcher.py b/tests/features/steps/launcher.py
index 237de7d..e8fe80e 100644
--- a/tests/features/steps/launcher.py
+++ b/tests/features/steps/launcher.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2020 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/runtests.py b/tests/runtests.py
index 6a581eb..6e170ee 100644
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -16,8 +16,9 @@
 
 import unittest
 from tests import (components_test, conf_test, config_test, data_test,
-                   security_test, service_test, session_test, tornadoweb_test)
-from tests.util import sqlalchemy_util_test, url_util_test
+                   security_test, service_test, session_test, sqlalchemy_test,
+                   tornadoweb_test)
+from tests.util import url_util_test
 
 
 def suite():
@@ -30,8 +31,8 @@ def suite():
     alltests.addTests(testLoader.loadTestsFromModule(security_test))
     alltests.addTests(testLoader.loadTestsFromModule(service_test))
     alltests.addTests(testLoader.loadTestsFromModule(session_test))
+    alltests.addTests(testLoader.loadTestsFromModule(sqlalchemy_test))
     alltests.addTests(testLoader.loadTestsFromModule(tornadoweb_test))
-    alltests.addTests(testLoader.loadTestsFromModule(sqlalchemy_util_test))
     alltests.addTests(testLoader.loadTestsFromModule(url_util_test))
     return alltests
 
diff --git a/tests/security_test.py b/tests/security_test.py
index 498070e..a280144 100644
--- a/tests/security_test.py
+++ b/tests/security_test.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/service_test.py b/tests/service_test.py
index 254e437..9664085 100644
--- a/tests/service_test.py
+++ b/tests/service_test.py
@@ -1,6 +1,4 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,11 +13,11 @@
 # limitations under the License.
 
 from firenado.data import DataConnectedMixin
-from firenado.service import served_by, sessionned, FirenadoService
+from firenado.service import with_service, FirenadoService
 import unittest
 
 
-class MockServiceDataConnected(FirenadoService):
+class TestableServiceDataConnected(FirenadoService):
     """ Serves a data connected method directly.
     When decorating a data connected directly the service must return the
     consumer.
@@ -27,7 +25,7 @@ class MockServiceDataConnected(FirenadoService):
     pass
 
 
-class MockSession(object):
+class TestableSession(object):
 
     def __init__(self, data_source):
         self._data_source = data_source
@@ -49,7 +47,7 @@ def close(self):
         self._oppened = False
 
 
-class MockDataSource(object):
+class TestableDataSource(object):
 
     def __init__(self, name):
         self._name = name
@@ -60,7 +58,7 @@ def name(self):
 
     @property
     def session(self):
-        return MockSession(self)
+        return TestableSession(self)
 
     @property
     def session_resolved_string(self):
@@ -70,21 +68,23 @@ def resolve_session(self):
         return "%s: %s" % (self.session_resolved_string, self.name)
 
 
-class MockDataConnected(DataConnectedMixin):
+class TestableDataConnected(DataConnectedMixin):
     """ Data connected mock object. This object holds the data sources to be
     used in the test cases.
     """
 
+    testable_service_data_connected: TestableServiceDataConnected
+
     def __init__(self):
-        self.data_sources['datasource1'] = MockDataSource("DataSource1")
-        self.data_sources['datasource2'] = MockDataSource("DataSource2")
+        self.data_sources['datasource1'] = TestableDataSource("DataSource1")
+        self.data_sources['datasource2'] = TestableDataSource("DataSource2")
 
-    @served_by(MockServiceDataConnected)
+    @with_service(TestableServiceDataConnected)
     def get_service_data_sources_directly(self):
-        return self.mock_service_data_connected.get_data_sources()
+        return self.testable_service_data_connected.get_data_sources()
 
 
-class MockTestService(FirenadoService):
+class TestableService(FirenadoService):
     """ Service that decorates the instance to be served directly and
     indirectly thought MockTestServiceRecursion.
     When decorating directly data connected and data sources will be returned
@@ -95,221 +95,97 @@ class MockTestService(FirenadoService):
     pass
 
 
-class MockTestServiceRecursion(FirenadoService):
-    """ Service that decorates the instance to be served and will be used
-    to return the data connected and data sources when MockTestService
-    delegates to during the recursive test.
+class RecursiveService(FirenadoService):
+    """ This service will be used to return the data connected reference
+    and data source, during the recursive test, delegating to MockTestService.
     """
 
-    @served_by(MockTestService)
+    testable_service: TestableService
+
+    @with_service(TestableService)
     def get_service_data_sources_recursively(self):
-        return self.mock_test_service.get_data_sources()
+        return self.testable_service.get_data_sources()
 
-    @served_by(MockTestService)
+    @with_service(TestableService)
     def get_data_connected_recursively(self):
-        return self.mock_test_service.data_connected
-
-
-class MockSessionedService(FirenadoService):
-
-    @sessionned
-    def resolve_from_default_data_source(self, **kwargs):
-        return kwargs
-
-    @sessionned(data_source="datasource1")
-    def resolve_from_data_source(self, **kwargs):
-        return kwargs
-
-    @property
-    def default_data_source(self):
-        return "datasource2"
+        return self.testable_service.data_connected
 
 
 class ServedByInstance(object):
     """ Class with methods to be decorated with the served_by decorator.
     """
 
+    testable_service: TestableService
+    recursive_service: RecursiveService
+
     def __init__(self, data_connected):
         self.data_connected = data_connected
 
-    @served_by(MockTestService)
+    @with_service(TestableService)
     def do_served_by_class(self):
         """ Method to be decorated with served_by with a class reference
         """
         pass
 
-    @served_by('tests.service_test.MockTestService')
+    @with_service("tests.service_test.TestableService")
     def do_served_by_string(self):
         """ Method to be decorated with served_by with a string as class
         reference
         """
         pass
 
-    @served_by(MockTestService)
+    @with_service(TestableService)
     def get_service_data_connected(self):
-        return self.mock_test_service.data_connected
+        return self.testable_service.data_connected
 
-    @served_by(MockTestServiceRecursion)
+    @with_service(RecursiveService)
     def get_service_data_connected_recursively(self):
-        return (self.mock_test_service_recursion.
-                get_data_connected_recursively())
+        return (self.recursive_service.get_data_connected_recursively())
 
-    @served_by(MockTestService)
+    @with_service(TestableService)
     def get_service_data_sources(self):
         """ This method returns the data sources from the data connected
         instance of this class. The method is returned by the service defined
         on the served_by decorator. Here there is no recursion once
         MockTestService is directly serving the ServiceByInstance.
         """
-        return self.mock_test_service.get_data_sources()
+        return self.testable_service.get_data_sources()
 
-    @served_by(MockTestServiceRecursion)
+    @with_service(RecursiveService)
     def get_service_data_sources_recursively(self):
-        """ Same as get_service_data_sources but the a service will be serving
-        the service defined here and then access the data sources
+        """ Same as get_service_data_sources but service will be serving
+        another service defined here and then access the data sources
         """
-        return (self.mock_test_service_recursion.
-                get_service_data_sources_recursively())
+        return (self.recursive_service.get_service_data_sources_recursively())
 
     def get_data_connected(self):
         return self.data_connected
 
 
-class SessionedTestCase(unittest.TestCase):
-
-    mock_sessioned_service: MockSessionedService
-
-    def setUp(self):
-        """ Setting up an object that has firenado.core.service.served_by
-        decorators on some methods.
-        """
-        self.data_connected_instance = MockDataConnected()
-        self.served_by_instance = ServedByInstance(
-            self.data_connected_instance)
-
-    @property
-    def data_connected(self):
-        return self.served_by_instance.data_connected
-
-    @served_by(MockSessionedService)
-    def test_sessioned_default_data_source(self):
-        """ Method resolve_from_default_data_source is anoteded with sessioned
-        and no parameter. The data source to be used is the one defined either
-        in the property or method named default_data_source. In this case we're
-        using a property.
-
-        As no session was provided the session will be closed
-        """
-        resolved_kwargs = (
-            self.mock_sessioned_service.resolve_from_default_data_source()
-        )
-        self.assertEqual("datasource2", resolved_kwargs['data_source'])
-        data_source = self.data_connected.get_data_source(
-            resolved_kwargs['data_source']
-        )
-        self.assertEqual(data_source.resolve_session(),
-                         resolved_kwargs['session'].name)
-        self.assertFalse(resolved_kwargs['session'].is_oppened)
-
-    @served_by(MockSessionedService)
-    def test_sessioned_default_data_source_my_session(self):
-        """ Method resolve_from_default_data_source is anoteded with sessioned
-        and no parameter. Instead of getting the session from the default data
-        source, we're providing our own session.
-        """
-        data_source = self.data_connected.get_data_source("datasource1")
-        resolved_kwargs = (
-            self.mock_sessioned_service.resolve_from_default_data_source(
-                session=data_source.session
-            )
-        )
-        # No datasource will be added to the kwards
-        self.assertIsNone(resolved_kwargs.get("data_source"))
-        # Using session provided
-        self.assertEqual(data_source.resolve_session(),
-                         resolved_kwargs['session'].name)
-        self.assertTrue(resolved_kwargs['session'].is_oppened)
-
-    @served_by(MockSessionedService)
-    def test_sessioned_from_data_source(self):
-        """ Method resolve_from_data_source is anoteded with sessioned and
-        datasource1 as parameter. It will be injected to the method kwargs
-        session from datasource1.
-        """
-        resolved_kwargs = (
-            self.mock_sessioned_service.resolve_from_data_source()
-        )
-        self.assertEqual("datasource1", resolved_kwargs['data_source'])
-        data_source = self.data_connected.get_data_source(
-            resolved_kwargs['data_source']
-        )
-        self.assertEqual(data_source.resolve_session(),
-                         resolved_kwargs['session'].name)
-        self.assertFalse(resolved_kwargs['session'].is_oppened)
-
-    @served_by(MockSessionedService)
-    def test_sessioned_from_data_source_provide_session(self):
-        """ Method resolve_from_default_data_source is anoteded with sessioned
-        and no parameter. This time we provide the session and provided to
-        close the datasource after executing resolve_from_data_source.
-        """
-        data_source = self.data_connected.get_data_source("datasource2")
-        resolved_kwargs = (
-            self.mock_sessioned_service.resolve_from_data_source(
-                session=data_source.session, close=True
-            )
-        )
-        # No datasource will be added to the kwards
-        self.assertIsNone(resolved_kwargs.get("data_source"))
-        # Using session provided
-        self.assertEqual(data_source.resolve_session(),
-                         resolved_kwargs['session'].name)
-        self.assertFalse(resolved_kwargs['session'].is_oppened)
-
-    @served_by(MockSessionedService)
-    def test_sessioned_with_my_data_source_closing_connection(self):
-        """ Method resolve_from_data_source is anoteded with sessioned and
-        datasource1 as parameter. We're overwriting the data_source parameter
-        during the method call, changing to datasource2. It will be injected
-        to the method kwargs a session from data_source1.
-        """
-        resolved_kwargs = (
-            self.mock_sessioned_service.resolve_from_data_source(
-                data_source="datasource2")
-        )
-        self.assertEqual("datasource2", resolved_kwargs['data_source'])
-        data_source = self.data_connected.get_data_source(
-            resolved_kwargs['data_source']
-        )
-        self.assertEqual(data_source.resolve_session(),
-                         resolved_kwargs['session'].name)
-        self.assertFalse(resolved_kwargs['session'].is_oppened)
-
-
 class ServiceTestCase(unittest.TestCase):
 
     def setUp(self):
         """ Setting up an object that has firenado.core.service.served_by
         decorators on some methods.
         """
-        self.data_connected_instance = MockDataConnected()
+        self.data_connected_instance = TestableDataConnected()
         self.served_by_instance = ServedByInstance(
             self.data_connected_instance)
 
     def test_served_by_class_reference(self):
-        self.assertFalse(hasattr(self.served_by_instance, 'mock_test_service'))
+        self.assertFalse(hasattr(self.served_by_instance, 'testable_service'))
         self.served_by_instance.do_served_by_class()
-        self.assertTrue(hasattr(self.served_by_instance, 'mock_test_service'))
+        self.assertTrue(hasattr(self.served_by_instance, 'testable_service'))
         self.assertTrue(isinstance(
-            self.served_by_instance.mock_test_service, MockTestService))
+            self.served_by_instance.testable_service, TestableService))
 
     def test_served_by_class_name_string(self):
-        self.assertFalse(hasattr(self.served_by_instance, 'mock_test_service'))
+        self.assertFalse(hasattr(self.served_by_instance, 'testable_service'))
         self.served_by_instance.do_served_by_string()
-        self.assertTrue(hasattr(self.served_by_instance, 'mock_test_service'))
+        self.assertTrue(hasattr(self.served_by_instance, 'testable_service'))
         self.assertEqual(
-            self.served_by_instance.mock_test_service.__class__.__name__,
-            MockTestService.__name__)
+            self.served_by_instance.testable_service.__class__.__name__,
+            TestableService.__name__)
 
     def test_data_connected_from_service(self):
         data_connected = self.served_by_instance.get_data_connected()
@@ -321,8 +197,8 @@ def test_data_connected_from_service_recursively(self):
         self.assertEqual(data_connected, self.data_connected_instance)
 
     def test_data_connected_from_service_none(self):
-        mock_service = MockTestService(None)
-        data_connected = mock_service.data_connected
+        service = TestableService(None)
+        data_connected = service.data_connected
         self.assertIsNone(data_connected)
 
     def test_get_data_source_from_service(self):
@@ -346,6 +222,6 @@ def test_get_data_source_from_service_recursively(self):
         self.assertEqual(data_sources['datasource2'].name, "DataSource2")
 
     def test_get_data_source_from_service_none(self):
-        mock_service = MockTestService(None)
-        data_sources = mock_service.get_data_sources()
+        service = TestableService(None)
+        data_sources = service.get_data_sources()
         self.assertIsNone(data_sources)
diff --git a/tests/session_test.py b/tests/session_test.py
index 58c137e..7e40053 100644
--- a/tests/session_test.py
+++ b/tests/session_test.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2022 Flávio Gonçalves Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
 
 
 class FileSessionTestCase(unittest.TestCase):
-    """ Case that tests an Firenado application after being loaded from its
+    """ Case that tests a Firenado application after being loaded from its
     configuration file.
     """
 
@@ -75,9 +75,8 @@ def test_session_type_file(self):
 
 
 class RedisSessionTestCase(unittest.TestCase):
-    """ Case that tests an Firenado application after being loaded from its
-    configuration file.
-    """
+    """ Tests a Firenado application after being loaded from its configuration
+    file. """
 
     def setUp(self):
         """ Application configuration file will be read and components will be
@@ -106,14 +105,14 @@ def test_pickle_session_encoder(self):
         """ Checks if the pickle session encoder will keep a dict structure
         and values intact after encoding and decoding it.
         """
-        from firenado.session import PickeSessionEncoder
+        from firenado.session import PickleSessionEncoder
         my_dict = {
             'value1': "My value1",
             'value2': {
                 'value3': "My value3",
             }
         }
-        encoder = PickeSessionEncoder()
+        encoder = PickleSessionEncoder()
         encoded_data = encoder.encode(my_dict)
         decoded_data = encoder.decode(encoded_data)
         self.assertEqual(decoded_data['value1'], my_dict['value1'])
diff --git a/tests/sqlalchemy_test.py b/tests/sqlalchemy_test.py
new file mode 100644
index 0000000..84087bc
--- /dev/null
+++ b/tests/sqlalchemy_test.py
@@ -0,0 +1,207 @@
+# Copyright 2015-2023 Flavio Garcia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from datetime import datetime
+from tests.service_test import TestableDataConnected, ServedByInstance
+from firenado.sqlalchemy import base_to_dict, with_session
+from firenado.service import FirenadoService, with_service
+from sqlalchemy import String
+from sqlalchemy.types import DateTime
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
+from sqlalchemy.sql import text
+import unittest
+
+
+class Base(DeclarativeBase):
+    pass
+
+
+class TestBase(Base):
+
+    __tablename__ = "test"
+
+    id: Mapped[int] = mapped_column(primary_key=True)
+    username: Mapped[str] = mapped_column(String(150), nullable=False)
+    first_name: Mapped[str] = mapped_column(String(150), nullable=False)
+    last_name: Mapped[str] = mapped_column(String(150), nullable=False)
+    password: Mapped[str] = mapped_column(String(150), nullable=False)
+    email: Mapped[str] = mapped_column(String(150), nullable=False)
+    created: Mapped[datetime] = mapped_column(DateTime, nullable=False,
+                                              server_default=text("now()"))
+    modified: Mapped[datetime] = mapped_column(DateTime, nullable=False,
+                                               server_default=text("now()"))
+
+
+class MockSessionedService(FirenadoService):
+
+    @with_session
+    def resolve_from_default_data_source(self, **kwargs):
+        return kwargs
+
+    @with_session(data_source="datasource1")
+    def resolve_from_data_source(self, **kwargs):
+        return kwargs
+
+    @property
+    def default_data_source(self):
+        return "datasource2"
+
+
+class BaseToDictTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.test_object = TestBase()
+        self.test_object.id = 1
+        self.test_object.username = "anusername"
+        self.test_object.password = "apassword"
+        self.test_object.first_name = "Test"
+        self.test_object.last_name = "Object"
+        self.test_object.email = "test@example.com"
+
+    def test_base_to_dict(self):
+        dict_from_base = base_to_dict(self.test_object)
+        self.assertEqual(dict_from_base['id'], self.test_object.id)
+        self.assertEqual(dict_from_base['username'], self.test_object.username)
+        self.assertEqual(dict_from_base['password'], self.test_object.password)
+        self.assertEqual(dict_from_base['first_name'],
+                         self.test_object.first_name)
+        self.assertEqual(dict_from_base['last_name'],
+                         self.test_object.last_name)
+        self.assertEqual(dict_from_base['email'], self.test_object.email)
+        self.assertEqual(dict_from_base['created'], self.test_object.created)
+        self.assertEqual(dict_from_base['modified'], self.test_object.modified)
+
+    def test_base_to_dict_parametrized(self):
+        dict_from_base = base_to_dict(self.test_object,
+                                      ["id", "username", "first_name"])
+        self.assertEqual(dict_from_base['id'], self.test_object.id)
+        self.assertEqual(dict_from_base['username'], self.test_object.username)
+        self.assertEqual(dict_from_base['first_name'],
+                         self.test_object.first_name)
+        self.assertTrue("password" not in dict_from_base)
+        self.assertTrue("last_name" not in dict_from_base)
+        self.assertTrue("email" not in dict_from_base)
+        self.assertTrue("created" not in dict_from_base)
+        self.assertTrue("modified" not in dict_from_base)
+
+
+class SessionedTestCase(unittest.TestCase):
+
+    mock_sessioned_service: MockSessionedService
+
+    def setUp(self):
+        """ Setting up an object that has firenado.core.service.served_by
+        decorators on some methods.
+        """
+        self.data_connected_instance = TestableDataConnected()
+        self.served_by_instance = ServedByInstance(
+            self.data_connected_instance)
+
+    @property
+    def data_connected(self):
+        return self.served_by_instance.data_connected
+
+    @with_service(MockSessionedService)
+    def test_sessioned_default_data_source(self):
+        """ Method resolve_from_default_data_source is anoteded with sessioned
+        and no parameter. The data source to be used is the one defined either
+        in the property or method named default_data_source. In this case we're
+        using a property.
+
+        As no session was provided the session will be closed
+        """
+        resolved_kwargs = (
+            self.mock_sessioned_service.resolve_from_default_data_source()
+        )
+        self.assertEqual("datasource2", resolved_kwargs['data_source'])
+        data_source = self.data_connected.get_data_source(
+            resolved_kwargs['data_source']
+        )
+        self.assertEqual(data_source.resolve_session(),
+                         resolved_kwargs['session'].name)
+        self.assertFalse(resolved_kwargs['session'].is_oppened)
+
+    @with_service(MockSessionedService)
+    def test_sessioned_default_data_source_my_session(self):
+        """ Method resolve_from_default_data_source is anoteded with sessioned
+        and no parameter. Instead of getting the session from the default data
+        source, we're providing our own session.
+        """
+        data_source = self.data_connected.get_data_source("datasource1")
+        resolved_kwargs = (
+            self.mock_sessioned_service.resolve_from_default_data_source(
+                session=data_source.session
+            )
+        )
+        # No datasource will be added to the kwards
+        self.assertIsNone(resolved_kwargs.get("data_source"))
+        # Using session provided
+        self.assertEqual(data_source.resolve_session(),
+                         resolved_kwargs['session'].name)
+        self.assertTrue(resolved_kwargs['session'].is_oppened)
+
+    @with_service(MockSessionedService)
+    def test_sessioned_from_data_source(self):
+        """ Method resolve_from_data_source is anoteded with sessioned and
+        datasource1 as parameter. It will be injected to the method kwargs
+        session from datasource1.
+        """
+        resolved_kwargs = (
+            self.mock_sessioned_service.resolve_from_data_source()
+        )
+        self.assertEqual("datasource1", resolved_kwargs['data_source'])
+        data_source = self.data_connected.get_data_source(
+            resolved_kwargs['data_source']
+        )
+        self.assertEqual(data_source.resolve_session(),
+                         resolved_kwargs['session'].name)
+        self.assertFalse(resolved_kwargs['session'].is_oppened)
+
+    @with_service(MockSessionedService)
+    def test_sessioned_from_data_source_provide_session(self):
+        """ Method resolve_from_default_data_source is anoteded with sessioned
+        and no parameter. This time we provide the session and provided to
+        close the datasource after executing resolve_from_data_source.
+        """
+        data_source = self.data_connected.get_data_source("datasource2")
+        resolved_kwargs = (
+            self.mock_sessioned_service.resolve_from_data_source(
+                session=data_source.session, close=True
+            )
+        )
+        # No datasource will be added to the kwards
+        self.assertIsNone(resolved_kwargs.get("data_source"))
+        # Using session provided
+        self.assertEqual(data_source.resolve_session(),
+                         resolved_kwargs['session'].name)
+        self.assertFalse(resolved_kwargs['session'].is_oppened)
+
+    @with_service(MockSessionedService)
+    def test_sessioned_with_my_data_source_closing_connection(self):
+        """ Method resolve_from_data_source is anoteded with sessioned and
+        datasource1 as parameter. We're overwriting the data_source parameter
+        during the method call, changing to datasource2. It will be injected
+        to the method kwargs a session from data_source1.
+        """
+        resolved_kwargs = (
+            self.mock_sessioned_service.resolve_from_data_source(
+                data_source="datasource2")
+        )
+        self.assertEqual("datasource2", resolved_kwargs['data_source'])
+        data_source = self.data_connected.get_data_source(
+            resolved_kwargs['data_source']
+        )
+        self.assertEqual(data_source.resolve_session(),
+                         resolved_kwargs['session'].name)
+        self.assertFalse(resolved_kwargs['session'].is_oppened)
diff --git a/tests/tornadoweb_test.py b/tests/tornadoweb_test.py
index 94b3970..aa84d66 100644
--- a/tests/tornadoweb_test.py
+++ b/tests/tornadoweb_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2021 Flavio Garcia
+# Copyright 2015-2023 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/tests/util/sqlalchemy_util_test.py b/tests/util/sqlalchemy_util_test.py
deleted file mode 100644
index c54769c..0000000
--- a/tests/util/sqlalchemy_util_test.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2021 Flavio Garcia
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from firenado.util.sqlalchemy_util import Base, base_to_dict
-from sqlalchemy import Column, String
-from sqlalchemy.types import Integer, DateTime
-from sqlalchemy.sql import text
-import unittest
-
-
-class TestBase(Base):
-
-    __tablename__ = "test"
-
-    id = Column("id", Integer, primary_key=True)
-    username = Column("username", String(150), nullable=False)
-    first_name = Column("first_name", String(150), nullable=False)
-    last_name = Column("last_name", String(150), nullable=False)
-    password = Column("password", String(150), nullable=False)
-    email = Column("email", String(150), nullable=False)
-    created = Column("created", DateTime, nullable=False,
-                     server_default=text("now()"))
-    modified = Column("modified", DateTime, nullable=False,
-                      server_default=text("now()"))
-
-
-class BaseToDictTestCase(unittest.TestCase):
-
-    def setUp(self):
-        self.test_object = TestBase()
-        self.test_object.id = 1
-        self.test_object.username = "anusername"
-        self.test_object.password = "apassword"
-        self.test_object.first_name = "Test"
-        self.test_object.last_name = "Object"
-        self.test_object.email = "test@example.com"
-
-    def test_base_to_dict(self):
-        dict_from_base = base_to_dict(self.test_object)
-        self.assertEqual(dict_from_base['id'], self.test_object.id)
-        self.assertEqual(dict_from_base['username'], self.test_object.username)
-        self.assertEqual(dict_from_base['password'], self.test_object.password)
-        self.assertEqual(dict_from_base['first_name'],
-                         self.test_object.first_name)
-        self.assertEqual(dict_from_base['last_name'],
-                         self.test_object.last_name)
-        self.assertEqual(dict_from_base['email'], self.test_object.email)
-        self.assertEqual(dict_from_base['created'], self.test_object.created)
-        self.assertEqual(dict_from_base['modified'], self.test_object.modified)
-
-    def test_base_to_dict_parametrized(self):
-        dict_from_base = base_to_dict(self.test_object,
-                                      ["id", "username", "first_name"])
-        self.assertEqual(dict_from_base['id'], self.test_object.id)
-        self.assertEqual(dict_from_base['username'], self.test_object.username)
-        self.assertEqual(dict_from_base['first_name'],
-                         self.test_object.first_name)
-        self.assertTrue("password" not in dict_from_base)
-        self.assertTrue("last_name" not in dict_from_base)
-        self.assertTrue("email" not in dict_from_base)
-        self.assertTrue("created" not in dict_from_base)
-        self.assertTrue("modified" not in dict_from_base)