Package: shelter
Shelter is aPython’s Tornadowrapper which provides classes and helpers
for creation new application skeleton, writing management commands (like aDjango), service processes and request handlers. It was tested withPython 2.7andPython 3.4or higher andTornado 3.2or higher.Instalationcd shelter/
python setup.py installAfter instalationshelter-admincommand is available. For help type:shelter-admin -hThe most important argument is-s/--settings, which joins Shelter library
and your application. Format is Python’s module path, eg.myapp.settings.
Second option how to handlesettingsmodule isSHELTER_SETTINGS_MODULEenvironment variable. If both are handled, command line argument has higher
priority than environment variable.shelter-admin -s myapp.settingsorSHELTER_SETTINGS_MODULE=myapp.settings shelter-adminUsageshelter-admin startproject myprojectSkeleton of the new application will be created in current working directory.
Project name has the same rules as Python’s module name. Entry point into new
application is a scriptmanage.py.cd myproject/
./manage.py -h
./manage.py devserversettings.pyis included in the new skeleton. See comments in file how to
define interfaces, management commands, service processes, …List of the management commands which provides Shelter library:runserverruns production HTTP, multi-process server. Number of the
processes are detected according toINTERFACESsetting in thesettingsmodule. Service processes are run in separated processes.
Parent process checks child processes and when child process exits (due to
crash or signal), it is run again. Crashes are counted, maximum amount of
the crashes are 100, after it whole application will be exited. If child
stops with exit code 0, crash counter is not incremented, so you can exit
the worker deliberately and it will be run again.devserverruns development HTTP server, which autoreloads application
when source files are changes. Server is run only in one process.shellruns interactive Python’s shell. First it tries to runIPython,
then standardPythonshell.showconfigshows effective configuration.startprojectwill generate new apllication skeleton.Pay attention, service processes are run only inrunservercommand!Config classLibrary provides base configuration classshelter.core.config.Configwhich holds all configuration. Public attributes aresettingswhich
issettingsmodule of the application andargs_parserwhich is
instance of theargparse.ArgumentParserfromPython’s standard library.You can override this class in thesettingsmodule:CONFIG_CLASS = 'myapp.core.config.AppConfig'Your ownAppConfigclass can contain additionalpropertieswith
application’s settings, e.g. database connection arguments. Way how the value
is found is only on you - either only insettingsorargs_parseror
in both. You can define additional arguments of the command line.import time

from shelter.core.config import Config, argument

class AppConfig(Config):

    arguments = (
        argument(
            '-k', '--secret-key',
            dest='secret_key', action='store',
            type=str, default='',
            help='configuration file'
        ),
    )

    Database = collections.namedtuple('Database', ['host', 'db'])

    def initialize(self):
        # initialize() is called after instance is created. If you want
        # add some instance attributes, use this method instead of
        # override __init__().
        self._server_started_time = time.time()

    def get_config_items(self):
        # Override get_config_items() method if you want to add
        # your options into showconfig management command.
        base_items = super(AppConfig, self).get_config_items()
        app_items = (
            ('secret_key', self.secret_key),
            ('database', self.database),
        )
        return base_items + app_items

    @property
    def secret_key(self):
        # If command-line argument -k/--secret-key exists, it will
        # override settings.SECRET_KEY value.
        return self.args_parser.secret_key or self.settings.SECRET_KEY

    @property
    def database(self):
        return self.Database(
            db=self.settings.DATABASE_NAME,
            host=self.settings.DATABASE_HOST,
            passwd=getattr(self.settings, DATABASE_PASSWORD, '')
        )Context classIn all handlers, management commands and service processes is available
instance of theshelter.core.context.Contextwhich holds resources for
your appllication. Bundled classContextcontains only one propertyconfigwithConfiginstance (see previous chapter).You can define own class insettingsmodule:CONTEXT_CLASS = 'myapp.core.context.Context'OverridedContextcan contain additionalproperties, e.g. database
connection pool.It is necesary to initialize shared resources (sockets, open files, …)
lazy!The reason is that subprocesses (Tornado HTTP workers, service
processes) have to get uninitializedContext, because forked resources
can cause a lot of nights without dreams…Also it is necessary to known
that Context is shared among coroutines!So you are responsible for
locking shared resources (be careful, it is blocking operation) or use
another mechanism, e.g. database connection pool.Contextclass contains two methods,initialize()andinitialize_child().initialize()is called from constructor during instance is initialized.
So it is the best place where you can initialize attributes which can be
shared among processes.initialize_child()is called when service processes or Tornado workers
are initialized. So it is the best place where you can safely initialize
shared resources like a database connection.process_typeargument contains
type of the child –shelter.core.constants.SERVICE_PROCESSorshelter.core.constants.TORNADO_WORKER.kwargscontains additional data
according toprocess_type:forSERVICE_PROCESScontainsprocesskey which is instance of the
service process.forTORNADO_WORKERcontainsprocesskey which is instance of the
HTTP worker.class Context(shelter.core.context.Context):

    def initialize(self):
        self._database = None

    def initialize_child(self, process_type, **kwargs):
        # Initialize database in the subprocesses when child is created
        self._init_database(max_connections=10)

    def _init_database(self, max_connections):
        self._database = ConnectionPool(
            self.config.database.host,
            self.config.database.db,
            max_connections=max_connections,
            connect_on_init=True)

    @property
    def database(self):
        # Lazy property if you need database connection in
        # the main process (e.g. management command)
        if self._database is None:
            self._init_database(max_connections=1)
        return self._databaseHooksYou can define several hooks in thesettingsmodule - when application
