Repository: candango/firenado (language: python)

Issue:
Delivered 0.2.6

Send line should return the process output

Use cartola instead of util.sysexits

Add has conf property to TornadoComponent



Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index bc924d2..43e5126 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,12 +2,15 @@
 language: python
 python:
   - "2.7"
-  - "3.4"
   - "3.5"
   - "3.6"
   - "3.7"
+  - "3.8"
 install:
   - "pip install -r requirements/basic.txt"
   - "pip install -r requirements/all.txt"
+  - "pip install -r requirements/tests.txt"
+  - ./scripts/install_tornado.sh
 script:
   - "PYTHONPATH=$PYTHONPATH:. python tests/runtests.py"
+  - ./scripts/run_behave.sh
diff --git a/MANIFEST.in b/MANIFEST.in
index fc9f624..07bbc79 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,13 @@
 include LICENSE
+include README.md
 include requirements/all.txt
 include requirements/basic.txt
 include requirements/pexpect.txt
 include requirements/redis.txt
+include requirements/schedule.txt
 include requirements/sqlalchemy.txt
+include requirements/tornado_legacy.txt
+include requirements/tornado_new.txt
 include firenado/conf/*.yml
 include firenado/management/templates/*/*.txt
 include firenado/components/*/conf/*.yaml.example
diff --git a/docs/components.rst b/docs/components.rst
deleted file mode 100644
index 6b3da46..0000000
--- a/docs/components.rst
+++ /dev/null
@@ -1,92 +0,0 @@
-Components
-==========
-
-Firenado is organized in components that could be distributed as a full
-application or loose-coupled parts made to be shared between applications.
-
-A component could provide new handlers, templates, services, models as other
-resources to an application in an organized manner.
-
-An application is a component and it is defined into the app.component
-parameter in the app application configuration file:
-
-.. code-block:: yaml
-
-   app:
-     component: skell
-
-Components are mapped into the framework, system or application configuration
-files. They are defined by the component section:
-
-.. code-block:: yaml
-
-   components:
-     - id: skell
-       class: skell.app.SkellComponent
-       enabled: true
-     - id: internal
-       class: skell.components.internal.component.SkellInternalComponent
-       enabled: true
-
-A component configuration item is contains the follow parameters:
-
-* id
-
-The component id to be registered in the application during the load time.
-
-The id is used to get the component from the application during run time and
-to reference a template from another component.
-
-* class
-
-The component class to be loaded during the load time.
-
-* enabled
-
-Defines if the component is enabled or disabled. If isn't informed the
-component will be disabled.
-
-Disabled components are not available at run time.
-
-
-Before and after handler methods
---------------------------------
-
-For all handler returned by the component it is possible to implement the
-methods before_handler and after_handler that will be executed before and after
-the some http method be triggered by the Tornado server.
-
-If a component TestComponent has a handler TestHandler that provides a get
-method, mapped at /test url, when a request to this method is executed by the
-server, the follow methods will be executed in this order:
-
-1. TestComponent.before_handler
-2. TestHandler.get
-3. TestComponent.after_handler
-
-Both before_handler and after_handler have the current handler as a parameter
-and the handler session will be initialized correctly during their execution.
-
-The execution of those methods will happen all the time a handler method
-returned by the component is requested. In another words before_handler and
-after_handler are global to the handlers returned by the component.
-
-.. code-block:: python
-
-   class TestComponent(firenado.tornadoweb.TornadoComponent):
-
-       def get_handlers(self):
-           return [
-               (r"/test", handlers.TestHandler),
-           ]
-
-       def after_handler(self, handler):
-           print("Doing something after handler: %s" % handler)
-
-       def before_handler(self, handler):
-           print("Doing something before handler: %s" % handler)
-
-.. toctree::
-   :maxdepth: 2
-
-   components/static_maps
diff --git a/docs/conf.py b/docs/conf.py
index f8fc8d5..0d587b0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -47,7 +47,7 @@
 
 # General information about the project.
 project = u'Firenado Framework'
-copyright = u'2015-2019, Flavio Garcia'
+copyright = u'2015-2020, Flavio Garcia'
 author = u'Flavio Garcia'
 
 # The version info for the project you're documenting, acts as replacement for
@@ -55,9 +55,9 @@
 # built documents.
 #
 # The short X.Y version.
-version = '0.1.7'
+version = '0.2'
 # The full version, including alpha/beta/rc tags.
-release = '0.1.7.7'
+release = '0.2.6'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/guide.rst b/docs/guide.rst
index 9c684bc..1c0a485 100644
--- a/docs/guide.rst
+++ b/docs/guide.rst
@@ -8,5 +8,6 @@ User's guide
    guide/command_line
    guide/components
    guide/datasources
+   guide/schedules
    guide/services
    guide/session
diff --git a/docs/guide/components.rst b/docs/guide/components.rst
index e69de29..6b3da46 100644
--- a/docs/guide/components.rst
+++ b/docs/guide/components.rst
@@ -0,0 +1,92 @@
+Components
+==========
+
+Firenado is organized in components that could be distributed as a full
+application or loose-coupled parts made to be shared between applications.
+
+A component could provide new handlers, templates, services, models as other
+resources to an application in an organized manner.
+
+An application is a component and it is defined into the app.component
+parameter in the app application configuration file:
+
+.. code-block:: yaml
+
+   app:
+     component: skell
+
+Components are mapped into the framework, system or application configuration
+files. They are defined by the component section:
+
+.. code-block:: yaml
+
+   components:
+     - id: skell
+       class: skell.app.SkellComponent
+       enabled: true
+     - id: internal
+       class: skell.components.internal.component.SkellInternalComponent
+       enabled: true
+
+A component configuration item is contains the follow parameters:
+
+* id
+
+The component id to be registered in the application during the load time.
+
+The id is used to get the component from the application during run time and
+to reference a template from another component.
+
+* class
+
+The component class to be loaded during the load time.
+
+* enabled
+
+Defines if the component is enabled or disabled. If isn't informed the
+component will be disabled.
+
+Disabled components are not available at run time.
+
+
+Before and after handler methods
+--------------------------------
+
+For all handler returned by the component it is possible to implement the
+methods before_handler and after_handler that will be executed before and after
+the some http method be triggered by the Tornado server.
+
+If a component TestComponent has a handler TestHandler that provides a get
+method, mapped at /test url, when a request to this method is executed by the
+server, the follow methods will be executed in this order:
+
+1. TestComponent.before_handler
+2. TestHandler.get
+3. TestComponent.after_handler
+
+Both before_handler and after_handler have the current handler as a parameter
+and the handler session will be initialized correctly during their execution.
+
+The execution of those methods will happen all the time a handler method
+returned by the component is requested. In another words before_handler and
+after_handler are global to the handlers returned by the component.
+
+.. code-block:: python
+
+   class TestComponent(firenado.tornadoweb.TornadoComponent):
+
+       def get_handlers(self):
+           return [
+               (r"/test", handlers.TestHandler),
+           ]
+
+       def after_handler(self, handler):
+           print("Doing something after handler: %s" % handler)
+
+       def before_handler(self, handler):
+           print("Doing something before handler: %s" % handler)
+
+.. toctree::
+   :maxdepth: 2
+
+   components/static_maps
diff --git a/docs/guide/schedules.rst b/docs/guide/schedules.rst
new file mode 100644
index 0000000..8a07cce
--- /dev/null
+++ b/docs/guide/schedules.rst
@@ -0,0 +1,89 @@
+Schedule
+========
+
+If you need to crate an application with scheduled actions Firenado provides a
+structure to do it.
+
+Using a scheduled component is possible to define a scheduler via configuration
+and point to tasks that could be run once or in a interval.
+
+
+Setting up a scheduled component:
+---------------------------------
+
+Instead of creating extending TornadoComponent extend it from
+ScheduledTornadoComponent.
+
+.. code-block:: python
+
+   from . import handlers
+   from firenado import schedule
+
+
+   class SchedappComponent(schedule.ScheduledTornadoComponent):
+
+We added the component to conf/firenado.yml and enabled it.
+
+.. code-block:: yaml
+
+   components:
+     - id: schedapp
+       class: schedapp.app.SchedappComponent
+       enabled: true
+
+This component could be used as the application component if you need.
+
+.. code-block:: yaml
+
+   app:
+     component: 'schedapp'
+
+By default Firenado will look for <component_id>_schedule.[yml|yaml] config
+file. Here is an example with a config based scheduler running a cron based
+scheduled task(the file is conf/schedapp_schedule.yml):
+
+.. code-block:: yaml
+
+   schedulers:
+     - id: scheduler1
+       name: Scheduler Example 1
+       jobs:
+         - id: job1
+           class: schedapp.jobs.PrintTestJob
+           cron: "*/1 * * * *"
+           custom_property: Custom property from job1.
+         - id: job2
+           class: schedapp.jobs.PrintTestJob
+           interval: 15000
+           custom_property: Custom property from job1.
+
+The configuration file is define the scheduler scheduler1 that manages a job
+identified as job1 that runs every minute and a custom property to be used
+by the the class schedapp.jobs.PrintTestJob. Here is the the job
+implementation:
+
+A scheduled job can be executed by interval and cron string.
+
+The interval is defined in milliseconds and will take priority over the cron
+string.
+
+The cron string will defined intervals using the cron like format and is
+resolved by `croniter <https://github.com/taichino/croniter>`_.
+
+.. code-block:: python
+
+   from firenado.schedule import ScheduledJob
+
+   
+   class PrintTestJob(ScheduledJob):
+
+       def __init__(self, scheduler, **kwargs):
+           super(PrintTestJob, self).__init__(scheduler, **kwargs)
+           self._custom_property = kwargs.get('custom_property', None)
+
+       def run(self):
+            print("This is the custom property value: %s" % self._custom_property)
+
+As demonstrated above we need to create a class that extends from ScheduledJob
+and implements the run method. We used the constructor to consume the custom
+property defined in the schedule config file and used it to print a message.
diff --git a/docs/index.rst b/docs/index.rst
index 2314b81..7a24775 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -21,7 +21,6 @@ Contents:
 
    guide
    configuration
-   components
    releases
 
 Indices and tables
diff --git a/docs/releases.rst b/docs/releases.rst
index 6cb00c4..7c90b00 100644
--- a/docs/releases.rst
+++ b/docs/releases.rst
@@ -4,6 +4,16 @@ Release notes
 .. toctree::
    :maxdepth: 2
 
+   releases/v0.2.6
+   releases/v0.2.5
+   releases/v0.2.4
+   releases/v0.2.3
+   releases/v0.2.2
+   releases/v0.2.1
+   releases/v0.2
+   releases/v0.1.9
+   releases/v0.1.8.3
+   releases/v0.1.8.1
    releases/v0.1.7.8
    releases/v0.1.7.7
    releases/v0.1.7.6
