Real World template project

Presents a template of a real Python project and shows features of the summer framework.

Requirements:

  • SQLite – uses in memory db available with no special config.
  • LDAP – uses simple ldap db holding user accounts.

SQL database is simple and uses just two entities with m:n relationship between Category <–> Item.

LDAP support can be turned off, if not, you will need to set up your own ldap server, see ldap/sample.local.sh for details.

As well as with Basic Application Context, there are some obvious configuration files:

  • standard Python logging config logging.cfg
  • summer framework config summer.cfg
  • custom application config custom.cfg

Project is now organized with one single package, so please look at:

  • tmplprojecttest.py
  • tmplproject/main.py
  • tmplproject/appcontext.py

summer.cfg

# Time-stamp: < summer.cfg (2016-01-24 23:39) >

# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

[DEFAULT]

project_name    = tmplproject
project_top_dir = .

[SQLALCHEMY]

#uri sqlite:///:memory: (or, sqlite://)
#uri sqlite:///relative/path/to/file.db
#uri sqlite:////absolute/path/to/file.db
#uri        = sqlite:///%(project_top_dir)s/file.sqlite
uri        = sqlite:///:memory:
echo       = no
autoflush  = yes
autocommit = no

[L10N]

domain    = %(project_name)s
l10n_dir  = %(project_top_dir)s/l10n
languages = cs,en

[LDAP]

hostname = localhost
port     = 389
base     = dc=sample,dc=local
login    = cn=admin,dc=sample,dc=local
passwd   = secret

tmplprojecttest.py

# -*- coding: utf-8 -*-
# Time-stamp: < tmplprojecttest.py (2016-02-08 19:48) >

# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Relatively advanced standalone example.

Check the sample config files in this directory.  Look at the single
:py:module:`tmplproject` module.

"""

import logging
import logging.config
import os
import os.path
import sys

from unittest import TestCase

# configure logging as soon as possible
LOGGING_CFG = os.path.join(os.path.dirname(__file__), "logging.cfg")
logging.config.fileConfig(LOGGING_CFG)
logger = logging.getLogger(__name__)
logger.info("logging config = %s", LOGGING_CFG)

from .tmplproject import main


class TmplProjectTest(TestCase):

    """Demonstrates various summer features in *real-world* project."""

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_tmplproject(self):
        logger.info("#### TEST STARTS HERE ####")
        main.main(sys.argv[1:])
        logger.info("#### TEST ENDS HERE ####")

tmplproject/main.py

# -*- coding: utf-8 -*-
# Time-stamp: < main.py (2016-02-08 19:49) >

# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Main entry point to the program."""

import logging

# configure logging as soon as possible
logger = logging.getLogger(__name__)

from summer import Filter
from summer.ipythonutils import run_ipshell

from .appcontext import ApplicationContext
from .manager import Category
from .cmdline import SHELL


def main(args):
    """Entry point to our program."""
    ctx = ApplicationContext(__file__, "tmplproject.cfg")
    logger.info("sample started")
    (options, args) = ctx.my_option_parser.parse_args(args)
    logger.info("args: %s", args)
    logger.info("options: %s", options)
    # create the sample database
    ctx.session_factory.create_schema()
    # do the specified action
    process_args(args, options, ctx)
    logger.info("sample finished")


def process_args(args, options, ctx):
    """Process the command line and takes the proper action."""
    if len(args) > 0:
        command = args[0]
        logger.info("running command '%s'", command)
        if command == SHELL:
            run_ipshell("Embedded IPython Shell Banner", {"ctx": ctx})
        else:
            ctx.my_option_parser.print_help()
    else:  # take the default action
        do_demonstration(ctx, options.use_ldap)


def do_demonstration(ctx, use_ldap):
    mark = "PROGRAM OUTPUT>"

    # l10n
    logger.info("%s test localization -- %s", mark, _("localized message"))

    # db
    # we need a proxied version cause we want database transactions
    logger.info("let's create some objects and persist them")
    category_dao = ctx.category_dao_proxy
    for i in range(1, 16):
        cat = Category()
        cat.order = i
        cat.code = "code_%02d" % (cat.order,)
        category_dao.save(cat)

    # we go through result set using paging
    logger.info(
        "let's iterate using db paging through what we have just persisted")
    cat_filter = Filter(1, 5)
    for page in range(1, 4):
        cat_filter.page = page
        logger.info("%s page %d", mark, cat_filter.page)
        for cat in category_dao.find(cat_filter):
            logger.info("%s %s", mark, cat)

    # ldap
    if use_ldap:
        logger.info("let's use LDAP demo query for arbitrary objects" +
                    " (not all of them, just those with ou=users)")
        user_manager = ctx.user_manager_proxy
        for user in user_manager.find():
            logger.info("%s %s", mark, user)

tmplproject/appcontext.py

# -*- coding: utf-8 -*-
# Time-stamp: < appcontext.py (2016-02-12 08:13) >

# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""
Application context definition.
"""

import logging

# configure logging as soon as possible
logger = logging.getLogger(__name__)

import summer

# do all the necessary imports, all that is imported bellow is deployed
# into the context, just as an example -- you can deploy anything you want
# of course
from .cmdline import MyOptionParser
from .orm.tables import TableDefinitions
from .orm.mappings import ClassMappings
from .manager import (
    CategoryDao, CategoryManager,
    ItemDao, ItemManager,
    UserDao, UserManager,
)


class ApplicationContext(summer.Context):

    def __init__(self, path_to_module, customcfg):
        summer.Context.__init__(self, path_to_module, customcfg)

    def orm_init(self):
        # you can do whatever setup you want, here you can see so-called
        # "classical" mapping, see SQLAlchemy for details

        # first let's complete database initialization with custom table
        # definitions and mappings
        self.session_factory.set_table_definitions(TableDefinitions())
        # class mappings must be defined _after_ table definitions
        self.session_factory.set_class_mappings(ClassMappings())

    def context_init(self):

        # create command line parser, pass it reference of our custom
        # config to guess the correct defaults
        self.my_option_parser = MyOptionParser(self.config)

        # let's deploy SQL DAO's with corresponding proxies -- use proxy
        # object whenever you want a separate transaction, use non-proxied
        # version as a 'building block' when you need to take part in
        # on-going transaction
        self.category_dao = CategoryDao(self.session_factory)
        self.category_dao_proxy = summer.TransactionProxy(self.category_dao)
        self.item_dao = ItemDao(self.session_factory)
        self.item_dao_proxy = summer.TransactionProxy(self.item_dao)

        # let's deploy LDAP DAO's; treat them as analogy to SQL DAO's,
        # though the LDAP has no sense of SQL transaction
        self.user_dao = UserDao(self.ldap_session_factory)
        self.user_dao_proxy = summer.LdapProxy(self.user_dao)

        # let's define some higher level business level objects (managers)
        self.category_manager = CategoryManager(self.category_dao)
        self.category_manager_proxy = \
            summer.TransactionProxy(self.category_manager,
                                    self.session_factory)
        self.item_manager = ItemManager(self.item_dao)
        self.item_manager_proxy = summer.TransactionProxy(self.item_manager,
                                                          self.session_factory)
        self.user_manager = UserManager(self.user_dao)
        self.user_manager_proxy = summer.LdapProxy(self.user_manager,
                                                   self.ldap_session_factory)