is launched, onSIGUSR1andSIGUSR2signals and when instance of the
Tornado application is created.APP_SETTINGS_HANDLER = 'myapp.core.app.get_app_settings'
INIT_HANDLER = 'myapp.core.app.init_handler'
SIGUSR1_HANDLER = 'myapp.core.app.sigusr1_handler'
SIGUSR2_HANDLER = 'myapp.core.app.sigusr2_handler'INIT_HANDLERis allowed to contain multiple values.INIT_HANDLER = [
    'myapp.core.app.init_handler1', 'myapp.core.app.init_handler2']Handler is commonPython’sfunction which takes only one argumentcontextwithContextinstance (see previous chapter).def init_handler(context):
    do_something(context.config)INIT_HANDLERis called during the application starts, before workers
or service processes are run.SIGUSR1_HANDLERis called onSIGUSR1signal. When signal receives
worker/child process, it is processed only in this process. When signal
receives main/parent process, signal is propagated into all workers.SIGUSR2_HANDLERis called onSIGUSR2signal. Signal is processed
only in process which received signal. It is not propagated anywhere.APP_SETTINGS_HANDLERis called on when instance of the Tornado
application is created. Function have to returndict, which is passed as**settingsargument intotornado.web.Applicationconstructor. Do not
passdebug,contextandinterfacekeys.Service processesService process are tasks which are repeatedly launched in adjusted interval,
e.g. warms cache data before they expire. Library provides base classshelter.core.process.BaseProcess. For new service process
you must inheritBaseProcess, adjustintervalattribute and overrideloop()method.from shelter.core.processes import BaseProcess

class WarmCache(BaseProcess)

    interval = 30.0

    def initialize(self):
        self.db_conn = self.context.db.conn_pool
        self.cache = self.context.cache

    def loop(self):
        self.logger.info("Warn cached data")
        with self.db_conn.get() as db:
            self.cache.set('key', db.get_data(), timeout=60)intervalis a time in seconds. After this timeloop()method is
repeatedly called.Service process has to be registered in thesettingsmodule.SERVICE_PROCESSES = (
    ('myapp.processes.WarmCache', True, 15.0),
)Each service process definition is list/tuple in format('path.to.ClassName', wait_unless_ready, timeout). Ifwait_unless_readyisTrue, wait maximumtimeoutseconds unless process is successfully
started, otherwise raiseshelter.core.exceptions.ProcessErrorexception.Management commandsClassshelter.core.commands.BaseCommandis an ancestor for user
defined managemend commands, e.g. export/import database data. For new
management command you must inheritBaseCommandand overridecommand()method and/orinitialize()method.import sys

from gettext import gettext as _

from shelter.core.commands import BaseCommand, argument

class Export(BaseCommand)

    name = 'export'
    help = 'export data from database'
    arguments = (
        argument(
            '-o', dest=output_file, type=str, default='-',
            help=_('output filename')),
    )

    def initialize(self):
        filename = self.conntext.config.args_parser.output_file
        if filename == '-':
            self.output_file = sys.stdout
        else:
            self.output_file = open(filename, 'wt')

    def command(self):
        self.logger.info("Exporting data")
        with self.context.db.get_connection_from_pool() as db:
            data = db.get_data()
        self.output_file.write(data)
        self.output_file.flush()nameis a name of the management command. This name is used from command