diff --git a/docs/releases/v0.1.8.1.rst b/docs/releases/v0.1.8.1.rst
new file mode 100644
index 0000000..5e24461
--- /dev/null
+++ b/docs/releases/v0.1.8.1.rst
@@ -0,0 +1,41 @@
+What's new in Firenado 0.1.8.1
+==============================
+
+Mar 04, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.1.8.1.
+
+We still support python 2.7 in the main development contrary to the announce
+from 0.1.7.8 but we started to relax tests against 2.7 and prepare the code
+base for a python 3.x only.
+
+Firenado gave birth to Cartola and we added this project to the basic
+dependency.
+
+This version fixes definitely the process launcher shutdown making easier to
+spam and teardown Firenado application in a test and that's the reason we still
+keep with python 2.7.
+
+A new json session encoder were added to the available list of session encoders
+making it easier to share session data between applications that don't have
+pikle encoding/serialization.
+
+Here are the highlights:
+
+New Features
+~~~~~~~~~~~~
+
+* Add json session encoder to the project. `#295 <https://github.com/candango/firenado/issues/295>`_
+* Clean sys.path and set PYTHONPATH before launch a process. . `#299 <https://github.com/candango/firenado/issues/299>`_
+
+Bug Fixes
+~~~~~~~~~
+* Process launcher shutdown doesn't work. `#290 <https://github.com/candango/firenado/issues/290>`_
+
+Refactory
+~~~~~~~~~
+* Use pymobiledetect instead of util.browser. `#292 <https://github.com/candango/firenado/issues/292>`_
+* Use cartola instead of util.sysexits. `#293 <https://github.com/candango/firenado/issues/293>`_
+* Use cartola instead of util.file. `#294 <https://github.com/candango/firenado/issues/294>`_
+* Use cartola instead of local security package. `#296 <https://github.com/candango/firenado/issues/296>`_
diff --git a/docs/releases/v0.1.8.3.rst b/docs/releases/v0.1.8.3.rst
new file mode 100644
index 0000000..6962af0
--- /dev/null
+++ b/docs/releases/v0.1.8.3.rst
@@ -0,0 +1,17 @@
+What's new in Firenado 0.1.8.3
+==============================
+
+Apr 10, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.1.8.3.
+
+Right now python 3.x will install tornado 6 and python 2.7 will keep with
+tornado 5.1.1.
+
+Here are the highlights:
+
+New Features
+~~~~~~~~~~~~
+
+* Install new tornado if python is 3.x. `#303 <https://github.com/candango/firenado/issues/303>`_
diff --git a/docs/releases/v0.1.9.rst b/docs/releases/v0.1.9.rst
new file mode 100644
index 0000000..d23a0bd
--- /dev/null
+++ b/docs/releases/v0.1.9.rst
@@ -0,0 +1,26 @@
+What's new in Firenado 0.1.9
+==============================
+
+Jul 23, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.1.9.
+
+That release fixes session, launcher issues and fixes installation under pip
+20.1 up.
+
+The paginator component was moved to cartola 0.7.
+
+Here are the highlights:
+
+Bug Fixes
+~~~~~~~~~
+
+* If PYTHONPATH isn't defined launcher will clean sys.path. `#306 <https://github.com/candango/firenado/issues/306>`_
+* Pip 20.1 will break installation. `#310 <https://github.com/candango/firenado/issues/310>`_
+* Redis session prefix isn't working. `#311 <https://github.com/candango/firenado/issues/311>`_
+
+Refactory
+~~~~~~~~~
+
+* Remove Paginator from the project. `#309 <https://github.com/candango/firenado/issues/309>`_
diff --git a/docs/releases/v0.2.1.rst b/docs/releases/v0.2.1.rst
new file mode 100644
index 0000000..81137cb
--- /dev/null
+++ b/docs/releases/v0.2.1.rst
@@ -0,0 +1,19 @@
+What's new in Firenado 0.2.1
+==========================
+
+Sep 11, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.2.1.
+
+This release will add couple minor features and updating cartola.
+
+We'll increment the release number instead of minor.
+
+Here are the highlights:
+
+New Features
+~~~~~~~~~~~~
+
+* Add function to create HTTPRequests`#131 <https://github.com/candango/firenado/issues/131>`_
+* Create a function to transform sqlalchemy objects to dict `#321 <https://github.com/candango/firenado/issues/321>`_
diff --git a/docs/releases/v0.2.2.rst b/docs/releases/v0.2.2.rst
new file mode 100644
index 0000000..f00fa1f
--- /dev/null
+++ b/docs/releases/v0.2.2.rst
@@ -0,0 +1,15 @@
+What's new in Firenado 0.2.2
+============================
+
+Oct 09, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.2.2.
+
+Here are the highlights:
+
+New Features
+~~~~~~~~~~~~
+
+* Create and store data sources at run time`#230 <https://github.com/candango/firenado/issues/230>`_
+* Make a scheduled job to be executed by interval `#325 <https://github.com/candango/firenado/issues/325>`_
diff --git a/docs/releases/v0.2.3.rst b/docs/releases/v0.2.3.rst
new file mode 100644
index 0000000..b300914
--- /dev/null
+++ b/docs/releases/v0.2.3.rst
@@ -0,0 +1,21 @@
+What's new in Firenado 0.2.3
+============================
+
+Nov 28, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.2.3.
+
+Here are the highlights:
+
+Bug Fixes
+~~~~~~~~~
+
+* Fix python 3.9 warnings. `#328 <https://github.com/candango/firenado/issues/328>`_
+
+New Features
+~~~~~~~~~~~~
+
+* Reference data sources in different files. `#171 <https://github.com/candango/firenado/issues/171>`_
+
+
diff --git a/docs/releases/v0.2.4.rst b/docs/releases/v0.2.4.rst
new file mode 100644
index 0000000..7bb238f
--- /dev/null
+++ b/docs/releases/v0.2.4.rst
@@ -0,0 +1,14 @@
+What's new in Firenado 0.2.4
+============================
+
+Feb 28, 2021
+------------
+
+We are pleased to announce the release of Firenado 0.2.4.
+
+Here are the highlights:
+
+New Features
+~~~~~~~~~~~~
+
+* Add http error handling to the framework. `#242 <https://github.com/candango/firenado/issues/242>`_
diff --git a/docs/releases/v0.2.5.rst b/docs/releases/v0.2.5.rst
new file mode 100644
index 0000000..2c57ea0
--- /dev/null
+++ b/docs/releases/v0.2.5.rst
@@ -0,0 +1,14 @@
+What's new in Firenado 0.2.5
+============================
+
+Mar 02, 2021
+------------
+
+We are pleased to announce the release of Firenado 0.2.5.
+
+Here are the highlights:
+
+Bug Fixes
+~~~~~~~~~
+
+* TornadoHandler write error will never hit the default RequestHandler write error. `#335 <https://github.com/candango/firenado/issues/335>`_
diff --git a/docs/releases/v0.2.6.rst b/docs/releases/v0.2.6.rst
new file mode 100644
index 0000000..50d9739
--- /dev/null
+++ b/docs/releases/v0.2.6.rst
@@ -0,0 +1,14 @@
+What's new in Firenado 0.2.6
+============================
+
+Mar 14, 2021
+------------
+
+We are pleased to announce the release of Firenado 0.2.6.
+
+Here are the highlights:
+
+Bug Fixes
+~~~~~~~~~
+
+* Pyyaml conflict with cartola when installing 0.2.5. `#338 <https://github.com/candango/firenado/issues/338>`_
diff --git a/docs/releases/v0.2.rst b/docs/releases/v0.2.rst
new file mode 100644
index 0000000..258a060
--- /dev/null
+++ b/docs/releases/v0.2.rst
@@ -0,0 +1,25 @@
+What's new in Firenado 0.2
+==========================
+
+Aug 30, 2020
+------------
+
+We are pleased to announce the release of Firenado 0.2.
+
+This release adds the scheduled component able to run jobs defined in a
+configuration file. Jobs are scheduled using cron strings.
+
+Now before_handler and after_handler methods from application where renamed to
+before_request and after_request and they are also present in the handler.
+
+By now releases will increment the minor. Next release will be 0.3.
+
+Here are the highlights:
+
+New Features
+~~~~~~~~~~~~
+
+* Create scheduled component and module `#45 <https://github.com/candango/firenado/issues/45>`_
+* Implement before and after request methods `#315 <https://github.com/candango/firenado/issues/315>`_
+* Add has conf property to TornadoComponent `#316 <https://github.com/candango/firenado/issues/316>`_
+* Create a method to resolve the config file to TornadoComponent `#317 <https://github.com/candango/firenado/issues/317>`_
diff --git a/examples/feed/requirements.txt b/examples/feed/requirements.txt
index e4c4511..0956681 100644
--- a/examples/feed/requirements.txt
+++ b/examples/feed/requirements.txt
@@ -1,2 +1,2 @@
-feedgen==0.4.0
+feedgen==0.9.0
 pytz==2016.6.1
\ No newline at end of file
diff --git a/examples/schedapp/__init__.py b/examples/schedapp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/schedapp/app.py b/examples/schedapp/app.py
new file mode 100644
index 0000000..b4b9c9c
--- /dev/null
+++ b/examples/schedapp/app.py
@@ -0,0 +1,10 @@
+from . import handlers
+from firenado import schedule
+
+
+class SchedappComponent(schedule.ScheduledTornadoComponent):
+
+    def get_handlers(self):
+        return [
+            (r'/', handlers.IndexHandler),
+        ]
diff --git a/examples/schedapp/conf/firenado.yml b/examples/schedapp/conf/firenado.yml
new file mode 100644
index 0000000..3a6a201
--- /dev/null
+++ b/examples/schedapp/conf/firenado.yml
@@ -0,0 +1,36 @@
+app:
+  component: 'schedapp'
+  data:
+    sources:
+        # Set here references from sources defined on data.sources
+        - 'session'
+  pythonpath: '..'
+  #type: 'tornado'
+  port: 8888
+
+data:
+  sources:
+    - name: session
+      connector: redis
+      # host: localhost
+      # port: 6379
+      # db: 0
+components:
+  - id: schedapp
+    class: schedapp.app.SchedappComponent
+    enabled: true
+  #- id: admin
+  #  enabled: true
+  #- id: info
+  #  enabled: true
+
+# Session types could be:
+# file or redis.
+session:
+  type: redis
+  enabled: true
+  # Redis session handler configuration
+  data:
+    source: session
+  # File session handler related configuration
+  # path: /tmp
diff --git a/examples/schedapp/conf/schedapp_schedule.yml b/examples/schedapp/conf/schedapp_schedule.yml
new file mode 100644
index 0000000..f00baa5
--- /dev/null
+++ b/examples/schedapp/conf/schedapp_schedule.yml
@@ -0,0 +1,24 @@
+# This is the loop interval where the scheduled component periodic runtime will
+# go trough all defined schedules and define them to the list.
+# Every schedule will manage it's own scheduled jobs and if not defined this
+# loop interval will be used as default schedule loop interval.
+# The loop interval is defined in milliseconds.
+# Default value is 1000 ms.
+interval: 3000
+schedulers:
+  - id: scheduler1
+    name: Scheduler Example 1
+    interval: 1000
+    jobs:
+      - id: job1
+        class: schedapp.jobs.PrintTestJob
+        interval: 5000
+        custom_property: Custom property from job1.
+      - id: job2
+        class: schedapp.jobs.PrintTestJob
+        cron: "*/1 * * * *"
+        custom_property: Custom property from job2.
+      - id: job3
+        class: schedapp.jobs.PrintAsyncTestJob
+        cron: "*/2 * * * *"
+        custom_property: Custom property from job3.
\ No newline at end of file
diff --git a/examples/schedapp/handlers.py b/examples/schedapp/handlers.py
new file mode 100644
index 0000000..a029672
--- /dev/null
+++ b/examples/schedapp/handlers.py
@@ -0,0 +1,7 @@
+from firenado import tornadoweb
+
+
+class IndexHandler(tornadoweb.TornadoHandler):
+
+    def get(self):
+        self.render("index.html", schedulers=self.component.schedulers)
diff --git a/examples/schedapp/jobs.py b/examples/schedapp/jobs.py
new file mode 100644
index 0000000..2f635c1
--- /dev/null
+++ b/examples/schedapp/jobs.py
@@ -0,0 +1,40 @@
+# -*- coding: UTF-8 -*-
+#
+# Copyright 2015-2020 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.schedule import ScheduledJob
+from tornado.gen import sleep
+
+
+class PrintTestJob(ScheduledJob):
+
+    def __init__(self, scheduler, **kwargs):
+        super(PrintTestJob, self).__init__(scheduler, **kwargs)
+        self._custom_property = kwargs.get('custom_property', None)
+
+    def run(self):
+        print("This is the custom property value: %s" % self._custom_property)
+
+
+class PrintAsyncTestJob(ScheduledJob):
+
+    def __init__(self, scheduler, **kwargs):
+        super(PrintAsyncTestJob, self).__init__(scheduler, **kwargs)
+        self._custom_property = kwargs.get('custom_property', None)
+
+    async def run(self):
+        print("This is the custom property value from async job: %s" %
+              self._custom_property)
+        await sleep(1)
diff --git a/examples/schedapp/templates/base.html b/examples/schedapp/templates/base.html
new file mode 100644
index 0000000..05457d1
--- /dev/null
+++ b/examples/schedapp/templates/base.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+      <meta charset="utf-8">
+      <meta http-equiv="X-UA-Compatible" content="IE=edge">
+      <meta name="viewport" content="width=device-width, initial-scale=1">
+      <title>Firenado Schedule Test Application</title>
+    </head>
+    <body>
+        <div class="container">
+            {% block content %}{% end %}
+        </div>
+        <footer class="my-footer">Firenado Schedule Test Application. Candango Open Source Group</footer>
+    </body>
+</html>
diff --git a/examples/schedapp/templates/index.html b/examples/schedapp/templates/index.html
new file mode 100644
index 0000000..389807c
--- /dev/null
+++ b/examples/schedapp/templates/index.html
@@ -0,0 +1,40 @@
+{% extends "schedapp:base.html" %}
+
+{% block content %}
+    <h1>Schedules</h1>
+
+        {% for scheduler in schedulers %}
+        <h2>Schedule {{ scheduler.id }}</h2>
+
+        <table>
+            <tr>
+                <th>Name</th>
+                <td>{{ scheduler.name }}</td>
+            </tr>
+            <tr>
+                <th style="text-align: left" colspan="2">Jobs:</th>
+            </tr>
+            <tr>
+                <td colspan="2">
+                    <ul>
+                    {% for job in scheduler.jobs %}
+                        <li>{{ job.id }} - {{ job.cron }} - {{ job.hard_id }}</li>
+                    {% end %}
+                    </ul>
+                </td>
+            </tr>
+        </table>
+        {% end %}
+        {% if len(schedulers) == 0 %}
+        <h2>No Schedules found.</h2>
+        <table>
+            <tr>
+                <td colspan="2">
+                    There are no schedulers configured.
+                </td>
+            </tr>
+        </table>
+        {% end %}
+<br>
+{% end %}
+
diff --git a/firenado/__init__.py b/firenado/__init__.py
index 0cd07ed..3a54843 100644
--- a/firenado/__init__.py
+++ b/firenado/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2020 Flavio Garcia
+# 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.
@@ -20,7 +20,7 @@
 from __future__ import (absolute_import, division, print_function,
                         with_statement)
 __author__ = "Flavio Garcia <piraz@candango.org>"
-__version__ = (0, 1, 7, 8)
+__version__ = (0, 2, 6)
 __licence__ = "Apache License V2.0"
 
 
diff --git a/firenado/components/toolbox/pagination.py b/firenado/components/toolbox/pagination.py
deleted file mode 100644
index c5d1a5d..0000000
--- a/firenado/components/toolbox/pagination.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from collections import namedtuple
-
-
-def page_first_row(page, rows_per_page=10):
-    return (int(page) * int(rows_per_page)) + 1 - int(rows_per_page)
-
-
-class Paginator(object):
-
-    def __init__(self, row_count, current_page=None, rows_per_page=10,
-                 pages_per_block=10):
-        if current_page is None or current_page == 0:
-            current_page = 1
-
-        self.row_count = row_count
-        self.rows_per_page = rows_per_page
-        self.rows_last_page = 0
-
-        self.current_page = int(current_page)
-        self.page_count = 1
-
-        self.block_count = 0
-        self.current_block = 0
-        self.pages_per_block = pages_per_block
-        self.pages_last_block = 0
-
-        self.current_first_row = 0
-        self.current_last_row = 0
-
-        self.block_first_page = 0
-        self.block_last_page = 0
-
-        # Setting page count
-        if self.row_count > 0:
-            self.page_count = int(self.row_count/self.rows_per_page) + (
-                1 if self.row_count % self.rows_per_page else 0)
-
-        # Fixed current if overflows the row count
-        if self.page_count < self.current_page:
-            self.current_page = self.page_count
-
-        # Setting rows in the last page
-        self.rows_last_page = self.rows_per_page if not(
-            self.row_count % self.rows_per_page) else (
-            self.row_count % self.rows_per_page)
-
-        # Setting block count
-        self.block_count = int(self.page_count / self.pages_per_block) + (
-            1 if self.page_count % self.pages_per_block else 0)
-
-        # Setting pages in the last block of pages
-        if self.page_count < self.pages_per_block:
-            self.pages_last_block = self.page_count
-        else:
-            self.pages_last_block = self.pages_per_block if not(
-                self.page_count % self.pages_per_block) else (
-                self.page_count % self.pages_per_block)
-
-        # Setting current block of pages
-        if self.current_page <= self.pages_per_block:
-            self.current_block = 1
-        else:
-            self.current_block = int(
-                self.current_page / self.pages_per_block) + (
-                1 if self.current_page % self.pages_per_block else 0)
-
-        # Setting current first row
-        self.current_first_row = page_first_row(self.current_page,
-                                                self.rows_per_page)
-        # Setting current last row
-        if self.current_page == self.page_count:
-            self.current_last_row = (self.current_first_row +
-                                     self.rows_last_page - 1)
-        else:
-            self.current_last_row = (self.current_page * self.rows_per_page)
-
-        # Setting current first page
-        self.block_first_page = ((self.current_block * self.pages_per_block
-                                    ) + 1 - self.pages_per_block)
-        # Setting current last page
-        if self.current_block == self.block_count:
-            self.block_last_page = (self.block_first_page +
-                                    self.pages_last_block - 1)
-        else:
-            self.block_last_page = (self.current_block *
-                                    self.pages_per_block)
-
-    @property
-    def is_first_page(self):
-        if self.current_page == 1:
-            return True
-        return False
-
-    @property
-    def is_last_page(self):
-        if self.current_page == self.page_count:
-            return True
-        return False
-
-    @property
-    def block_pages(self):
-        return range(self.block_first_page, self.block_last_page+1)
-
-PagedData = namedtuple('PagedData', ['data', 'count'])
diff --git a/firenado/components/toolbox/uimodules.py b/firenado/components/toolbox/uimodules.py
index f78a820..772cb5f 100644
--- a/firenado/components/toolbox/uimodules.py
+++ b/firenado/components/toolbox/uimodules.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2016 Flavio Garcia
+# Copyright 2015-2020 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,8 +14,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from cartola.pagination import Paginator
 import tornado.web
