Skip to content

Commit

Permalink
Merge pull request #388 from piraz/develop
Browse files Browse the repository at this point in the history
Implemented sessioned decorator for services.
  • Loading branch information
piraz committed Jan 30, 2022
2 parents af9c5f9 + 1052463 commit c63b537
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 22 deletions.
8 changes: 6 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
extensions = ["myst_parser"]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
source_suffix = {
'.rst': 'restructuredtext',
'.txt': 'markdown',
'.md': 'markdown',
}

# The encoding of source files.
#source_encoding = 'utf-8-sig'
Expand Down
46 changes: 46 additions & 0 deletions docs/guide/services.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Services
========

The way firenado organizes the logic to be executed in several parts of an
application is defining services.

Those services can be injected with the decorator
``firenado.service.served_by``. This decorator will add an instance of a
service to a method of any data connected object. Examples of data connected
classes are ``firenado.tornadoweb.TornadoHandler`` and any descendent of
``firenado.service.FirenadoService``.

Creating a service and decorating a handler:

.. code-block::python
from firenado import service, tornadoweb
# Importing a package with some services
import another_service_package
class MyService(service.FirenadoService):
def do_something(self):
# Self consumer will be the handler where this service was
# called from.
self.consumer.write("Something was done")
class MyHandlerBeingServed(tornadoweb.TornadoHandler):
# A good way to keep the reference is keeping the type hint
my_service: MyService
service_from_another_package: another_service_package.AnotherService
@service.served_by(MyService)
# you can also set the attribute/property name to be used
@service.served_by(another_service_package.AnotherService,
attribute_name="service_from_another_package"
)
def get(self):
# The anotation service.served_by added self.my_service
# here. The attribute/property name will be converted from the
# cammel cased class to dashed separated.
self.my_service.do_something()
self.service_from_another_package.do_another_thing()
You can also add services to another services using the decorator:
26 changes: 24 additions & 2 deletions firenado/schedule.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2021 Flavio Garcia
# Copyright 2015-2022 Flávio Gonçalves Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -71,6 +71,14 @@ def __init__(self, component, **kwargs):
self.component = component
self._periodic_callback = None

@property
def app_component(self) -> TornadoComponent:
""" Shortcut to self.component.app_component
:return: TornadoComponent
"""
return self.component.app_component

@property
def id(self):
return self._id
Expand All @@ -84,7 +92,9 @@ def name(self):
return self._name

@property
def can_run(self):
def can_run(self) -> bool:
""" Indicates if the scheduler can run
"""
return self._can_run

def add_job(self, job):
Expand Down Expand Up @@ -239,6 +249,18 @@ def __init__(self, scheduler, **kwargs):
self._interval = kwargs.get('interval')
self._periodic_callback = None

@property
def component(self) -> "ScheduledTornadoComponent":
""" Return scheduler's component
"""
return self._scheduler.component

@property
def app_component(self) -> TornadoComponent:
""" Shortcut to self.component.app_component
"""
return self.component.app_component

@property
def id(self):
return self._id
Expand Down
61 changes: 60 additions & 1 deletion firenado/service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2021 Flavio Garcia
# Copyright 2015-2022 Flávio Gonçalves Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,10 @@

import functools
import importlib
import inspect
import logging

logger = logging.getLogger(__name__)


class FirenadoService(object):
Expand Down Expand Up @@ -114,3 +118,58 @@ def wrapper(self, *args, **kwargs):

return wrapper
return f_wrapper


def sessionned(*args, **kwargs):
""" This decorator will add an existing session to the method being
decorated or create a new session to be used by the method."""
service = None
if len(args) > 0:
service = args[0]

def method_wrapper(method):
@functools.wraps(method)
def wrapper(self, *method_args, **method_kwargs):
session = method_kwargs.get("session")
close = kwargs.get("close", False)
close = method_kwargs.get("close", close)
if not session:
data_source = kwargs.get("data_source")
data_source = method_kwargs.get("data_source", data_source)
if not data_source:
hasattr(self, "default_data_source")
if hasattr(self, "default_data_source"):
if inspect.ismethod(self.default_data_source):
data_source = self.default_data_source()
else:
data_source = self.default_data_source
try:
if isinstance(data_source, str):
ds = self.get_data_source(data_source)
method_kwargs['data_source'] = data_source
session = ds.session
else:
print(data_source)
session = data_source.session
method_kwargs['session'] = session
except KeyError:
logger.exception("There is no datasource defined with"
"index \"%s\" related to the service." %
data_source)
result = method(self, *method_args, **method_kwargs)
if close:
if not session:
logger.warning("No session was resolved.")
logger.debug("Closing session %s." % session)
session.close()
return result
return wrapper
# If the decorator has no parameter, I mean no parentesis, we need to wrap
# the service variable again , instead of the service instance, we need to
# deal with the method being decorated but as a <function> instace.
if inspect.isfunction(service):
@functools.wraps(None)
def func_wrapper(_function):
return method_wrapper(_function)
return func_wrapper(service)
return method_wrapper
7 changes: 6 additions & 1 deletion firenado/tornadoweb.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ def __init__(self, default_host="", transforms=None, **settings):
else:
logger.debug("Session is disabled.")

def get_app_component(self):
def get_app_component(self) -> "TornadoComponent":
""" Return the component set as the application component at the
app config.
:return: TornadoComponent
"""
return self.components[firenado.conf.app['component']]

def __load_components(self):
Expand Down

0 comments on commit c63b537

Please sign in to comment.