line, e.g../manage.py export.helpis a short description of the management command. This help is
printed onto console when you type./manage.py command-h.argumentsare arguments of the command line parser.argument()function has the same meaning asArgumentParser.add_argument()fromPython’s standard library.settings_requiredIf it isFalse,settingsmodule will not be
required for command. However, only internalshelter.core.config.Configandshelter.core.context.Contextwill be available, not your own defined
in settings. For example, internalstartprocestcommand sets this flag
toFalse.It is not public API, do not use this attribute unless you
really know what you are doing!Management command has to be registered in thesettingsmodule.MANAGEMENT_COMMANDS = (
    'myapp.commands.Export',
)InterfacesTornado’s HTTP servercan be run in multiple instances. Interface are
defined insettingsmodule. Interfaces can be set as either TCP/IP sockets
(LISTENdirective) or unix sockets (UNIX_SOCKETdirective) or both.INTERFACES = {
    'default': {
        # IP/hostname (not required) and port where the interface
        # listens to requests
        'LISTEN': ':8000',

        # Path to desired unix socket
        'UNIX_SOCKET': '/run/myapp.sock',

        # Amount of the server processes if application is run
        # using runserver command. Positive integer, 0 will
        # detect amount of the CPUs
        'PROCESSES': 0,

        # Path in format 'path.to.module.variable_name' where
        # urls patterns are defined
        'URLS': 'myapp.urls.default_urls',

        # Path in format 'path.to.module.variable_name' to class
        # of the Tornado's application. If not specified, default
        # tornado.web.Application is used.
        'APP_CLASS': 'myapp.core.app.TornadoApplication',

        # Maximum amount of seconds unless process is
        # successfully started, otherwise raise
        # shelter.core.exceptions.ProcessError exception.
        'START_TIMEOUT': 5.0,
    },
}URL path to HTTP handler routingIt is the same as inPython’s Tornadoapplication.from tornado.web import URLSpec

from myapp.handlers import HomepageHandler, AboutHandler

urls_default = (
    URLSpec(r'/', HomepageHandler),
    URLSpec(r'/about/', AboutHandler),
)Tuple/listurls_defaultis handled into relevant interface in thesettingsmodule, see previous chapter.HTTP handler is a subclass of theshelter.core.web.BaseRequestHandlerwhich enhancestornado.web.RequestHandler. Provides additional instance
attributes/propertieslogger,contextandinterface.loggeris an instance of thelogging.LoggerfromPython’s standard
library. Logger name is derived from handlers’s name, e.gmyapp.handlers.HomepageHandler.contextis an instance of theContext, seeContextparagraph.interfaceis a namedtuple with informations about current interface.
Named attributes arename,host,port,processesandurls.from shelter.core.web import BaseRequestHandler

class DummyHandler(BaseRequestHandler):

    def get(self):
        self.write(
            "Interface '%s' works!\n" % self.interface.name)
        self.set_header(
            "Content-Type", 'text/plain; charset=UTF-8')LoggingStandardPython’s loggingis used.Config.configure_logging()method
is responsible for setting the logging. DefaultConfigclass reads
logging’s configuration fromsettingsmodule:LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'default',
        },
    },
    'root': {
        'handlers': ['console'],
        'level': 'INFO',
    },
}Contribshelter.contrib.config.iniconfig.IniConfigDescendant of theshelter.core.config.Config, providesINIfiles
configuration. Adds additional public attributeconfig_parserwhich is
instance of theRawConfigParserfromPython’s standard library.
Interfaces and application’s name can be overrided in configuration file,Python’s loggingmust be defined.Configuration file is specified either bySHELTER_CONFIG_FILENAMEenvironment variable or-f/--config-filecommand line argument. First,
main configuration file is read. Then all configuration files fromfile.conf.dsubdirectory are read in alphabetical order. E.g. if-fconf/myapp.confis handled, firstconf/myapp.conffile is read
and then allconf/myapp.conf.d/*.conffiles. Value in later
configuration file overrides previous defined value.[application]
name = MyApp

[interface_http]
Listen=:4444
Processes=8
Urls=tests.urls1.urls_http
AppClass=myapp.core.app.TornadoApplication

[formatters]
keys=default

[formatter_default]
class=logging.Formatter
format=%(asctime)s %(name)s %(levelname)s: %(message)s

[handlers]
keys=console

[handler_console]
class=logging.StreamHandler
args=()
level=NOTSET

[loggers]
keys=root

[logger_root]
level=INFO
handlers=consoleshelter.contrib.swagger.SwaggerApplicationRequires Python 3.6 or newer.Extendstornado.web.Applicationwith support of Swagger API documentation.Usage:Install shelter with swagger extras:shelter[swagger]Refer theAPP_CLASSin interface settings to a descendant class ofshelter.contrib.swagger.SwaggerApplicationProvide your handler methods with OpenApi docstring, eg.def get(self):
    """Returns current value from internal context
    ---
    tags: [value]
    summary: Get current value
    description: Get the current value from internal context

    responses:
        200:
            description: Message with the current value
            content:
                text/plain:
                    schema:
                        type: string
    """
    self.write("Current value: %s" % self.context.value)License3-clause BSD