-from .pagination import Paginator
 
 
 class Paginate(tornado.web.UIModule):
@@ -36,5 +36,5 @@ def render(self, row_count, current_page, rows_per_page=None,
             pages_per_block = 10
         paginator = Paginator(row_count, current_page, rows_per_page,
                               pages_per_block)
-        return self.render_string(template,argument=argument,
+        return self.render_string(template, argument=argument,
                                   paginator=paginator)
diff --git a/firenado/conf/__init__.py b/firenado/conf/__init__.py
index 0ffb364..6c76681 100644
--- a/firenado/conf/__init__.py
+++ b/firenado/conf/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 import logging
 import os
 import sys
+import tempfile
 
 # Setting root path
 ROOT = None
@@ -80,7 +81,7 @@
 
 # Tmp path variable
 # TODO: Should I care about windows?
-TMP_SYS_PATH = "/tmp"
+TMP_SYS_PATH = tempfile.gettempdir()
 TMP_APP_PATH = TMP_SYS_PATH
 
 # Setting firenado's default variables
@@ -129,11 +130,11 @@
 # If set to 0 the session will not expire
 session['life_time'] = 1800
 session['name'] = "FIRENADOSESSID"
+session['prefix'] = "firenado:session"
 session['purge_limit'] = 500
 session['redis'] = {}
 session['redis']['data'] = {}
 session['redis']['data']['source'] = ""
-session['redis']['prefix'] = "firenado:session"
 session['type'] = ""
 
 if HAS_LIB_CONFIG_FILE:
diff --git a/firenado/conf/firenado.yml b/firenado/conf/firenado.yml
index 82983d7..ce6f1ef 100644
--- a/firenado/conf/firenado.yml
+++ b/firenado/conf/firenado.yml
@@ -36,6 +36,8 @@ session:
     - name: redis
       class: firenado.session.RedisSessionHandler
   encoders:
+    - name: json
+      class: firenado.session.JsonSessionEncoder
     - name: pickle
       class: firenado.session.PickeSessionEncoder
   id_generators:
diff --git a/firenado/config.py b/firenado/config.py
index 1c997d1..97b8a67 100644
--- a/firenado/config.py
+++ b/firenado/config.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 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,8 +14,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from cartola import sysexits
 import yaml
 import logging
+import os
 
 
 def get_app_defaults():
@@ -73,6 +75,7 @@ def log_level_from_string(str_level):
         'WARNING': logging.WARNING,
         'INFO': logging.INFO,
         'DEBUG': logging.DEBUG,
+        'NOTSET': logging.NOTSET,
     }
     try:
         return levels[str_level.upper()]
@@ -202,7 +205,7 @@ def process_app_config(config, config_data):
         config.is_multi_app = True
         process_apps_config_session(config, config_data['apps'])
         if 'app' in config_data:
-            from .util import sysexits
+
             logger = logging.getLogger(__name__)
             logger.critical("Firenado is running in multi application mode. "
                             "The app section is only allowed in simple "
@@ -216,8 +219,6 @@ def process_app_config(config, config_data):
 
 # TODO: This is being used for the multi app configuration
 def process_apps_config_session(config, apps_config):
-    from .util import sysexits
-    import os
     logger = logging.getLogger(__name__)
 
     class AppConf:
@@ -358,9 +359,23 @@ def process_data_config_section(config, data_config):
                 connector['class'])
     if 'sources' in data_config:
         if data_config['sources']:
-            for source in data_config['sources']:
-                config.data['sources'][source['name']] = source
-                del config.data['sources'][source['name']]['name']
+            process_data_sources_config(config, data_config['sources'])
+
+
+def process_data_sources_config_file(config, file):
+    if not os.path.isabs(file):
+        file = os.path.join(config.APP_CONFIG_PATH, file)
+        sources_config = load_yaml_config_file(file)
+        process_data_sources_config(config, sources_config)
+
+
+def process_data_sources_config(config, sources_config):
+    for source_config in sources_config:
+        if "name" in source_config:
+            config.data['sources'][source_config['name']] = source_config
+            del config.data['sources'][source_config['name']]['name']
+        if "file" in source_config:
+            process_data_sources_config_file(config, source_config['file'])
 
 
 def process_log_config_section(config, log_config):
@@ -440,5 +455,15 @@ def process_session_config_section(config, session_config):
         config.session['callback_hiccup'] = session_config['callback_hiccup']
     if 'callback_time' in session_config:
         config.session['callback_time'] = session_config['callback_time']
+    if 'prefix' in session_config:
+        config.session['prefix'] = session_config['prefix']
     if 'purge_limit' in session_config:
         config.session['purge_limit'] = session_config['purge_limit']
+    if 'encoder' in session_config:
+        if session_config['encoder'] in config.session['encoders']:
+            config.session['encoder'] = session_config['encoder']
+        else:
+            logger = logging.getLogger(__name__)
+            logger.critical("The session encoder \"{}\" is not defined."
+                            "".format(session_config['encoder']))
+            sysexits.exit_fatal(sysexits.EX_CONFIG)
diff --git a/firenado/data.py b/firenado/data.py
index 947ee38..203d0f5 100644
--- a/firenado/data.py
+++ b/firenado/data.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 Flavio Goncalves Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 
 import errno
 import functools
+import firenado.conf
 import importlib
 import logging
 import sys
@@ -29,42 +30,6 @@
 logger = logging.getLogger(__name__)
 
 
-def configure_data_sources(data_sources, data_connected):
-    """
-    Configures data sources to a data connected object.
-    :param data_sources: List of data sources to be configured
-    :param data_connected: Data connected object where the data sources will
-    be configured.
-    """
-    if isinstance(data_sources, (string_types, text_type)):
-        import firenado.conf
-        if data_sources in firenado.conf.data['sources']:
-            logger.debug("Found data source [%s] in the list. Preceding with "
-                         "the configuration process." % data_sources)
-            config = firenado.conf.data['sources'][data_sources]
-            connection_handler_config = firenado.conf.data['connectors'][
-                config['connector']]
-            module = importlib.import_module(connection_handler_config['module'])
-            handler_class = getattr(module, connection_handler_config['class'])
-            data_source_instance = handler_class(data_connected)
-            config = data_source_instance.process_config(config)
-            data_source_instance.configure(config)
-            data_connected.set_data_source(data_sources, data_source_instance)
-        else:
-            logger.fatal("It was not possible to find [%s] in the list of "
-                         "available data sources. Please fix the firenado "
-                         "configuration file. Sometimes that could be only a "
-                         "typo in one of app data sources to be created. Look "
-                         "at app.data.sources list." % data_sources)
-            sys.exit(errno.ENOKEY)
-        # TODO: Testing the connection
-        # Without that the error will just happen during the handler execution
-    elif isinstance(data_sources, list):
-        for data_source in data_sources:
-            configure_data_sources(data_source, data_connected)
-    # TODO Throw an error here if it is not string or list
-
-
 def configure(data_sources):
     """ Decorator that configures data sources on a data connected object.
     :param data_sources: List of data sources to be configured.
@@ -320,3 +285,50 @@ def process_config(self, config):
         # TODO: Handler errors here
         db_config['backend'] = db_config['url'].split(':')[0].split('+')[0]
         return db_config
+
+
+def config_to_data_source(config, data_connected):
+    """ Convert a data source config to it's respective data source. We need
+    a data connected to use while instantiating the data source.
+    :param config: A data source configuration item
+    :param data_connected: A data connected object
+    :return: Connector
+    """
+    connector_config = firenado.conf.data['connectors'][
+        config['connector']]
+    module = importlib.import_module(connector_config['module'])
+    handler_class = getattr(module, connector_config['class'])
+    data_source_instance = handler_class(data_connected)
+    config = data_source_instance.process_config(config)
+    data_source_instance.configure(config)
+    return data_source_instance
+
+
+def configure_data_sources(data_sources, data_connected):
+    """ Configure all data sources from configuration and set to the data
+    connected.
+    :param data_sources: List of data sources to be configured
+    :param data_connected: Data connected object where the data sources will
+    be configured.
+    """
+    if isinstance(data_sources, (string_types, text_type)):
+        if data_sources in firenado.conf.data['sources']:
+            logger.debug("Found data source [%s] in the list. Preceding with "
+                         "the configuration process." % data_sources)
+            config = firenado.conf.data['sources'][data_sources]
+            data_source_instance = config_to_data_source(config,
+                                                         data_connected)
+            data_connected.set_data_source(data_sources, data_source_instance)
+        else:
+            logger.fatal("It was not possible to find [%s] in the list of "
+                         "available data sources. Please fix the firenado "
+                         "configuration file. Sometimes that could be only a "
+                         "typo in one of app data sources to be created. Look "
+                         "at app.data.sources list." % data_sources)
+            sys.exit(errno.ENOKEY)
+        # TODO: Testing the connection
+        # Without that the error will just happen during the handler execution
+    elif isinstance(data_sources, list):
+        for data_source in data_sources:
+            configure_data_sources(data_source, data_connected)
+    # TODO Throw an error here if it is not string or list
diff --git a/firenado/launcher.py b/firenado/launcher.py
index 76963a0..344ffeb 100644
--- a/firenado/launcher.py
+++ b/firenado/launcher.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 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,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from cartola import sysexits
 import firenado.conf
 import logging
 import os
@@ -38,6 +39,9 @@ class FirenadoLauncher(object):
 
     def __init__(self, **settings):
         self.app = settings.get("app", None)
+        self.path = settings.get("path", None)
+        if self.path is not None:
+            sys.path.append(self.path)
         if self.app:
             os.environ["CURRENT_APP"] = self.app
         if os.environ.get("CURRENT_APP"):
@@ -47,6 +51,23 @@ def __init__(self, **settings):
         self.port = settings.get("port", None)
         self.socket = settings.get("socket", None)
 
+        # Fixing and cleaning PYTHONPATH and sys.path
+        # This is useful so we can run a process launcher with the same
+        # PYTHONPATH from the parent process
+        real_pythonpaths = sys.path[:]
+        if "PYTHONPATH" in os.environ and os.environ['PYTHONPATH'] is not None:
+            current_pythonpaths = os.environ['PYTHONPATH'].split(":")
+            for path in current_pythonpaths:
+                if path.strip() != "":
+                    if path.strip() not in real_pythonpaths:
+                        real_pythonpaths.append(path.strip())
+            for path in sys.path:
+                if path.strip() != "":
+                    if path.strip() not in real_pythonpaths:
+                        real_pythonpaths.append(path.strip())
+        sys.path = real_pythonpaths
+        os.environ['PYTHONPATH'] = ":".join(real_pythonpaths)
+
     def load(self):
         return None
 
@@ -128,11 +149,12 @@ def send(self, line):
         logger.info("Sending line {}".format(line))
         self.process.sendline(line)
 
-    @gen.coroutine
     def shutdown(self):
         logger.warning("Shutting down process launcher.")
-        self.send("^C")
-        yield self.process.expect("^C", async_=True)
+        self.process.terminate(force=True)
+
+    def is_alive(self):
+        return self.process.isalive()
 
 
 class TornadoLauncher(FirenadoLauncher):
@@ -209,7 +231,6 @@ def launch(self):
                 tornado.process.fork_processes(num_processes, max_restarts)
             tornado.ioloop.IOLoop.current().start()
         else:
-            from .util import sysexits
             logger.critical("Firenado unable to start.")
             sysexits.exit_fatal(sysexits.EX_SOFTWARE)
 
@@ -228,7 +249,7 @@ def sig_handler(self, sig, _):
         else:
             logger.warning("child %s (pid %s) caught signal: %s" %
                            (tid, pid, sig))
-        tornado.ioloop.IOLoop.current().add_callback(self.shutdown)
+        tornado.ioloop.IOLoop.current().add_callback_from_signal(self.shutdown)
 
     def add_sockets(self, port, address=None):
         from socket import gaierror
diff --git a/firenado/management/tasks.py b/firenado/management/tasks.py
index 7b29999..139c1ad 100644
--- a/firenado/management/tasks.py
+++ b/firenado/management/tasks.py
@@ -14,15 +14,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from cartola import fs
+import firenado.conf
+from firenado.management import ManagementTask
+from firenado.util import argparse_util
 import logging
-import sys
-
 import os
 from six import iteritems
-
-import firenado.conf
-from firenado.management import ManagementTask
-from firenado.util import file as _file, argparse_util
+import sys
 
 logger = logging.getLogger(__name__)
 
@@ -37,7 +36,7 @@ def run(self, namespace):
             module = namespace.module
             component = module.replace(".", " ").title().replace(" ", "")
             project_name = module.replace(".", "_").lower()
-            project_directory = _file.create_module(module, os.getcwd())
+            project_directory = fs.create_module(module, os.getcwd())
             #TODO: Check if project exists
             #TODO: If doesn't exists create project
             #TODO: If exists throw an error
@@ -47,24 +46,24 @@ def run(self, namespace):
             project_init_content = loader.load("app.py.txt").generate(
                 project_name=project_name, module=module, component=component)
             # Generating application firenado component and handlers
-            _file.write(os.path.join(project_directory, "__init__.py"), "")
-            _file.write(os.path.join(project_directory, "app.py"),
+            fs.write(os.path.join(project_directory, "__init__.py"), "")
+            fs.write(os.path.join(project_directory, "app.py"),
                         project_init_content.decode(sys.stdout.encoding))
             handlers_file_name = os.path.join(project_directory, "handlers.py")
-            _file.touch(handlers_file_name)
+            fs.touch(handlers_file_name)
             project_handlers_content = loader.load("handlers.py.txt").generate(
                 handlers=["Index"])
-            _file.write(handlers_file_name,
+            fs.write(handlers_file_name,
                         project_handlers_content.decode(sys.stdout.encoding))
             # Generating configuration
             project_conf_directory = os.path.join(project_directory, "conf")
             os.mkdir(project_conf_directory)
             project_conf_file = os.path.join(project_conf_directory,
                                              "firenado.yml")
-            _file.touch(project_conf_file)
+            fs.touch(project_conf_file)
             project_init_content = loader.load("firenado.yml.txt").generate(
                 app_name=project_name, module=module, component=component)
-            _file.write(project_conf_file,
+            fs.write(project_conf_file,
                         project_init_content.decode(sys.stdout.encoding))
         else:
             #TODO: This thing has to go. The parameter validation should be
@@ -114,6 +113,7 @@ def add_arguments(self, parser):
         parser.add_argument("-a", "--addresses", default=None)
         parser.add_argument("-A", "--app", default=None)
         parser.add_argument("-d", "--dir", default=None)
+        parser.add_argument("-p", "--path", default=None)
         parser.add_argument("-P", "--port", type=int)
         parser.add_argument("-s", "--socket", default=None)
 
@@ -128,6 +128,8 @@ def run(self, namespace):
             parameters['app'] = namespace.app
         if namespace.dir is not None:
             parameters['dir'] = namespace.dir
+        if namespace.path is not None:
+            parameters['path'] = namespace.path
         if namespace.socket is None:
             if namespace.addresses is not None:
                 parameters['addresses'] = namespace.addresses.split(",")
@@ -150,14 +152,14 @@ def add_arguments(self, parser):
         parser.add_argument("size", nargs='?', default=64, type=int)
 
     def run(self, namespace):
-        from firenado.util import random_string
+        from cartola import security
         size = namespace.size
         if size > 1000000:
             logger.error("The size {} has reached this command "
                          "limit.".format(size))
             sys.exit(1)
         logger.debug("Displaying a random string with size {}".format(size))
-        print(random_string(size))
+        print(security.random_string(size))
         sys.exit(0)
 
 
diff --git a/firenado/schedule.py b/firenado/schedule.py
new file mode 100644
index 0000000..b58d221
--- /dev/null
+++ b/firenado/schedule.py
@@ -0,0 +1,378 @@
+# -*- coding: UTF-8 -*-
+#
+# Copyright 2015-2020 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 .tornadoweb import TornadoComponent
+from cartola import config, sysexits
+from datetime import datetime, timedelta
+import logging
+from six import iteritems
+import sys
+import tornado.ioloop
+
+logger = logging.getLogger(__name__)
+
+try:
+    from croniter import croniter
+except ImportError:
+    logger.error("*** DEPENDENCY ERROR ***\n The croniter module isn't "
+                 "installed.\n You need to either install firenado with "
+                 "extras(all or schedule) or install croniter to use "
+                 "firenado.schedule package.\n\n Possible fixes:\n  pip "
+                 "install firenado[all]\n  pip install firenado[schedule]\n  "
+                 "pip install croniter\n\n You can also add firenado[all] or "
+                 "firenado[scheudle] or croniter to you requirements file "
+                 "also.\n It's recommended to use firenado with extras "
+                 "instead of installing croniter directly to avoid any "
+                 "version compatibility error.\n The croniter package"
+                 "installed by extras was tested with firenado and no errors "
+                 "are expected during the execution.")
+    sys.exit(sysexits.EX_FATAL_ERROR)
+
+
+def next_from_cron(cron):
+    """ Return next schedule run offset from now to the time of execution
+
+    :param cron: The cron string
+    :return:
+    """
+    iterator = croniter(cron, datetime.now())
+    return iterator.get_next(datetime)
+
+
+class Scheduler(object):
+
+    def __init__(self, component, **kwargs):
+        """
+        Scheduler constructor. It will receive a scheduled component and
+        loop interval as parameters.
+
+        :param ScheduledTornadoComponent component: The scheduled component
+        that owns the scheduler
+        owns the scheduler.
+        :param int interval:
+        """
+        self._can_run = False
+        self._id = None
+        self._jobs = {}
+        self._interval = kwargs.get("interval", 1000)
+        self._name = None
+        self.component = component
+        self._periodic_callback = None
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def jobs(self):
+        return [job for _, job in iteritems(self._jobs)]
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def can_run(self):
+        return self._can_run
+
+    def add_job(self, job):
+        logger.info("Adding job %s into the scheduler [id: %s, name: %s]." %
+                    (job.id, self.id, self.name))
+        self._jobs[job.id] = job
+
+    def get_job(self, job_id):
+        return self._jobs.get(job_id)
+
+    def initialize(self, **kwargs):
+        self._id = kwargs.get("id")
+        self._name = kwargs.get("name")
+        logger.info("Initializing scheduler [id: %s, name: %s]." % (
+            self.id, self.name))
+
+    def load_jobs(self):
+        raise NotImplementedError
+
+    def remove_job(self, job_id):
+        logger.info("Removing job %s from the scheduler [id: %s, name: %s]."
+                    % (job_id, self.id, self.name))
+        job = self.get_job(job_id)
+        if job is None:
+            return None
+        del(self._jobs[job_id])
+        return job.id
+
+    def run(self):
+        self.load_jobs()
+        logger.info("Scheduler [id: %s, name: %s] interval set to %sms." %
+                    (self.id, self.name, self._interval))
+        self._periodic_callback = tornado.ioloop.PeriodicCallback(
+            self._manage_jobs, self._interval)
+        self._periodic_callback.start()
+
+    def _manage_jobs(self):
+        logger.debug("Scheduler [id: %s, name: %s] managing jobs." %
+                    (self.id, self.name))
+        logger.debug("Scheduler [id: %s, name: %s] stopping periodic callback."
+                     % (self.id, self.name))
+        self._periodic_callback.stop()
+        for job in self.jobs:
+            if not job.already_scheduled:
+                logger.info("Job %s from Scheduler [id: %s, name: %s] isn't "
+                            "scheduled yet." % (job.hard_id, self.id,
+                                                self.name))
+                if job.must_schedule:
+                    logger.info(
+                        "Job %s from Scheduler [id: %s, name: %s] must be "
+                        "scheduled to run at %s." % (
+                            job.hard_id, self.id, self.name, job.next_run))
+                    job.schedule()
+            else:
+                logger.debug("Job %s from Scheduler [id: %s, name: %s] already"
+                             "scheduled." % (job.hard_id, self.id,
+                                                self.name))
+
+        logger.debug("Scheduler [id: %s, name: %s] ending of managing jobs." %
+                    (self.id, self.name))
+        logger.debug("Scheduler [id: %s, name: %s] starting periodic callback."
+                     % (self.id, self.name))
+        self._periodic_callback.start()
+
+
+class ConfScheduler(Scheduler):
+
+    def __init__(self, scheduled_component, **kwargs):
+        super(ConfScheduler, self).__init__(scheduled_component, **kwargs)
+        self._conf = kwargs.get("conf", None)
+
+    def load_jobs(self):
+        if "jobs" in self._conf:
+            for job_conf in self._conf['jobs']:
+                job_id = job_conf.get("id")
+                job_resolved_class = None
+                job_cron = job_conf.get("cron")
+                job_date = job_conf.get("date")
+                job_interval = job_conf.get("interval")
+                if 'class' in job_conf:
+                    job_resolved_class = config.get_from_string(
+                        job_conf['class'])
+                if job_resolved_class is None:
+                    logger.warning("Firenado Scheduled Job Error:\n    The "
+                                   "scheduled class job %s was not found "
+                                   "in the file:\n        %s\n    Job id: %s"
+                                   "\n    Scheduler: [id: %s, name: %s]"
+                                   "\n        Config: %s\n"
+                                   "\n    Please fix this issue.\n" %
+                                   (job_conf['class'],
+                                    self.component.get_complete_config_file(),
+                                    job_id, self.id, self.name, job_conf))
+                else:
+                    if job_id is None:
+                        logger.warning(
+                            "Firenado Scheduled Job Error:\n    The "
+                            "scheduled job id not defined in the "
+                            "file:\n        %s"
+                            "\n    Scheduler: [id: %s, name: %s]"
+                            "\n        Config: %s\n"
+                            "\n    Please fix this issue.\n" %
+                            (self.component.get_complete_config_file(),
+                             self.id, self.name, job_conf))
+                    else:
+                        if (job_cron is None and job_date is None
+                                and job_interval is None):
+                            logger.warning(
+                                "Firenado Scheduled Job Error:\n    The "
+                                "scheduled must have a cron or date or "
+                                "interval defined in the file:\n        %s\n  "
+                                "  Job id: %s\n    Scheduler: [id: %s, "
+                                "name: %s]\n        Config: %s\n"
+                                "\n    Please define either a cron string or "
+                                "date.\n" %
+                                (self.component.get_complete_config_file(),
+                                 job_id, self.id, self.name, job_conf))
+                        else:
+                            self.add_job(job_resolved_class(self, **job_conf))
+
+    def initialize(self):
+        has_error = False
+        if self._conf is None:
+            logger.warning("It is not possible to initialize the scheduler "
+                           "because the configuration is missing.")
+            has_error = True
+        if "id" not in self._conf:
+            logger.warning("It is not possible to initialize the scheduler "
+                           "because id is missing in the configuration "
+                           "provided.\n Config: %s" % self._conf)
+            has_error = True
+        if "name" not in self._conf:
+            logger.warning("It is not possible to initialize the scheduler "
+                           "because name is missing in the configuration "
+                           "provided.\n Config: %s" % self._conf)
+            has_error = True
+
+        if not has_error:
+            self._can_run = True
+            super(ConfScheduler, self).initialize(**self._conf)
+
+        if "interval" in self._conf:
+            self._interval = self._conf['interval']
+
+
+class ScheduledJob(object):
+
+    def __init__(self, scheduler, **kwargs):
+        self._scheduler = scheduler
+        self._id = kwargs.get('id')
+        self._date = kwargs.get('date')
+        self._cron = kwargs.get('cron')
+        self._interval = kwargs.get('interval')
+        self._periodic_callback = None
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def hard_id(self):
+        return "%s:%s:%s" % (self._id, self.__class__.__name__, id(self))
+
+    @property
+    def date(self):
+        return self._date
+
+    @property
+    def cron(self):
+        return self._cron
+
+    @property
+    def already_scheduled(self):
+        return self._periodic_callback is not None
+
+    @property
+    def next_run(self):
+        if self._interval:
+            return datetime.now() + timedelta(milliseconds=self._interval)
+        if self._cron:
+            return next_from_cron(self.cron)
+        # TODO: run if date is defined
+        return datetime.now() + timedelta(days=-356)
+
+    @property
+    def next_interval(self):
+        now = datetime.now()
+        delta = self.next_run - now
+        return delta.total_seconds() * 1000
+
+    @property
+    def must_schedule(self):
+        return self.next_interval > 0
+
+    def schedule(self):
+        next_interval = self.next_interval
+        logger.info("Job %s from Scheduler [id: %s, name: %s] scheduled to run"
+                    " at next interval of %sms." % (self.hard_id,
+                                                    self._scheduler.id,
+                                                    self._scheduler.name,
+                                                    self.next_interval))
+        self._periodic_callback = tornado.ioloop.PeriodicCallback(
+            self._run_job, next_interval)
+        self._periodic_callback.start()
+
+    async def _run_job(self):
+        """ Run the job
+        :return None:
+        """
+        self._periodic_callback.stop()
+        logger.info(
+            "Running job %s from Scheduler [id: %s, name: %s]." % (
+                self.hard_id, self._scheduler.id, self._scheduler.name))
+        future = self.run()
+        if future is not None:
+            logger.debug(
+                "Running job %s from Scheduler [id: %s, name: %s] "
+                "asynchronously. " % (self.hard_id, self._scheduler.id,
+                                      self._scheduler.name))
+            await future
+        logger.info(
+            "Job %s removed from Scheduler [id: %s, name: %s]" % (
+                self.hard_id, self._scheduler.id, self._scheduler.name))
+        self._periodic_callback = None
+        return
+
+    def run(self):
+        pass
+
+
+class ScheduledTornadoComponent(TornadoComponent):
+
+    def __init__(self, name, application):
+        super(ScheduledTornadoComponent, self).__init__(name, application)
+        self._interval = 1000
+        self._has_conf = False
+        self._schedulers = {}
+
+    @property
+    def has_schedulers(self):
+        return len(self._schedulers) > 0
+
+    @property
+    def schedulers(self):
+        return [schedule for _, schedule in iteritems(self._schedulers)]
+
+    def get_config_filename(self):
+        return "%s_%s" % (self.name, "schedule")
+
+    def get_scheduler(self, name):
+        if name in self.schedulers:
+            return self.schedulers[name]
+        return None
+
+    def _config_schedule_component(self):
+        if "interval" in self.schedule_conf():
+            self._interval = self.conf['interval']
+        logger.info("Scheduled component %s interval set to %sms." %
+                    (self.name, self._interval))
+        if "schedulers" in self.schedule_conf():
+            logger.info("Initializing %s schedulers." % self.name)
+            self._initialize_schedulers()
+        else:
+            logger.warning("No scheduler was defined in the scheduled "
+                           "component %s.\n\n%s\n* It is necessary either "
+                           "define schedulers or scheduler loaders in the \n*"
+                           " %s config file. \n* %s-- Firenado scheduled "
+                           "component\n%s\n" %
+                           (self.name, "*" * 70,
+                            self.get_config_file(), " " * 37, "*" * 70))
+
+    def _initialize_schedulers(self):
+        for scheduler_conf in self.conf['schedulers']:
+            scheduler = ConfScheduler(self, interval=self._interval,
+                                      conf=scheduler_conf)
+            scheduler.initialize()
+            if scheduler.can_run:
+                self._schedulers[scheduler.id] = scheduler
+                scheduler.run()
+
+    def schedule_conf(self):
+        return self.conf
+
+    def initialize(self):
+        if self.has_conf:
+            logger.info("Configuration file found. Starting scheduled "
+                        "component %s initialization." % self.name)
+            self._config_schedule_component()
+        else:
+            logger.warning("No configuration file was found. Scheduled"
+                           "component %s won't initialize")
diff --git a/firenado/security.py b/firenado/security.py
index 7683a75..659ad7d 100644
--- a/firenado/security.py
+++ b/firenado/security.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,9 +19,6 @@
 
 import functools
 from tornado.web import HTTPError
-import os
-import string
-import sys
 
 try:
     import urlparse  # py2
@@ -141,6 +138,7 @@ def f_wrapper(par_method):
             import firenado.conf
             # Means someone added a parameter to the decorator
             url = firenado.conf.app['login']['urls'][method]
+
             @functools.wraps(par_method)
             def par_wrapper(self, *args, **kwargs):
                 kwargs['wrapped_method'] = par_method
@@ -160,67 +158,3 @@ def wrapper(self, *args, **kwargs):
             return method(self, *args, **kwargs)
         return wrapper
     return f_wrapper
-
-
-# Used implementations described on: http://bit.ly/2gHlH9z
-# Recommended here: http://bit.ly/2fm97H3
-# Confirmed here:
-# https://docs.python.org/2/library/random.html#random.SystemRandom
-# TODO: Use that after 3.6 https://bit.ly/2wvubJ6
-def random_string(length=5, upper_chars=True, punctuation=False):
-    """
-    Generate a random string with the size equal to the given length.
-
-    The string is based on random choices from a sequence of ascii lower case
-    characters and digits.
-
-    If length is not informed the string size will be 5.
-    """
-    chars = string.ascii_lowercase + string.digits
-    if upper_chars:
-        chars += string.ascii_uppercase
-    if punctuation:
-        chars += string.punctuation
-    if sys.version_info < (3, 6):
-        import random
-        return ''.join(
-            random.SystemRandom().choice(chars) for _ in range(length)
-        )
-    else:
-        import secrets
-        return ''.join(secrets.choice(chars) for _ in range(length))
-
-
-class KeyManager(object):
-
-    SALT_SIZE = 24
-
-    def generate(self, password):
-        # First is hash second is salt
-        # Salt is generated randomly
-        return None
-
-    def create_hash(self, password, salt):
-        return None
-
-    def validate_password(self, password, correct_hash):
-        return False
-
-
-class Sha512KeyManager(KeyManager):
-    # following: https://crackstation.net/hashing-security.htm
-
-    def generate(self, password):
-        salt = os.urandom(self.SALT_SIZE).encode('hex')
-        return "%s:%s" % (self.create_hash(password, salt), salt)
-
-    def create_hash(self, password, salt):
-        import hashlib
-        salted_password = "%s%s" % (password, salt)
-        return hashlib.sha512(salted_password).hexdigest()
-
-    def validate_password(self, password, correct_hash):
-        correct_hashX = correct_hash.split(':')
-        canditate_hash = "%s:%s" % (
-            self.create_hash(password, correct_hashX[1]), correct_hashX[1])
-        return canditate_hash == correct_hash
diff --git a/firenado/session.py b/firenado/session.py
index 9a4839e..8b6cf9e 100644
--- a/firenado/session.py
+++ b/firenado/session.py
@@ -17,9 +17,9 @@
 from __future__ import (absolute_import, division, print_function,
                         with_statement)
 
+from cartola import fs, security
 import firenado.conf
 from firenado.config import get_class_from_config
-from firenado.util import file as _file, random_string
 
 import functools
 import logging
@@ -394,13 +394,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))
-            _file.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 = _file.read(session_file)
+        timed_data = fs.read(session_file)
         return binascii.unhexlify(timed_data.split("--")[0])
 
     def write_stored_session(self, session_id, data):
@@ -409,7 +409,7 @@ def write_stored_session(self, session_id, data):
         timed_data = "%s--%s" % (binascii.hexlify(data).decode("ascii"),
                                  int(time.time()))
         session_file = os.path.join(self.path, self.__get_filename(session_id))
-        _file.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)
@@ -442,7 +442,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 = _file.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:
@@ -549,12 +549,12 @@ def is_session_stored(self, session_id):
     def __get_key(self, session_id):
         if firenado.conf.app['id'] is not None:
             return '%s:%s:%s' % (
-                firenado.conf.session['redis']['prefix'],
+                firenado.conf.session['prefix'],
                 firenado.conf.app['id'],
                 session_id
             )
         return '%s:%s' % (
-            firenado.conf.session['redis']['prefix'],
+            firenado.conf.session['prefix'],
             session_id
         )
 
@@ -579,6 +579,17 @@ def decode(self, data):
         return pickle.loads(data)
 
 
+class JsonSessionEncoder(SessionEncoder):
+
+    def encode(self, data):
+        from tornado import escape
+        return escape.json_encode(data)
+
+    def decode(self, data):
+        from tornado import escape
+        return escape.json_decode(data)
+
+
 def generate_session_id():
     """
     Default firenado session id generator
@@ -586,4 +597,4 @@ def generate_session_id():
     Returns:
         A random string containing digits, upper and lower characters
     """
-    return random_string(64)
+    return security.random_string(64)
diff --git a/firenado/tornadoweb.py b/firenado/tornadoweb.py
index 03b6dfe..765d7c0 100644
--- a/firenado/tornadoweb.py
+++ b/firenado/tornadoweb.py
@@ -1,6 +1,6 @@
 # -*- coding: UTF-8 -*-
 #
-# Copyright 2015-2019 Flavio Garcia
+# 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.
@@ -17,6 +17,7 @@
 from __future__ import (absolute_import, division, print_function,
                         with_statement)
 
+from cartola import fs
 import tornado.web
 import firenado.conf
 from . import data
@@ -25,19 +26,73 @@
 from .config import get_class_from_config, load_yaml_config_file
 import inspect
 import logging
-from tornado.escape import json_encode
+import os
+from six import iteritems
+from tornado.httpclient import HTTPRequest
 from tornado.template import Loader
 import tornado.websocket
-import os
-from six import iteritems, string_types
-
+from typing import Any
 
 logger = logging.getLogger(__name__)
 
 
+def get_request(url, **kwargs):
+    """ Return a HTTPRequest to help with AsyncHTTPClient and HTTPClient
+    execution. The HTTPRequest will use the provided url combined with path
+    if provided. The HTTPRequest method will be GET by default and can be
+    changed if method is informed.
+    If form_urlencoded is defined as True a Content-Type header will be added
+    to the request with application/x-www-form-urlencoded value.
+
+    :param str url: Base url to be set to the HTTPRequest
+    :key form_urlencoded: If the true will add the header Content-Type
+    application/x-www-form-urlencoded to the form. Default is False.
+    :key method: Method to be used by the HTTPRequest. Default it GET.
+    :key path: If informed will add the path to the base url informed. Default
+    is None.
+    :return HTTPRequest:
+    """
+    method = kwargs.get("method", "GET")
+    path = kwargs.get("path", None)
+    form_urlencoded = kwargs.get("form_urlencoded", False)
+    if path is not None:
+        if not url.endswith("/"):
+            url = "%s/" % url
+        url = "%s%s" % (url, path)
+    request = HTTPRequest(url, method=method)
+    if form_urlencoded:
+        request.headers.add("Content-Type",
+                            "application/x-www-form-urlencoded")
+    return request
+
+
+class TornadoErrorHandler(object):
+
+    def __init__(self, host):
+        self._host = host
+
+    @property
+    def host(self):
+        return self._host
+
+    def is_component(self):
+        if isinstance(self.host, TornadoComponent):
+            return True
+        return False
+
+    def is_handler(self):
+        if isinstance(self.host, TornadoHandler):
+            return True
+        return False
+
+    def handle_error(self, request: "TornadoHandler", status_code: int,
+                     **kwargs: Any) -> None:
+        request.write_error(status_code, **kwargs)
+
+
 class TornadoApplication(tornado.web.Application, data.DataConnectedMixin,
                          session.SessionEnginedMixin):
-    """Firenado basic Tornado application.
+    """ Firenado basic Tornado application.
     """
 
     def __init__(self, default_host="", transforms=None, **settings):
@@ -114,39 +169,24 @@ def get_app_component(self):
         return self.components[firenado.conf.app['component']]
 
     def __load_components(self):
-        """ Loads all enabled components registered into the components
-        conf.
+        """ Loads all enabled components registered from the components
+        config section.
         """
         for key, value in iteritems(firenado.conf.components):
             if value['enabled']:
                 component_class = get_class_from_config(value)
                 self.components[key] = component_class(key, self)
                 if self.components[key].get_config_file():
-                    from firenado.util.file import file_has_extension
-                    filename = self.components[key].get_config_file()
-                    comp_config_file = None
-                    if file_has_extension(filename):
-                        if os.path.isfile(os.path.join(
-                                firenado.conf.APP_CONFIG_PATH, filename)):
-                            comp_config_file = os.path.join(
-                                firenado.conf.APP_CONFIG_PATH, filename)
-                    else:
-                        config_file_extensions = ['yml', 'yaml']
-                        for extension in config_file_extensions:
-                            candidate_filename = os.path.join(
-                                    firenado.conf.APP_CONFIG_PATH,
-                                    "%s.%s" % (filename, extension))
-                            if os.path.isfile(candidate_filename):
-                                comp_config_file = candidate_filename
-                                break
-                    if comp_config_file is not None:
+                    filename = self.components[key].get_complete_config_file()
+                    if filename is not None:
                         self.components[key].conf = load_yaml_config_file(
-                            comp_config_file)
+                            filename)
                         self.components[key].process_config()
+                        self.components[key].has_conf = True
                     else:
                         logger.debug("Failed to find the file for the "
-                                     "component %s at %s. Component filename "
-                                     "returned is %s." % (
+                                     "component %s at %s. Component's "
+                                     "filename returned is %s." % (
                                         key, firenado.conf.APP_CONFIG_PATH,
                                         self.components[key].get_config_file())
                                      )
@@ -166,28 +206,44 @@ def __init__(self, name, application):
         self.name = name
         self.application = application
         self.conf = {}
+        self._has_conf = False
         self.plugins = dict()
 
-    def after_handler(self, handler):
+    def after_request(self, handler):
         """ Add a logic to be executed after all component's handlers
         execution.
         """
         pass
 
-    def before_handler(self, handler):
+    def before_request(self, handler):
         """ Add a logic to be executed before all component's handler
         execution.
         """
         pass
 
+    def get_error_handler(self) -> TornadoErrorHandler:
+        """Return a `TornadoErrorHandler` here to provide a different error
+        handling than the tornado's default. If the error handler is
+        implemented at the component, all handlers will use it as default. If a
+        handler implements the `get_error_handler` method, it will be used
+        instead of the one implemented at the component."""
+        pass
+
     def is_current_app(self):
         if not firenado.conf.is_multi_app:
             return True
-        else:
-            if firenado.conf.current_app_name == self.name:
-                return True
+        if firenado.conf.current_app_name == self.name:
+            return True
         return False
 
+    @property
+    def has_conf(self):
+        return self._has_conf
+
+    @has_conf.setter
+    def has_conf(self, value):
+        self._has_conf = value
+
     def get_handlers(self):
         """ Returns handlers being added by the component to the application.
         :return: A list of handlers the component provides.
@@ -216,6 +272,25 @@ def get_config_file(self):
             return filename
         return None
 
+    def get_complete_config_file(self):
+        """ Return the config file with the correct extension, if
+        get_config_file has no extension.
+
+        :return str: The config file with extension
+        """
+        if fs.file_has_extension(self.get_config_file()):
+            if os.path.isfile(self.get_config_file()):
+                return os.path.join(firenado.conf.APP_CONFIG_PATH,
+                                    self.get_config_file())
+        config_file_extensions = ['yml', 'yaml']
+        for extension in config_file_extensions:
+            candidate_filename = "%s.%s" % (self.get_config_file(), extension)
+            if os.path.isfile(os.path.join(
+                    firenado.conf.APP_CONFIG_PATH, candidate_filename)):
+                return os.path.join(firenado.conf.APP_CONFIG_PATH,
+                                    candidate_filename)
+        return None
+
     def get_template_path(self):
         """ Returns the path that holds the component's templates.
         """
@@ -276,27 +351,91 @@ def authenticated(self):
         return self.current_user is not None
 
     def is_mobile(self):
-        from .util import browser
+        from mobiledetect import MobileDetect
         if 'User-Agent' in self.request.headers:
-            return browser.is_mobile(self.request.headers['User-Agent'])
+            return MobileDetect(
+                useragent=self.request.headers['User-Agent']
+            ).is_mobile()
         return False
 
+    def write_error(self, status_code: int, **kwargs: Any) -> None:
+        """
+        See: https://tinyurl.com/9t3jrend
+        :param int status_code:
+        :param Any kwargs:
+        :return:
+        """
+        error_handler = self.get_error_handler()
+        if error_handler is None:
+            error_handler = self.component.get_error_handler()
+        if error_handler is None:
+            super(TornadoHandler, self).write_error(status_code, **kwargs)
+        else:
+            error_handler.handle_error(self, status_code, **kwargs)
+
     @session.read
     def prepare(self):
-        self.component.before_handler(self)
+        self.component.before_request(self)
+        self.before_request()
 
     @session.write
     def on_finish(self):
-        self.component.after_handler(self)
+        self.after_request()
+        self.component.after_request(self)
+
+    def after_request(self):
+        """Called after the end of a request.
+
+        Override this method to perform cleanup, logging, etc.
+        This method is a counterpart to `prepare`.  ``on_finish`` may
+        not produce any output, as it is called after the response
+        has been sent to the client.
+
+        Use this method instead of `on_finish` to avoid the session to break
+        and use session features. This method will be called by `on_finish`
+        with a valid session.
+
+        This method is called before it's component's after_request if defined.
+        """
+        pass
+
+    def get_error_handler(self) -> TornadoErrorHandler:
+        """Return a `TornadoErrorHandler` here to provide a different error
+        handling than the tornado's default. If the error handler is
+        implemented at the handler, it will be used instead of the one
+        implemented at the component."""
+        pass
+
+    def before_request(self):
+        """Called at the beginning of a request before  `get`/`post`/etc.
+
+        Override this method to perform common initialization regardless
+        of the request method.
+
+        Use this method instead of `prepare` to avoid the session to break and
+        use session features. This method will be called by `prepare` with
+        a valid session.
+
+        This method is called after it's component's before_request if defined.
+
+        Asynchronous support: Use ``async def`` or decorate this method with
+        `.gen.coroutine` to make it asynchronous.
+        If this method returns an  ``Awaitable`` execution will not proceed
+        until the ``Awaitable`` is done.
+
+        .. versionadded:: 0.1.10
+           Asynchronous support.
+        """
+        pass
 
     def render_string(self, template_name, **kwargs):
         ignore_component = False
         application_component = None
         for key in ('ignore_component', 'component',):
             if key in kwargs:
-                if key is 'ignore_component':
+                if key == 'ignore_component':
                     ignore_component = kwargs[key]
-                if key is 'component':
+                if key == 'component':
                     pass
         kwargs['user_agent'] = self.user_agent if hasattr(
             self, 'user_agent') else None
@@ -313,22 +452,6 @@ def render_string(self, template_name, **kwargs):
             self._finished = False
             return None
 
-    def write_error(self, status_code, **kwargs):
-        error_stack = {'code': status_code}
-
-        exc_info = None
-        for key in kwargs:
-            if key == 'exc_info':
-                exc_info = kwargs[key]
-        error = exc_info[1]
-
-        if type(error) == JSONError:
-            error_stack.update(error.data)
-            response = dict(data=None, error=error_stack)
-            self.write(response)
-        else:
-            raise error
-
     def get_data_connected(self):
         return self.application
 
@@ -440,9 +563,9 @@ def render_string(self, template_name, **kwargs):
         application_component = None
         for key in ('ignore_component', 'component',):
             if key in kwargs:
-                if key is 'ignore_component':
+                if key == 'ignore_component':
                     ignore_component = kwargs[key]
-                if key is 'component':
+                if key == 'component':
                     pass
         kwargs['user_agent'] = self.user_agent if hasattr(
             self, 'user_agent') else None
@@ -459,22 +582,6 @@ def render_string(self, template_name, **kwargs):
             self._finished = False
             return None
 
-    def write_error(self, status_code, **kwargs):
-        error_stack = {'code': status_code}
-
-        exc_info = None
-        for key in kwargs:
-            if key == 'exc_info':
-                exc_info = kwargs[key]
-        error = exc_info[1]
-
-        if type(error) == JSONError:
-            error_stack.update(error.data)
-            response = dict(data=None, error=error_stack)
-            self.write(response)
-        else:
-            raise error
-
     def get_firenado_template_path(self):
         """Override to customize the firenado template path for each handler.
 
@@ -512,17 +619,3 @@ def create_template_loader(self, template_path):
             kwargs['autoescape'] = settings['autoescape']
         return FirenadoComponentLoader(
             template_path, component=self.component, **kwargs)
-
-
-#TODO: This is iFlux migration leftover. Is that necessary?.
-class JSONError(tornado.web.HTTPError):
-
-    def __init__(self, status_code, log_message=None, *args, **kwargs):
-        data = {}
-        self.data.update(log_message)
-        if not isinstance(log_message, string_types):
-            json_log_message = self.data
-            json_log_message['code'] = status_code
-            json_log_message = json_encode(json_log_message)
-        super(JSONError, self).__init__(
-            status_code, json_log_message, *args, **kwargs)
diff --git a/firenado/util/__init__.py b/firenado/util/__init__.py
index bbd7f74..e69de29 100644
--- a/firenado/util/__init__.py
+++ b/firenado/util/__init__.py
@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2018 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 __future__ import (absolute_import, division, print_function,
-                        with_statement)
-
-from ..security import random_string as _random_string
-
-
-def random_string(length=5, upper_chars=True, punctuation=False):
-    return _random_string(length, upper_chars, punctuation)
diff --git a/firenado/util/browser.py b/firenado/util/browser.py
deleted file mode 100644
index 2f67728..0000000
--- a/firenado/util/browser.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2016 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.
-
-import re
-
-# from http://detectmobilebrowsers.com/
-reg_b = re.compile(
-    r"(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal"
-    r"|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|"
-    r"mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|"
-    r"re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|"
-    r"link)|vodafone|wap|windows ce|xda|xiino", re.I | re.M)
-reg_v = re.compile(
-    r"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|"
-    r"ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|"
-    r"au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|"
-    r"bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw"
-    r"|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)"
-    r"|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560"
-    r"|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|"
-    r"hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|"
-    r"hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k"
-    r"|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |"
-    r"kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|"
-    r"m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|"
-    r"ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10"
-    r"[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|"
-    r"wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|"
-    r"pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio"
-    r"|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9"
-    r"|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se"
-    r"(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm"
-    r"(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00"
-    r"|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70"
-    r"|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40"
-    r"|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|"
-    r"w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|"
-    r"zte\\-", re.I | re.M)
-
-
-def is_mobile(user_agent):
-    """ Checks if the user browser from the given user agent is mobile.
-
-    Args:
-        user_agent: A given user agent.
-
-    Returns: True if the browser from the user agent is mobile.
-
-    """
-    if user_agent:
-        b = reg_b.search(user_agent)
-        v = reg_v.search(user_agent[0:4])
-        return b or v
-    return False
-
diff --git a/firenado/util/file.py b/firenado/util/file.py
deleted file mode 100644
index 4249843..0000000
--- a/firenado/util/file.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2016 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.
-
-import os
-
-
-def create_module(module, target):
-    """ Create a module directory structure into the target directory. """
-    module_x = module.split('.')
-    cur_path = ''
-    for path in module_x:
-        cur_path = os.path.join(cur_path, path)
-        if not os.path.isdir(os.path.join(target, cur_path)):
-            os.mkdir(os.path.join(target, cur_path))
-        if not os.path.exists(os.path.join(target, cur_path, '__init__.py')):
-            touch(os.path.join(target, cur_path, '__init__.py'))
-    return cur_path
-
-
-def file_has_extension(filename):
-    """ Return True if the informed filename was extension on it.
-
-    :param filename: The filename.
-    :return: True if has extension.
-    """
-    if get_file_extension(filename) is None:
-        return False
-    return True
-
-
-def get_file_extension(filename):
-    """ Return the extension if the filename has it. None if not.
-
-    :param filename: The filename.
-    :return: Extension or None.
-    """
-    filename_x = filename.split('.')
-    if len(filename_x) > 1:
-        if filename_x[-1].strip() is not '':
-            return filename_x[-1]
-    return None
-
-
-def write(path, data, binary=False):
-    """ Writes a given data to a file located at the given path. """
-    mode = "w"
-    if binary:
-        mode = "wb"
-    with open(path, mode) as f:
-        f.write(data)
-    f.close()
-
-
-def read(path):
-    """ Reads a file located at the given path. """
-    data = None
-    with open(path, 'r') as f:
-        data = f.read()
-    f.close()
-    return data
-
-
-def touch(path):
-    """ Creates a file located at the given path. """
-    with open(path, 'a') as f:
-        os.utime(path, None)
-    f.close()
diff --git a/firenado/util/sqlalchemy_util.py b/firenado/util/sqlalchemy_util.py
index 48433a3..bb978e5 100644
--- a/firenado/util/sqlalchemy_util.py
+++ b/firenado/util/sqlalchemy_util.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2018 Flavio Garcia
+# Copyright 2015-2020 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,18 +15,32 @@
 # limitations under the License.
 
 import logging
-from sqlalchemy import text
+from sqlalchemy import inspect, text
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import sessionmaker
 
-# Name as sqlalchemy_util to avoid concurrency with the original
-# sqlalchemy module
+# 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 run_script(script_path, session, handle_command=None, handle_line=None):
     """ Run a script file using a valid sqlalchemy session.
 
diff --git a/firenado/util/sysexits.py b/firenado/util/sysexits.py
deleted file mode 100644
index d033781..0000000
--- a/firenado/util/sysexits.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# -*- coding: UTF-8 -*-
-#
-# Copyright 2015-2019 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.
-
-"""
-The Explanation from the codes are described here: https://bit.ly/2D2ZFY9
-
-Also see:
-    http://tldp.org/LDP/abs/html/exitcodes.html
-"""
-
-# Catchall for general errors
-EX_CATCHALL = 1
-# Misuse of shell builtins (according to Bash documentation)
-EX_MISUSE = 2
-# Command invoked cannot execute
-EX_CANNOT_EXECUTE = 126
-# Command not found
-EX_NOT_FOUND = 127
-# Invalid argument to exit
-EX_INVALID_ARGUMENT_TO_EXIT = 128
-# Fatal error signal "n"
-EX_FATAL_ERROR = 128
-# Script terminated by Control-C
-EX_TERMINATED_BY_CRTL_C = 130
-# Exit status out of range
-EX_OUT_OF_RANGE = 255
-
-# successful termination
-EX_OK = 0
-# base value for error messages
-EX__BASE = 64
-# command line usage error
-EX_USAGE = 64
-# data format error
-EX_DATAERR = 65
-# cannot open input
-EX_NOINPUT = 66
-# addressee unknown
-EX_NOUSER = 67
-# host name unknown
-EX_NOHOST = 68
-# service unavailable
-EX_UNAVAILABLE = 69
-# internal software error
-EX_SOFTWARE = 70
-# system error (e.g., can't fork)
-EX_OSERR = 71
-# critical OS file missing
-EX_OSFILE = 72
-# can't create (user) output file
-EX_CANTCREAT = 73
-# input/output error
-EX_IOERR = 74
-# temp failure; user is invited to retry
-EX_TEMPFAIL = 75
-# remote error in protocol
-EX_PROTOCOL = 76
-# permission denied
-EX_NOPERM = 77
-# configuration error
-EX_CONFIG = 78
-
-# maximum listed value
-EX__MAX = 78
-
-
-def exit_fatal(signal):
-    import sys
-    sys.exit(EX_FATAL_ERROR + signal)
diff --git a/requirements/all.txt b/requirements/all.txt
index 2de4716..1afc538 100644
--- a/requirements/all.txt
+++ b/requirements/all.txt
@@ -1,3 +1,4 @@
 -r pexpect.txt
 -r redis.txt
+-r schedule.txt
 -r sqlalchemy.txt
diff --git a/requirements/basic.txt b/requirements/basic.txt
index c0531b3..e8a14a9 100644
--- a/requirements/basic.txt
+++ b/requirements/basic.txt
@@ -1,3 +1,3 @@
-pyyaml==5.2
-tornado==5.1.1
-six==1.13.0
+cartola==0.12
+pymobiledetect==1.3.2
+six==1.15.0
diff --git a/requirements/development.txt b/requirements/development.txt
new file mode 100644
index 0000000..6ca83c9
--- /dev/null
+++ b/requirements/development.txt
@@ -0,0 +1,5 @@
+-r all.txt
+-r basic.txt
+-r tests.txt
+-r tornado_new.txt
+pymysql==1.0.2
diff --git a/requirements/pexpect.txt b/requirements/pexpect.txt
index 8eb843e..57d3570 100644
--- a/requirements/pexpect.txt
+++ b/requirements/pexpect.txt
@@ -1,1 +1,1 @@
-pexpect==4.7.0
+pexpect==4.8.0
diff --git a/requirements/redis.txt b/requirements/redis.txt
index c05919f..a58ec79 100644
--- a/requirements/redis.txt
+++ b/requirements/redis.txt
@@ -1,2 +1,2 @@
-redis==3.3.11
-hiredis==1.0.1
+redis==3.5.3
+hiredis==1.1.0
diff --git a/requirements/schedule.txt b/requirements/schedule.txt
new file mode 100644
index 0000000..7b9aa21
--- /dev/null
+++ b/requirements/schedule.txt
@@ -0,0 +1,1 @@
+croniter==1.0.8
diff --git a/requirements/sqlalchemy.txt b/requirements/sqlalchemy.txt
index 0edc354..bfd4a1c 100644
--- a/requirements/sqlalchemy.txt
+++ b/requirements/sqlalchemy.txt
@@ -1,1 +1,1 @@
-sqlalchemy==1.3.12
+sqlalchemy==1.3.23
diff --git a/requirements/tornado_legacy.txt b/requirements/tornado_legacy.txt
new file mode 100644
index 0000000..46690a7
--- /dev/null
+++ b/requirements/tornado_legacy.txt
@@ -0,0 +1,1 @@
+tornado==5.1.1
diff --git a/requirements/tornado_new.txt b/requirements/tornado_new.txt
new file mode 100644
index 0000000..87fad5d
--- /dev/null
+++ b/requirements/tornado_new.txt
@@ -0,0 +1,1 @@
+tornado==6.0.4
diff --git a/scripts/install_tornado.sh b/scripts/install_tornado.sh
new file mode 100755
index 0000000..20d6671
--- /dev/null
+++ b/scripts/install_tornado.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+export PYTHONPATH=$PYTHONPATH:.
+
+PYTHON_VERSION=`python -c 'import sys; print(sys.version_info[0])'`
+
+if [ $PYTHON_VERSION -eq 2 ]; then
+    pip install -r requirements/tornado_legacy.txt;
+    exit;
+else
+    pip install -r requirements/tornado_new.txt;
+    exit;
+fi
+exit 0;
diff --git a/scripts/run_behave.sh b/scripts/run_behave.sh
new file mode 100755
index 0000000..faaca61
--- /dev/null
+++ b/scripts/run_behave.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export PYTHONPATH=$PYTHONPATH:.
+
+PYTHON_VERSION=`python -c 'import sys; print(sys.version_info[0])'`
+
+if [ $PYTHON_VERSION -eq 3 ]; then
+    behave tests/features; 
+    exit;
+fi
+exit 0;
diff --git a/setup.cfg b/setup.cfg
index a19bc41..be99247 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,3 @@
 [metadata]
 license-file = LICENSE
-description-file = README.rst
+description-file = README.md
diff --git a/setup.py b/setup.py
index 8319f58..a875498 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2020 Flavio Garcia
+# 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.
@@ -14,32 +14,45 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import firenado
 from setuptools import setup, find_packages
+import sys
+
 try:
     # for pip >= 10
     from pip._internal.req import parse_requirements
 except ImportError:
     # for pip <= 9.0.3
-    from pip.req import parse_requirements
-import firenado
-import os
+    print("error: Upgrade to a pip version newer than 10. Run \"pip install "
+          "--upgrade pip\".")
+    sys.exit(1)
 
 
 # Solution from http://bit.ly/29Yl8VN
 def resolve_requires(requirements_file):
-    requirements = parse_requirements("./%s" % requirements_file,
-            session=False)
-    return [str(ir.req) for ir in requirements]
+    try:
+        requirements = parse_requirements("./%s" % requirements_file,
+                                          session=False)
+        return [str(ir.req) for ir in requirements]
+    except AttributeError:
+        # for pip >= 20.1.x
+        # Need to run again as the first run was ruined by the exception
+        requirements = parse_requirements("./%s" % requirements_file,
+                                          session=False)
+        # pr stands for parsed_requirement
+        return [str(pr.requirement) for pr in requirements]
+
 
+def use_right_tornado(requirements):
+    tornado_req = "requirements/tornado_new.txt"
+    if sys.version[0] == "2":
+        tornado_req = "requirements/tornado_legacy.txt"
+    requirements.append(resolve_requires(tornado_req)[0])
+    return requirements
 
-# Utility function to read the README file.
-# Used for the long_description.  It's nice, because now 1) we have a top level
-# README file and 2) it's easier to type in the README file than to put a raw
-# string in below ...
-# Solution from: http://bit.ly/2mig8RT
-def read(fname):
-    return open(os.path.join(os.path.dirname(__file__), fname)).read()
 
+with open("README.md", "r") as fh:
+    long_description = fh.read()
 
 # We still running: python setup.py sdist upload --repository=testpypi
 # Twine isn't handling long_descriptions as per:
@@ -47,19 +60,23 @@ def read(fname):
 setup(
     name="Firenado",
     version=firenado.get_version(),
-    description="Firenado is a python web framework based on "
-                "Tornado web framework/server.",
-    long_description=read("README.rst"),
+    description="Firenado is a python web framework based on Tornado web "
+                "framework/server.",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
     license=firenado.__licence__,
     author=firenado.get_author(),
     author_email=firenado.get_author_email(),
     maintainer=firenado.get_author(),
     maintainer_email=firenado.get_author_email(),
-    install_requires=resolve_requires("requirements/basic.txt"),
+    install_requires=use_right_tornado(resolve_requires(
+        "requirements/basic.txt"
+    )),
     extras_require={
         'all': resolve_requires("requirements/all.txt"),
         'redis': resolve_requires("requirements/redis.txt"),
         'pexpect': resolve_requires("requirements/pexpect.txt"),
+        'schedule': resolve_requires("requirements/schedule.txt"),
         'sqlalchemy': resolve_requires("requirements/sqlalchemy.txt"),
     },
     url="https://github.com/candango/firenado",
@@ -74,9 +91,11 @@ def read(fname):
         "Operating System :: OS Independent",
         "Programming Language :: Python",
         "Programming Language :: Python :: 2.7",
-        "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
         "Topic :: Internet :: WWW/HTTP",
         "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
         "Topic :: Internet :: WWW/HTTP :: WSGI",


Test patch:
diff --git a/examples/testapp/app.py b/examples/testapp/app.py
index 7331828..258e8dc 100644
--- a/examples/testapp/app.py
+++ b/examples/testapp/app.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2018 Flavio Garcia
+# 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.
@@ -15,14 +15,23 @@
 # limitations under the License.
 
 from . import handlers, services, uimodules
-import firenado.tornadoweb
-from firenado import service
+from firenado import tornadoweb
+from firenado import data, service
 import logging
+from typing import Any
 
 logger = logging.getLogger(__name__)
 
 
-class TestappComponent(firenado.tornadoweb.TornadoComponent):
+class ComponentCustomErrorHandler(tornadoweb.TornadoErrorHandler):
+
+    def handle_error(self, request: tornadoweb.TornadoHandler,
+                     status_code: int, **kwargs: Any) -> None:
+        request.set_status(status_code)
+        request.render("error.html", http_error=kwargs.get('exc_info')[1])
+
+
+class TestappComponent(tornadoweb.TornadoComponent):
 
     user_service: services.LoginService
 
@@ -30,12 +39,17 @@ def __init__(self, name, application):
         super(TestappComponent, self).__init__(name, application)
         self.user_service = None
 
+    def get_error_handler(self) -> tornadoweb.TornadoErrorHandler:
+        return ComponentCustomErrorHandler(self)
+
     def get_handlers(self):
         import firenado.conf
         default_login = firenado.conf.app['login']['urls']['default']
         return [
             (r"/", handlers.IndexHandler),
             (r"/async/timeout", handlers.AsyncTimeoutHandler),
+            (r"/component/error", handlers.ComponentErrorHandler),
+            (r"/handler/error", handlers.HandlerErrorHandler),
             (r"/session/counter", handlers.SessionCounterHandler),
             (r"/session/config", handlers.SessionConfigHandler),
             (r"/pagination", handlers.PaginationHandler),
@@ -51,14 +65,20 @@ def get_ui_modules(self):
     def initialize(self):
         import firenado.conf
         firenado.conf.app['login']['urls']['buga'] = 'buga'
+        data_source_conf = {
+            'connector': "sqlalchemy",
+            'url': "mysql+pymysql://root@localhost:3306/test"}
+        data_source = data.config_to_data_source(data_source_conf,
+                                                 self.application)
+        self.application.set_data_source("test", data_source)
 
     @service.served_by(services.UserService)
     def install(self):
         """  Installing test database
         """
         from firenado.util.sqlalchemy_util import Base
-        print('Installing Diasporapy Pod...')
-        print('Creating Pod ...')
+        print('Installing Testapp App...')
+        print('Creating App ...')
         engine = self.application.get_data_source(
             'test').engine
         engine.echo = True
@@ -75,11 +95,12 @@ def install(self):
             'email': "test@test.ts"
         })
 
-    def get_data_sources(self):
-        return self.application.data_sources
+    @property
+    def data_connected(self):
+        return self.application
 
-    def after_handler(self, handler):
-        logging.info("Doing something after handler: %s" % handler)
+    def after_request(self, handler):
+        logging.info("Doing something after handler's request: %s" % handler)
 
-    def before_handler(self, handler):
-        logging.info("Doing something before handler: %s" % handler)
+    def before_request(self, handler):
+        logging.info("Doing something before handler's request: %s" % handler)
diff --git a/examples/testapp/conf/data_sources.yml b/examples/testapp/conf/data_sources.yml
new file mode 100644
index 0000000..1893de8
--- /dev/null
+++ b/examples/testapp/conf/data_sources.yml
@@ -0,0 +1,5 @@
+- name: cache
+  connector: redis
+  # host: localhost
+  # port: 6379
+  db: 1
diff --git a/examples/testapp/conf/firenado.yml b/examples/testapp/conf/firenado.yml
index ef44dce..0b6582d 100644
--- a/examples/testapp/conf/firenado.yml
+++ b/examples/testapp/conf/firenado.yml
@@ -5,7 +5,9 @@ app:
     sources:
         # Set here references from sources defined on data.sources
         - session
-        - test
+        - cache
+        # Test datasource will be loaded from the application initialize method
+        # - test
   pythonpath: ..
   login:
     urls:
@@ -31,9 +33,7 @@ data:
       # host: localhost
       # port: 6379
       # db: 0
-    - name: test
-      connector: sqlalchemy
-      url: mysql+pymysql://root:rootpass@localhost:3306/test
+    - file: data_sources.yml
 
 components:
   - id: testapp
@@ -69,7 +69,9 @@ session:
   # Scan interval by default is 2 minutes = 120 seconds
   # scan_interval: 120
   # Redis session handler configuration
+  encoder: json
   data:
     source: session
+  prefix: testapp:session
   # File session handler related configuration
   # path: /tmp
diff --git a/examples/testapp/handlers.py b/examples/testapp/handlers.py
index dfb4dd7..1b9ee22 100644
--- a/examples/testapp/handlers.py
+++ b/examples/testapp/handlers.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# 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.
@@ -14,10 +14,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from cartola.pagination import Paginator
 import firenado.conf
-from firenado import security, service, tornadogen, tornadoweb
-from firenado.components.toolbox.pagination import Paginator
-from tornado import gen
+from firenado import security, service, tornadoweb
+from firenado.util.sqlalchemy_util import base_to_dict
+import hashlib
+from tornado import escape, gen
+from tornado.web import HTTPError
+from typing import Any
 import logging
 
 logger = logging.getLogger(__name__)
@@ -33,6 +37,14 @@ def get_current_user(self):
         return None
 
 
+class HandlerCustomErrorHandler(tornadoweb.TornadoErrorHandler):
+
+    def handle_error(self, request: tornadoweb.TornadoHandler,
+                     status_code: int, **kwargs: Any) -> None:
+        request.set_status(status_code)
+        request.render("error.html", http_error=kwargs.get('exc_info')[1])
+
+
 class AsyncTimeoutHandler(tornadoweb.TornadoHandler):
 
     def timed_out(self):
@@ -42,7 +54,7 @@ def timed_out(self):
     def get(self):
         from tornado.util import TimeoutError
         try:
-            yield tornadogen.with_timeout(2, gen.sleep(5))
+            yield gen.with_timeout(2, gen.sleep(5))
             self.write("This will never be reached!!")
         except TimeoutError as te:
             logger.warning(te.__repr__())
@@ -58,6 +70,28 @@ def get(self):
                     login_url=default_login)
 
 
+class ComponentErrorHandler(tornadoweb.TornadoHandler):
+
+    def get(self):
+        raise HTTPError(400, "This is an error thrown by my handler and "
+                             "handled by the component's error handler.",
+                        reason="ComponentCustomErrorHandler is set at the "
+                               "handler's component and be used by default.")
+
+
+class HandlerErrorHandler(tornadoweb.TornadoHandler):
+
+    def get_error_handler(self) -> tornadoweb.TornadoErrorHandler:
+        return HandlerCustomErrorHandler(self)
+
+    def get(self):
+        raise HTTPError(400, "This is an error thrown by my handler and "
+                             "handled by the handler's error handler.",
+                        reason="HandlerCustomErrorHandler is set at the "
+                               "current handler overwriting the component's "
+                               "custom error handler.")
+
+
 class SessionConfigHandler(tornadoweb.TornadoHandler):
 
     def get(self):
@@ -96,7 +130,6 @@ def get(self):
     @service.served_by("testapp.services.LoginService")
     @service.served_by("testapp.services.UserService")
     def post(self):
-        from tornado.escape import json_encode
         self.session.delete('login_errors')
         default_login = firenado.conf.app['login']['urls']['default']
         username = self.get_argument('username')
@@ -112,13 +145,26 @@ def post(self):
                 errors['fail'] = "Invalid login"
             else:
                 user = self.user_service.by_username(username)
-                self.session.set("user", json_encode(user))
+                user_data = base_to_dict(user, ['id', 'username', 'password',
+                                                'first_name', 'last_name',
+                                                'email'])
+                # Shhhhh!!!! this is a secret.
+                user_data['password'] = hashlib.sha256(
+                    "_".join(user_data['password']).encode('ascii')
+                ).hexdigest()
+                self.session.set("user", escape.json_encode(user_data))
                 self.redirect(self.get_rooted_path("private"))
 
         if errors:
             self.session.set('login_errors', errors)
             self.redirect(self.get_rooted_path(default_login))
 
+    def after_request(self):
+        logging.info("Doing something after the login handler's request.")
+
+    def before_request(self):
+        logging.info("Doing something before the login handler's request.")
+
 
 class LogoutHandler(AuthHandler, tornadoweb.TornadoHandler):
 
@@ -136,7 +182,8 @@ class PrivateHandler(AuthHandler, tornadoweb.TornadoHandler):
 
     @security.authenticated
     def get(self):
-        self.render("private.html")
+        user_data = escape.json_decode(self.session.get("user"))
+        self.render("private.html", user_data=user_data)
 
 
 class PaginationHandler(tornadoweb.TornadoHandler):
diff --git a/examples/testapp/services.py b/examples/testapp/services.py
index 190e761..4446db0 100644
--- a/examples/testapp/services.py
+++ b/examples/testapp/services.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2017 Flavio Garcia
+# Copyright 2015-2020 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,9 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from firenado import service
 from .models import UserBase
 import datetime
+from firenado import service
 
 
 def password_digest(pass_phrase):
@@ -46,7 +46,7 @@ def is_valid(self, username, password):
         """
         user = self.user_service.by_username(username)
         if user:
-            if user['pass'] == password:
+            if user.password == password_digest(password):
                 return True
         return False
 
@@ -60,8 +60,7 @@ def create(self, user_data):
         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['last_name']
-
+        user.email = user_data['email']
         db_session = self.get_data_source('test').session
         db_session.add(user)
         db_session.commit()
@@ -69,9 +68,8 @@ def create(self, user_data):
         return user
 
     def by_username(self, username):
-        user = None
-        if username == "test":
-            user = {'usename': "test", 'pass': "testpass"}
+        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/templates/error.html b/examples/testapp/templates/error.html
new file mode 100644
index 0000000..f19bbbe
--- /dev/null
+++ b/examples/testapp/templates/error.html
@@ -0,0 +1,14 @@
+{% extends "testapp:base.html" %}
+
+    {% block content %}
+
+    {% module Header1("This is an error Page") %}
+    <b>Error status code: </b> {{http_error.status_code}} <br>
+    <b>Error message: </b> {{http_error.log_message}} <br>
+    <b>Reason: </b> {{http_error.reason}} <br>
+
+    <ul>
+        <li><a href="{% module RootedPath('/') %}">Index</a></li>
+    </ul>
+
+{% end %}
\ No newline at end of file
diff --git a/examples/testapp/templates/index.html b/examples/testapp/templates/index.html
index bf38132..b1e0271 100644
--- a/examples/testapp/templates/index.html
+++ b/examples/testapp/templates/index.html
@@ -30,4 +30,10 @@
     <ul>
         <li><a href="{% module RootedPath('/pagination') %}">Pagination</a></li>
     </ul>
+
+    {% module Header1("Error") %}
+    <ul>
+        <li><a href="{% module RootedPath('/component/error') %}">Component Error</a></li>
+        <li><a href="{% module RootedPath('/handler/error') %}">Handler Error</a></li>
+    </ul>
 {% end %}
diff --git a/examples/testapp/templates/private.html b/examples/testapp/templates/private.html
index 5dae295..6deaff6 100644
--- a/examples/testapp/templates/private.html
+++ b/examples/testapp/templates/private.html
@@ -3,6 +3,14 @@
 {% block content %}
     <h1>Private Page</h1>
     <p>This is a private page you can see because you're logged.</p>
+
+    <h2>User</h2>
+
+    <p><b>Username:</b> {{ user_data['username'] }}</p>
+    <p><b>First Name:</b> {{ user_data['first_name'] }}</p>
+    <p><b>Last Name:</b> {{ user_data['last_name'] }}</p>
+    <p><b>Email:</b> {{ user_data['email'] }}</p>
+
     <ul>
         <li><a href="{% module RootedPath('/') %}">Index</a></li>
     </ul>
diff --git a/requirements/tests.txt b/requirements/tests.txt
index cda6a36..985480f 100644
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -1,1 +1,2 @@
 behave==1.2.6
+bandit==1.7.0
diff --git a/tests/__init__.py b/tests/__init__.py
index 642912d..3a2524b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -29,6 +29,27 @@
         import imp
         reload = imp.reload
 
+TEST_ROOT = os.path.dirname(os.path.abspath(__file__))
+PROJECT_ROOT = os.path.abspath(os.path.join(TEST_ROOT, ".."))
+
+
+def chdir_fixture_app(app_name, **kwargs):
+    dir_name = kwargs.get("dir_name", None)
+    suppress_log = kwargs.get("suppress_log", False)
+    fixture_root = kwargs.get("fixture_root", "fixtures")
+    test_app_dirname = os.path.join(TEST_ROOT, fixture_root, app_name)
+    if dir_name is not None:
+        test_app_dirname = os.path.join(TEST_ROOT, fixture_root,
+                                        dir_name, app_name)
+    os.chdir(test_app_dirname)
+    import firenado.conf
+    if suppress_log:
+        import logging
+        for handler in logging.root.handlers[:]:
+            # clearing loggers, solution from: https://bit.ly/2yTchyx
+            logging.root.removeHandler(handler)
+    return test_app_dirname
+
 
 def chdir_app(app_name, dir_name=None):
     """ Change to the application directory located at the resource directory
diff --git a/tests/conf_test.py b/tests/conf_test.py
index c76fee8..4502ed8 100644
--- a/tests/conf_test.py
+++ b/tests/conf_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 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,9 +17,9 @@
 from __future__ import (absolute_import, division, print_function,
                         with_statement)
 
+from cartola import fs
 import unittest
 import firenado.conf
-import firenado.util.file as _file
 from tests import chdir_app
 import logging
 import os
@@ -116,7 +116,8 @@ def test_sys_stack(self):
                           firenado.conf.LIB_CONFIG_FILE)
         self.assertEqual(firenado.conf.stack[1],
                           firenado.conf.SYS_CONFIG_FILE)
-        self.assertEqual("sys_log_format", firenado.conf.log['format'])
+        self.assertEqual("%(asctime)s - %(message)s",
+                         firenado.conf.log['format'])
         self.assertEqual(logging.DEBUG, firenado.conf.log['level'])
         del os.environ['FIRENADO_SYS_CONFIG_PATH']
         reload(firenado.conf)
@@ -160,7 +161,7 @@ def test_yml_loaded(self):
         be loaded.
         """
         chdir_app("yml", "conf")
-        self.assertEqual("yml", _file.get_file_extension(
+        self.assertEqual("yml", fs.get_file_extension(
                 firenado.conf.APP_CONFIG_FILE))
 
     def test_settings_empty(self):
diff --git a/tests/features/config/application_section.feature b/tests/features/config/application_section.feature
deleted file mode 100644
index c8e014b..0000000
--- a/tests/features/config/application_section.feature
+++ /dev/null
@@ -1,8 +0,0 @@
-# Created by fgarcia at 1/31/19
-Feature: Application configuration section
-  # Enter feature description here
-
-  Scenario: # Enter scenario name here
-    # Enter steps here
-    Given: A default application configuration
-    Then: This happened
diff --git a/tests/features/config/steps/application_section.py b/tests/features/config/steps/application_section.py
deleted file mode 100644
index 47a97b5..0000000
--- a/tests/features/config/steps/application_section.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from behave import *
-
-
-@given("A default application configuration")
-def step_impl(context):
-    pass
-
-
-@then("This happened")
-def step_impl(context):
-    pass
diff --git a/tests/features/environment.py b/tests/features/environment.py
index e771a2b..f55c024 100644
--- a/tests/features/environment.py
+++ b/tests/features/environment.py
@@ -1,10 +1,35 @@
-from behave import fixture
+# -*- coding: UTF-8 -*-
+#
+# Copyright 2015-2020 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 behave import fixture, use_fixture
 from firenado import launcher
+from unittest.case import TestCase
 
 
 @fixture
 def firenado_app(context, timeout=30, **kwargs):
-
     context.launcher = launcher.FirenadoLauncher()
-    print(context.launcher)
     yield context.launcher
+
+
+@fixture
+def tester(context, timeout=1, **kwargs):
+    context.tester = TestCase()
+    yield context.tester
+
+
+def before_all(context):
+    use_fixture(tester, context)
diff --git a/tests/features/launcher.feature b/tests/features/launcher.feature
new file mode 100644
index 0000000..8cfcb57
--- /dev/null
+++ b/tests/features/launcher.feature
@@ -0,0 +1,11 @@
+# Created by Flavio Garcia at 01/17/2020
+Feature: Launcher
+  # A Firenado application is started by a launcher. Here we tests important
+  # scenarios related to launchers.
+
+  Scenario: Start and stop a Process launcher
+    # Enter steps here
+    Given We launch launcherapp application using process launcher at 128393 port
+    When The application is running correctly at 128393 port
+    Then We shutdown the process launcher
+      And The application stops successfully
diff --git a/tests/features/steps/__init__.py b/tests/features/steps/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/features/steps/launcher.py b/tests/features/steps/launcher.py
new file mode 100644
index 0000000..3c1a30f
--- /dev/null
+++ b/tests/features/steps/launcher.py
@@ -0,0 +1,63 @@
+# -*- coding: UTF-8 -*-
+#
+# Copyright 2015-2020 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 behave import given, when, then
+from behave.api.async_step import async_run_until_complete
+from tests import PROJECT_ROOT, chdir_fixture_app
+from firenado.launcher import ProcessLauncher
+from tornado import gen
+from tornado.httpclient import AsyncHTTPClient
+import sys
+
+
+@given("We launch {application} application using process launcher at {port} "
+       "port")
+@async_run_until_complete
+async def step_we_launch_application_using_process_launcher(context,
+                                                            application, port):
+    application_dir = chdir_fixture_app(application, suppress_log=True)
+    context.launcher = ProcessLauncher(
+        dir=application_dir, path=PROJECT_ROOT, port=port, logfile=sys.stderr)
+    context.launcher.load()
+    await context.launcher.launch()
+    await gen.sleep(1)
+    context.tester.assertTrue(context.launcher.is_alive())
+
+
+@when("The application is running correctly at {port} port")
+@async_run_until_complete
+async def step_we_launch_application_using_process_launcher(context, port):
+    http_client = AsyncHTTPClient()
+    try:
+        response = await http_client.fetch("http://localhost:%s" %
+                                           port)
+    except Exception as e:
+        print("Error: %s" % e)
+        context.tester.assertTrue(False)
+    else:
+        context.tester.assertEquals(b"IndexHandler output", response.body)
+        context.tester.assertTrue(context.launcher.is_alive())
+
+
+@then("We shutdown the process launcher")
+def step_we_shutdown_the_process_launcher(context):
+    context.tester.assertTrue(context.launcher.is_alive())
+    context.launcher.shutdown()
+
+
+@then("The application stops successfully")
+def step_the_application_stops_successfully(context):
+    context.tester.assertFalse(context.launcher.is_alive())
diff --git a/tests/fixtures/launcherapp/__init__.py b/tests/fixtures/launcherapp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/fixtures/launcherapp/app.py b/tests/fixtures/launcherapp/app.py
new file mode 100644
index 0000000..079ad50
--- /dev/null
+++ b/tests/fixtures/launcherapp/app.py
@@ -0,0 +1,10 @@
+from . import handlers
+from firenado import tornadoweb
+
+
+class LauncherappComponent(tornadoweb.TornadoComponent):
+
+    def get_handlers(self):
+        return [
+            (r'/', handlers.IndexHandler),
+        ]
diff --git a/tests/fixtures/launcherapp/conf/firenado.yml b/tests/fixtures/launcherapp/conf/firenado.yml
new file mode 100644
index 0000000..26c3cb6
--- /dev/null
+++ b/tests/fixtures/launcherapp/conf/firenado.yml
@@ -0,0 +1,35 @@
+app:
+  component: 'launcherapp'
+  pythonpath: '..'
+  #type: 'tornado'
+  port: 8888
+
+data:
+  sources:
+    - name: session
+      connector: redis
+      # host: localhost
+      # port: 6379
+      # db: 0
+components:
+  - id: launcherapp
+    class: launcherapp.app.LauncherappComponent
+    enabled: true
+  #- id: admin
+  #  enabled: true
+  #- id: info
+  #  enabled: true
+
+log:
+  level: DEBUG
+
+# Session types could be:
+# file or redis.
+session:
+  type: redis
+  enabled: false
+  # Redis session handler configuration
+  data:
+    source: session
+  # File session handler related configuration
+  # path: /tmp
diff --git a/tests/fixtures/launcherapp/handlers.py b/tests/fixtures/launcherapp/handlers.py
new file mode 100644
index 0000000..0d6ffc4
--- /dev/null
+++ b/tests/fixtures/launcherapp/handlers.py
@@ -0,0 +1,7 @@
+from firenado import tornadoweb
+
+
+class IndexHandler(tornadoweb.TornadoHandler):
+
+    def get(self):
+        self.write("IndexHandler output")
diff --git a/tests/resources/conf/sys_config/firenado.yml b/tests/resources/conf/sys_config/firenado.yml
index db8322d..90d487a 100644
--- a/tests/resources/conf/sys_config/firenado.yml
+++ b/tests/resources/conf/sys_config/firenado.yml
@@ -1,3 +1,3 @@
 log:
-  format: 'sys_log_format'
+  format: '%(asctime)s - %(message)s'
   level: DEBUG
diff --git a/tests/runtests.py b/tests/runtests.py
index ed6679b..24c9cb1 100644
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
 import unittest
 from tests import (components_test, conf_test, config_test, security_test,
                    service_test, session_test, tornadoweb_test)
-from tests.util import file_test, url_util_test
+from tests.util import sqlalchemy_util_test, url_util_test
 
 
 def suite():
@@ -33,7 +33,7 @@ def suite():
     alltests.addTests(testLoader.loadTestsFromModule(service_test))
     alltests.addTests(testLoader.loadTestsFromModule(session_test))
     alltests.addTests(testLoader.loadTestsFromModule(tornadoweb_test))
-    alltests.addTests(testLoader.loadTestsFromModule(file_test))
+    alltests.addTests(testLoader.loadTestsFromModule(sqlalchemy_util_test))
     alltests.addTests(testLoader.loadTestsFromModule(url_util_test))
     return alltests
 
diff --git a/tests/session_test.py b/tests/session_test.py
index 3385c04..5591c12 100644
--- a/tests/session_test.py
+++ b/tests/session_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 Flavio Garcia
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -54,10 +54,10 @@ def test_application_session_handler(self):
         """ Checks if the session handler loaded is the same the session
         handler defined.
         """
-        app_session_handler_class = \
-            self.application.session_engine.session_handler.__class__
         self.assertEqual(
-            app_session_handler_class, self.session_handler_class)
+            self.application.session_engine.session_handler.__class__,
+            self.session_handler_class
+        )
 
     def test_session_type_file(self):
         """ Checks if test component was loaded correctly by the application
@@ -67,9 +67,8 @@ def test_session_type_file(self):
         session_handler_config = firenado.conf.session[
             'handlers'][firenado.conf.session['type']]
         session_handler_class = get_class_from_config(session_handler_config)
-        app_session_handler_class = \
-            application.session_engine.session_handler.__class__
-        self.assertEqual(app_session_handler_class, session_handler_class)
+        self.assertEqual(application.session_engine.session_handler.__class__,
+                         session_handler_class)
 
 
 class RedisSessionTestCase(unittest.TestCase):
@@ -97,6 +96,9 @@ def test_custom_session_parameters(self):
         self.assertEqual(firenado.conf.session['life_time'], 1900)
         self.assertEqual(firenado.conf.session['callback_time'], 40)
 
+
+class EncodersSessionTestCase(unittest.TestCase):
+
     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.
@@ -113,4 +115,21 @@ def test_pickle_session_encoder(self):
         decoded_data = encoder.decode(encoded_data)
         self.assertEqual(decoded_data['value1'], my_dict['value1'])
         self.assertEqual(decoded_data['value2']['value3'],
-                          my_dict['value2']['value3'])
+                         my_dict['value2']['value3'])
+
+    def test_json_session_encoder(self):
+        """Checks if json session encoder will keep a dict structure and values
+        intact after encoding and decoding it."""
+        from firenado.session import JsonSessionEncoder
+        my_dict = {
+            'value1': "My value1",
+            'value2': {
+                'value3': "My value3",
+            }
+        }
+        encoder = JsonSessionEncoder()
+        encoded_data = encoder.encode(my_dict)
+        decoded_data = encoder.decode(encoded_data)
+        self.assertEqual(decoded_data['value1'], my_dict['value1'])
+        self.assertEqual(decoded_data['value2']['value3'],
+                         my_dict['value2']['value3'])
diff --git a/tests/tornadoweb_test.py b/tests/tornadoweb_test.py
index b487fe1..873ab88 100644
--- a/tests/tornadoweb_test.py
+++ b/tests/tornadoweb_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright 2015-2019 Flavio Garcia
+# Copyright 2015-2020 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,8 +18,7 @@
                         with_statement)
 
 import firenado.conf
-from firenado.tornadoweb import TornadoApplication
-from firenado.tornadoweb import TornadoHandler
+from firenado.tornadoweb import get_request, TornadoApplication, TornadoHandler
 from firenado.launcher import FirenadoLauncher, TornadoLauncher
 from firenado.tornadoweb import TornadoComponent
 import unittest
@@ -96,6 +95,36 @@ def test_static_path(self):
         self.assertEqual(firenado.conf.app['static_path'], static_path_x[-1])
 
 
+class GetRequestTestCase(unittest.TestCase):
+
+    def test_get_request_simple(self):
+        expected = "http://test"
+        request = get_request(expected)
+        self.assertEqual(expected, request.url)
+        self.assertEqual(0, len(request.headers))
+        self.assertEqual("GET", request.method)
+
+    def test_get_request_with_path(self):
+        expected = "http://test/a_path/informed"
+        base_url = "http://test"
+        request = get_request(url=base_url, path="a_path/informed")
+        self.assertEqual(expected, request.url)
+        base_url = "http://test/"
+        request = get_request(url=base_url, path="a_path/informed")
+        self.assertEqual(expected, request.url)
+
+    def test_get_request_with_method(self):
+        method = "POST"
+        request = get_request(url="http://test", method=method)
+        self.assertEqual(method, request.method)
+
+    def test_get_request_with_form_urlencoded(self):
+        request = get_request(url="http://test", form_urlencoded=True)
+        self.assertEqual(1, len(request.headers))
+        self.assertEqual("application/x-www-form-urlencoded",
+                         request.headers['Content-Type'])
+
+
 class TornadoHandlerTestCase(unittest.TestCase):
     """ TornadoHandler tests
     """
diff --git a/tests/util/file_test.py b/tests/util/file_test.py
deleted file mode 100644
index 28416bb..0000000
--- a/tests/util/file_test.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2015-2019 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 __future__ import (absolute_import, division, print_function,
-                        with_statement)
-
-
-import unittest
-from firenado.util.file import file_has_extension, get_file_extension
-
-
-class FileTestCase(unittest.TestCase):
-    """ Case that tests the file util functions.
-    """
-
-    def test_extension_file_with_extension(self):
-        """ Happy scenario, a filename with an extension
-        """
-        filename = 'the_file.ext'
-        expected_extension = 'ext'
-        extension = get_file_extension(filename)
-        self.assertEqual(extension, expected_extension)
-        self.assertTrue(file_has_extension(filename))
-
-    def test_extension_file_ending_with_dot(self):
-        """ Filename ending with a dot
-        """
-        filename = 'the_file.'
-        extension = get_file_extension(filename)
-        self.assertIsNone(extension)
-        self.assertFalse(file_has_extension(filename))
-
-    def test_extension_file_no_extension(self):
-        """ Filename without extension
-        """
-        filename = 'the_file'
-        extension = get_file_extension(filename)
-        self.assertIsNone(extension)
-        self.assertFalse(file_has_extension(filename))
diff --git a/tests/util/sqlalchemy_util_test.py b/tests/util/sqlalchemy_util_test.py
new file mode 100644
index 0000000..4764eb2
--- /dev/null
+++ b/tests/util/sqlalchemy_util_test.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2020 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 __future__ import (absolute_import, division, print_function,
+                        with_statement)
+
+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(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)