Changeset - e1ab82613133
[Not reviewed]
default
0 33 1
Alessandro Molina - 9 years ago 2017-01-29 21:08:49
alessandro.molina@axant.it
backend: replace Pylons with TurboGears2

Replace the no-longer-supported Pylons application framework by TurboGears2
which is largely compatible/similar to Pylons.
Some interesting history is described at:
https://en.wikipedia.org/wiki/TurboGears

Changes by Dominik Ruf:
- fix sql config in test.ini

Changes by Thomas De Schampheleire:
- set-up of test suite
- tests: 'fix' repo archival test failure
Between Pylons and TurboGears2, there seems to be a small difference in the
headers sent for repository archive files, related to character encoding.
It is assumed that this difference is not important, and that the test
should just align with reality.
- remove need to import helpers/app_globals in lib
TurboGears2 by default expects helpers and app_globals to be available
in lib. For this reason kallithea/lib/__init__.py was originally changed
to include those files. However, this triggered several types of
circular import problems. If module A imported something from lib (e.g.
lib.annotate), and lib.helpers imported (possibly indirectly) module A,
then there was a circular import. Fix this by overruling the relevant
method of tg AppConfig, which is also hinted in the TurboGears2 code.
Hereby, the include of something from lib does not automatically import
helpers, greatly reducing the chances of circular import problems.
- make sure HTTP error '400' uses the custom error pages
TurboGears2 does not by default handle HTTP status code
'400 (Bad Request)' via the custom error page handling, causing a
standard non-styled error page.
- disable transaction manager
Kallithea currently handles its own transactions and does not need the
TurboGears2 transaction manager. However, TurboGears2 tries to enable it
by default and fails, throwing an error during application initialization.
The error itself seemed to be harmless for normal application functioning,
but was nevertheless confusing.
- add backlash as required dependency: backlash is meant as the WebError
replacement in TurboGears2 (originally WebError is part of Pylons). When
debug==true, it provides an interactive debugger in the browser. When
debug==false, backlash is necessary to show backtraces on the console.
- misc fixes
34 files changed with 284 insertions and 309 deletions:
0 comments (0 inline, 0 general)
dev_requirements.txt
Show inline comments
 
@@ -6,3 +6,4 @@ pytest-sugar>=0.7.0
 
pytest-catchlog
 
mock
 
sphinx
 
webtest < 3
development.ini
Show inline comments
 
@@ -512,7 +512,7 @@ script_location = kallithea:alembic
 
################################
 

	
 
[loggers]
 
keys = root, routes, kallithea, sqlalchemy, gearbox, beaker, templates, whoosh_indexer
 
keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer
 

	
 
[handlers]
 
keys = console, console_sql
 
@@ -553,6 +553,12 @@ handlers =
 
qualname = kallithea
 
propagate = 1
 

	
 
[logger_tg]
 
level = DEBUG
 
handlers =
 
qualname = tg
 
propagate = 1
 

	
 
[logger_gearbox]
 
level = DEBUG
 
handlers =
kallithea/__init__.py
Show inline comments
 
@@ -30,10 +30,6 @@ Original author and date, and relevant c
 
import sys
 
import platform
 

	
 
# temporary aliasing to allow early introduction of imports like 'from tg import request'
 
import pylons
 
sys.modules['tg'] = pylons
 

	
 
VERSION = (0, 3, 99)
 
BACKENDS = {
 
    'hg': 'Mercurial repository',
kallithea/config/app_cfg.py
Show inline comments
 
@@ -11,76 +11,112 @@
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
Global configuration file for TurboGears2 specific settings in Kallithea.
 

	
 
import os
 
import kallithea
 
This file complements the .ini file.
 
"""
 

	
 
import platform
 

	
 
import pylons
 
import mako.lookup
 
import formencode
 
import os, sys
 

	
 
import kallithea.lib.app_globals as app_globals
 

	
 
from kallithea.config.routing import make_map
 
import tg
 
from tg import hooks
 
from tg.configuration import AppConfig
 
from tg.support.converters import asbool
 

	
 
from kallithea.lib import helpers
 
from kallithea.lib.middleware.https_fixup import HttpsFixup
 
from kallithea.lib.middleware.simplegit import SimpleGit
 
from kallithea.lib.middleware.simplehg import SimpleHg
 
from kallithea.config.routing import make_map
 
from kallithea.lib.auth import set_available_permissions
 
from kallithea.lib.utils import repo2db_mapper, make_ui, set_app_settings, \
 
    load_rcextensions, check_git_version, set_vcs_config, set_indexer_config
 
from kallithea.lib.utils2 import engine_from_config, str2bool
 
from kallithea.model.base import init_model
 
from kallithea.lib.db_manage import DbManage
 
from kallithea.lib.utils import load_rcextensions, make_ui, set_app_settings, set_vcs_config, \
 
    set_indexer_config, check_git_version, repo2db_mapper
 
from kallithea.lib.utils2 import str2bool
 
from kallithea.model.scm import ScmModel
 

	
 
from routes.middleware import RoutesMiddleware
 
from paste.cascade import Cascade
 
from paste.registry import RegistryManager
 
from paste.urlparser import StaticURLParser
 
from paste.deploy.converters import asbool
 
import formencode
 
import kallithea
 

	
 

	
 
class KallitheaAppConfig(AppConfig):
 
    # Note: AppConfig has a misleading name, as it's not the application
 
    # configuration, but the application configurator. The AppConfig values are
 
    # used as a template to create the actual configuration, which might
 
    # overwrite or extend the one provided by the configurator template.
 

	
 
    # To make it clear, AppConfig creates the config and sets into it the same
 
    # values that AppConfig itself has. Then the values from the config file and
 
    # gearbox options are loaded and merged into the configuration. Then an
 
    # after_init_config(conf) method of AppConfig is called for any change that
 
    # might depend on options provided by configuration files.
 

	
 
    def __init__(self):
 
        super(KallitheaAppConfig, self).__init__()
 

	
 
        self['package'] = kallithea
 

	
 
        self['prefer_toscawidgets2'] = False
 
        self['use_toscawidgets'] = False
 

	
 
        self['renderers'] = []
 

	
 
        # Enable json in expose
 
        self['renderers'].append('json')
 

	
 
from pylons.middleware import ErrorHandler, StatusCodeRedirect
 
from pylons.wsgiapp import PylonsApp
 
        # Configure template rendering
 
        self['renderers'].append('mako')
 
        self['default_renderer'] = 'mako'
 
        self['use_dotted_templatenames'] = False
 

	
 
        # Configure Sessions, store data as JSON to avoid pickle security issues
 
        self['session.enabled'] = True
 
        self['session.data_serializer'] = 'json'
 

	
 
        # Configure the base SQLALchemy Setup
 
        self['use_sqlalchemy'] = True
 
        self['model'] = kallithea.model.base
 
        self['DBSession'] = kallithea.model.meta.Session
 

	
 
        # Configure App without an authentication backend.
 
        self['auth_backend'] = None
 

	
 
from kallithea.lib.middleware.simplehg import SimpleHg
 
from kallithea.lib.middleware.simplegit import SimpleGit
 
from kallithea.lib.middleware.https_fixup import HttpsFixup
 
from kallithea.lib.middleware.sessionmiddleware import SecureSessionMiddleware
 
from kallithea.lib.middleware.wrapper import RequestWrapper
 
        # Use custom error page for these errors. By default, Turbogears2 does not add
 
        # 400 in this list.
 
        # Explicitly listing all is considered more robust than appending to defaults,
 
        # in light of possible future framework changes.
 
        self['errorpage.status_codes'] = [400, 401, 403, 404]
 

	
 
def setup_configuration(config, paths, app_conf, test_env, test_index):
 
        # Disable transaction manager -- currently Kallithea takes care of transactions itself
 
        self['tm.enabled'] = False
 

	
 
base_config = KallitheaAppConfig()
 

	
 
# TODO still needed as long as we use pylonslib
 
sys.modules['pylons'] = tg
 

	
 
def setup_configuration(app):
 
    config = app.config
 

	
 
    # store some globals into kallithea
 
    kallithea.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
 
    kallithea.CELERY_EAGER = str2bool(config['app_conf'].get('celery.always.eager'))
 
    kallithea.CONFIG = config
 

	
 
    config['routes.map'] = make_map(config)
 
    config['pylons.app_globals'] = app_globals.Globals(config)
 
    config['pylons.h'] = helpers
 
    kallithea.CONFIG = config
 
    # Provide routes mapper to the RoutedController
 
    root_controller = app.find_controller('root')
 
    root_controller.mapper = config['routes.map'] = make_map(config)
 

	
 
    load_rcextensions(root_path=config['here'])
 

	
 
    # Setup cache object as early as possible
 
    pylons.cache._push_object(config['pylons.app_globals'].cache)
 

	
 
    # Create the Mako TemplateLookup, with the default auto-escaping
 
    config['pylons.app_globals'].mako_lookup = mako.lookup.TemplateLookup(
 
        directories=paths['templates'],
 
        strict_undefined=True,
 
        module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
 
        input_encoding='utf-8', default_filters=['escape'],
 
        imports=['from webhelpers.html import escape'])
 

	
 
    # sets the c attribute access when don't existing attribute are accessed
 
    config['pylons.strict_tmpl_context'] = True
 
    # FIXME move test setup code out of here
 
    test = os.path.split(config['__file__'])[-1] == 'test.ini'
 
    if test:
 
        if test_env is None:
 
            test_env = not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0))
 
        if test_index is None:
 
            test_index = not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0))
 
        test_env = not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0))
 
        test_index = not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0))
 
        if os.environ.get('TEST_DB'):
 
            # swap config if we pass enviroment variable
 
            # swap config if we pass environment variable
 
            config['sqlalchemy.url'] = os.environ.get('TEST_DB')
 

	
 
        from kallithea.tests.fixture import create_test_env, create_test_index
 
@@ -93,11 +129,6 @@ def setup_configuration(config, paths, a
 
        if test_index:
 
            create_test_index(TESTS_TMP_PATH, config, True)
 

	
 
    # MULTIPLE DB configs
 
    # Setup the SQLAlchemy database engine
 
    sa_engine = engine_from_config(config, 'sqlalchemy.')
 
    init_model(sa_engine)
 

	
 
    set_available_permissions(config)
 
    repos_path = make_ui('db').configitems('paths')[0][1]
 
    config['base_path'] = repos_path
 
@@ -108,78 +139,37 @@ def setup_configuration(config, paths, a
 
        instance_id = '%s-%s' % (platform.uname()[1], os.getpid())
 
        kallithea.CONFIG['instance_id'] = instance_id
 

	
 
    # CONFIGURATION OPTIONS HERE (note: all config options will override
 
    # any Pylons config options)
 
    # update kallithea.CONFIG with the meanwhile changed 'config'
 
    kallithea.CONFIG.update(config)
 

	
 
    # store config reference into our module to skip import magic of
 
    # pylons
 
    kallithea.CONFIG.update(config)
 
    # configure vcs and indexer libraries (they are supposed to be independent
 
    # as much as possible and thus avoid importing tg.config or
 
    # kallithea.CONFIG).
 
    set_vcs_config(kallithea.CONFIG)
 
    set_indexer_config(kallithea.CONFIG)
 

	
 
    #check git version
 
    check_git_version()
 

	
 
    if str2bool(config.get('initial_repo_scan', True)):
 
        repo2db_mapper(ScmModel().repo_scan(repos_path),
 
                       remove_obsolete=False, install_git_hooks=False)
 

	
 
    formencode.api.set_stdtranslation(languages=[config.get('lang')])
 

	
 
    return config
 

	
 
def setup_application(config, global_conf, full_stack, static_files):
 
hooks.register('configure_new_app', setup_configuration)
 

	
 
    # The Pylons WSGI app
 
    app = PylonsApp(config=config)
 

	
 
    # Routing/Session/Cache Middleware
 
    app = RoutesMiddleware(app, config['routes.map'], use_method_override=False)
 
    app = SecureSessionMiddleware(app, config)
 

	
 
    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
 
    if asbool(config['pdebug']):
 
        from kallithea.lib.profiler import ProfilingMiddleware
 
        app = ProfilingMiddleware(app)
 

	
 
    if asbool(full_stack):
 

	
 
        from kallithea.lib.middleware.sentry import Sentry
 
        from kallithea.lib.middleware.appenlight import AppEnlight
 
        if AppEnlight and asbool(config['app_conf'].get('appenlight')):
 
            app = AppEnlight(app, config)
 
        elif Sentry:
 
            app = Sentry(app, config)
 

	
 
        # Handle Python exceptions
 
        app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
 
def setup_application(app):
 
    config = app.config
 

	
 
        # Display error documents for 401, 403, 404 status codes (and
 
        # 500 when debug is disabled)
 
        # Note: will buffer the output in memory!
 
        if asbool(config['debug']):
 
            app = StatusCodeRedirect(app)
 
        else:
 
            app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
 

	
 
        # we want our low level middleware to get to the request ASAP. We don't
 
        # need any pylons stack middleware in them - especially no StatusCodeRedirect buffering
 
        app = SimpleHg(app, config)
 
        app = SimpleGit(app, config)
 
    # we want our low level middleware to get to the request ASAP. We don't
 
    # need any stack middleware in them - especially no StatusCodeRedirect buffering
 
    app = SimpleHg(app, config)
 
    app = SimpleGit(app, config)
 

	
 
        # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy
 
        if any(asbool(config.get(x)) for x in ['https_fixup', 'force_https', 'use_htsts']):
 
            app = HttpsFixup(app, config)
 

	
 
        app = RequestWrapper(app, config) # logging
 

	
 
    # Establish the Registry for this application
 
    app = RegistryManager(app) # thread / request-local module globals / variables
 
    # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy
 
    if any(asbool(config.get(x)) for x in ['https_fixup', 'force_https', 'use_htsts']):
 
        app = HttpsFixup(app, config)
 
    return app
 

	
 
    if asbool(static_files):
 
        # Serve static files
 
        static_app = StaticURLParser(config['pylons.paths']['static_files'])
 
        app = Cascade([static_app, app])
 

	
 
    app.config = config
 

	
 
    return app
 
hooks.register('before_config', setup_application)
kallithea/config/environment.py
Show inline comments
 
@@ -11,34 +11,11 @@
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
    Pylons environment configuration
 
"""
 
"""WSGI environment setup for Kallithea."""
 

	
 
import os
 
import kallithea
 
import pylons
 

	
 
from kallithea.config.app_cfg import setup_configuration
 
from kallithea.config.app_cfg import base_config
 

	
 
def load_environment(global_conf, app_conf,
 
                     test_env=None, test_index=None):
 
    """
 
    Configure the Pylons environment via the ``pylons.config``
 
    object
 
    """
 
    config = pylons.configuration.PylonsConfig()
 
__all__ = ['load_environment']
 

	
 
    # Pylons paths
 
    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
    paths = dict(
 
        root=root,
 
        controllers=os.path.join(root, 'controllers'),
 
        static_files=os.path.join(root, 'public'),
 
        templates=[os.path.join(root, 'templates')]
 
    )
 

	
 
    # Initialize config with the basic options
 
    config.init_app(global_conf, app_conf, package='kallithea', paths=paths)
 

	
 
    return setup_configuration(config, paths, app_conf, test_env, test_index)
 
# Use base_config to setup the environment loader function
 
load_environment = base_config.make_load_environment()
kallithea/config/middleware.py
Show inline comments
 
@@ -11,33 +11,35 @@
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
    Pylons middleware initialization
 
"""
 
"""WSGI middleware initialization for the Kallithea application."""
 

	
 
from kallithea.config.app_cfg import setup_application
 
from kallithea.config.app_cfg import base_config
 
from kallithea.config.environment import load_environment
 

	
 
def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
 
    """Create a Pylons WSGI application and return it
 
__all__ = ['make_app']
 

	
 
# Use base_config to setup the necessary PasteDeploy application factory.
 
# make_base_app will wrap the TurboGears2 app with all the middleware it needs.
 
make_base_app = base_config.setup_tg_wsgi_app(load_environment)
 

	
 
    ``global_conf``
 
        The inherited configuration for this application. Normally from
 
        the [DEFAULT] section of the Paste ini file.
 

	
 
def make_app(global_conf, full_stack=True, **app_conf):
 
    """
 
    Set up Kallithea with the settings found in the PasteDeploy configuration
 
    file used.
 

	
 
    ``full_stack``
 
        Whether or not this application provides a full WSGI stack (by
 
        default, meaning it handles its own exceptions and errors).
 
        Disable full_stack when this application is "managed" by
 
        another WSGI middleware.
 
    :param global_conf: The global settings for Kallithea (those
 
        defined under the ``[DEFAULT]`` section).
 
    :type global_conf: dict
 
    :param full_stack: Should the whole TurboGears2 stack be set up?
 
    :type full_stack: str or bool
 
    :return: The Kallithea application with all the relevant middleware
 
        loaded.
 

	
 
    ``app_conf``
 
        The application's local configuration. Normally specified in
 
        the [app:<name>] section of the Paste ini file (where <name>
 
        defaults to main).
 
    This is the PasteDeploy factory for the Kallithea application.
 

	
 
    ``app_conf`` contains all the application-specific settings (those defined
 
    under ``[app:main]``.
 
    """
 
    # Configure the Pylons environment
 
    config = load_environment(global_conf, app_conf)
 

	
 
    return setup_application(config, global_conf, full_stack, static_files)
 
    app = make_base_app(global_conf, full_stack=full_stack, **app_conf)
 
    return app
kallithea/config/routing.py
Show inline comments
 
@@ -28,7 +28,7 @@ ADMIN_PREFIX = '/_admin'
 

	
 
def make_map(config):
 
    """Create, configure and return the routes Mapper"""
 
    rmap = Mapper(directory=config['pylons.paths']['controllers'],
 
    rmap = Mapper(directory=config['paths']['controllers'],
 
                  always_scan=config['debug'])
 
    rmap.minimization = False
 
    rmap.explicit = False
 
@@ -46,7 +46,7 @@ def make_map(config):
 
        repo_name = match_dict.get('repo_name')
 

	
 
        if match_dict.get('f_path'):
 
            #fix for multiple initial slashes that causes errors
 
            # fix for multiple initial slashes that causes errors
 
            match_dict['f_path'] = match_dict['f_path'].lstrip('/')
 

	
 
        by_id_match = get_repo_by_id(repo_name)
 
@@ -90,11 +90,6 @@ def make_map(config):
 
    def check_int(environ, match_dict):
 
        return match_dict.get('id').isdigit()
 

	
 
    # The ErrorController route (handles 404/500 error pages); it should
 
    # likely stay at the top, ensuring it can always be resolved
 
    rmap.connect('/error/{action}', controller='error')
 
    rmap.connect('/error/{action}/{id}', controller='error')
 

	
 
    #==========================================================================
 
    # CUSTOM ROUTES HERE
 
    #==========================================================================
 
@@ -426,8 +421,8 @@ def make_map(config):
 
    #==========================================================================
 
    # API V2
 
    #==========================================================================
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='api/api') as m:
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='api/api',
 
                        action='_dispatch') as m:
 
        m.connect('api', '/api')
 

	
 
    #USER JOURNAL
kallithea/controllers/admin/repo_groups.py
Show inline comments
 
@@ -32,7 +32,7 @@ import itertools
 

	
 
from formencode import htmlfill
 

	
 
from tg import request, tmpl_context as c
 
from tg import request, tmpl_context as c, app_globals
 
from tg.i18n import ugettext as _, ungettext
 
from webob.exc import HTTPFound, HTTPForbidden, HTTPNotFound, HTTPInternalServerError
 

	
 
@@ -112,7 +112,7 @@ class RepoGroupsController(BaseControlle
 
        group_iter = RepoGroupList(_list, perm_level='admin')
 
        repo_groups_data = []
 
        total_records = len(group_iter)
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        repo_group_name = lambda repo_group_name, children_groups: (
kallithea/controllers/admin/user_groups.py
Show inline comments
 
@@ -30,7 +30,7 @@ import traceback
 
import formencode
 

	
 
from formencode import htmlfill
 
from tg import request, tmpl_context as c, config
 
from tg import request, tmpl_context as c, config, app_globals
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPFound
 

	
 
@@ -94,7 +94,7 @@ class UserGroupsController(BaseControlle
 
        group_iter = UserGroupList(_list, perm_level='admin')
 
        user_groups_data = []
 
        total_records = len(group_iter)
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        user_group_name = lambda user_group_id, user_group_name: (
kallithea/controllers/admin/users.py
Show inline comments
 
@@ -30,7 +30,7 @@ import traceback
 
import formencode
 

	
 
from formencode import htmlfill
 
from tg import request, tmpl_context as c, config
 
from tg import request, tmpl_context as c, config, app_globals
 
from tg.i18n import ugettext as _
 
from sqlalchemy.sql.expression import func
 
from webob.exc import HTTPFound, HTTPNotFound
 
@@ -73,7 +73,7 @@ class UsersController(BaseController):
 

	
 
        users_data = []
 
        total_records = len(c.users_list)
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        grav_tmpl = '<div class="gravatar">%s</div>'
kallithea/controllers/api/__init__.py
Show inline comments
 
@@ -32,12 +32,9 @@ import traceback
 
import time
 
import itertools
 

	
 
from paste.response import replace_header
 
from pylons.controllers import WSGIController
 
from pylons.controllers.util import Response
 
from tg import request
 
from tg import Response, response, request, TGController
 

	
 
from webob.exc import HTTPError
 
from webob.exc import HTTPError, HTTPException, WSGIHTTPException
 

	
 
from kallithea.model.db import User
 
from kallithea.model import meta
 
@@ -59,19 +56,20 @@ class JSONRPCError(BaseException):
 
        return safe_str(self.message)
 

	
 

	
 
class JSONRPCErrorResponse(Response, Exception):
 
class JSONRPCErrorResponse(Response, HTTPException):
 
    """
 
    Generate a Response object with a JSON-RPC error body
 
    """
 

	
 
    def __init__(self, message=None, retid=None, code=None):
 
        HTTPException.__init__(self, message, self)
 
        Response.__init__(self,
 
                          body=json.dumps(dict(id=retid, result=None, error=message)),
 
                          json_body=dict(id=retid, result=None, error=message),
 
                          status=code,
 
                          content_type='application/json')
 

	
 

	
 
class JSONRPCController(WSGIController):
 
class JSONRPCController(TGController):
 
    """
 
     A WSGI-speaking JSON-RPC controller class
 

	
 
@@ -95,19 +93,15 @@ class JSONRPCController(WSGIController):
 
        """
 
        return self._rpc_args
 

	
 
    def __call__(self, environ, start_response):
 
    def _dispatch(self, state, remainder=None):
 
        """
 
        Parse the request body as JSON, look up the method on the
 
        controller and if it exists, dispatch to it.
 
        """
 
        try:
 
            return self._handle_request(environ, start_response)
 
        except JSONRPCErrorResponse as e:
 
            return e
 
        finally:
 
            meta.Session.remove()
 
        # Since we are here we should respond as JSON
 
        response.content_type = 'application/json'
 

	
 
    def _handle_request(self, environ, start_response):
 
        environ = state.request.environ
 
        start = time.time()
 
        ip_addr = request.ip_addr = self._get_ip_addr(environ)
 
        self._req_id = None
 
@@ -218,39 +212,26 @@ class JSONRPCController(WSGIController):
 
                )
 

	
 
        self._rpc_args = {}
 

	
 
        self._rpc_args.update(self._request_params)
 

	
 
        self._rpc_args['action'] = self._req_method
 
        self._rpc_args['environ'] = environ
 
        self._rpc_args['start_response'] = start_response
 

	
 
        status = []
 
        headers = []
 
        exc_info = []
 

	
 
        def change_content(new_status, new_headers, new_exc_info=None):
 
            status.append(new_status)
 
            headers.extend(new_headers)
 
            exc_info.append(new_exc_info)
 

	
 
        output = WSGIController.__call__(self, environ, change_content)
 
        output = list(output) # expand iterator - just to ensure exact timing
 
        replace_header(headers, 'Content-Type', 'application/json')
 
        start_response(status[0], headers, exc_info[0])
 
        log.info('IP: %s Request to %s time: %.3fs' % (
 
            self._get_ip_addr(environ),
 
            safe_unicode(_get_access_path(environ)), time.time() - start)
 
        )
 
        return output
 

	
 
    def _dispatch_call(self):
 
        state.set_action(self._rpc_call, [])
 
        state.set_params(self._rpc_args)
 
        return state
 

	
 
    def _rpc_call(self, action, environ, **rpc_args):
 
        """
 
        Implement dispatch interface specified by WSGIController
 
        Call the specified RPC Method
 
        """
 
        raw_response = ''
 
        try:
 
            raw_response = self._inspect_call(self._func)
 
            raw_response = getattr(self, action)(**rpc_args)
 
            if isinstance(raw_response, HTTPError):
 
                self._error = str(raw_response)
 
        except JSONRPCError as e:
kallithea/controllers/error.py
Show inline comments
 
@@ -29,9 +29,8 @@ import os
 
import cgi
 
import logging
 

	
 
from tg import tmpl_context as c, request, config
 
from tg import tmpl_context as c, request, config, expose
 
from tg.i18n import ugettext as _
 
from pylons.middleware import media_path
 

	
 
from kallithea.lib.base import BaseController, render
 

	
 
@@ -52,8 +51,9 @@ class ErrorController(BaseController):
 
        # disable all base actions since we don't need them here
 
        pass
 

	
 
    def document(self):
 
        resp = request.environ.get('pylons.original_response')
 
    @expose('/errors/error_document.html')
 
    def document(self, *args, **kwargs):
 
        resp = request.environ.get('tg.original_response')
 
        c.site_name = config.get('title')
 

	
 
        log.debug('### %s ###', resp and resp.status or 'no response')
 
@@ -70,7 +70,7 @@ class ErrorController(BaseController):
 
            c.error_message = _('No response')
 
            c.error_explanation = _('Unknown error')
 

	
 
        return render('/errors/error_document.html')
 
        return dict()
 

	
 
    def get_error_explanation(self, code):
 
        """ get the error explanations of int codes
kallithea/controllers/root.py
Show inline comments
 
new file 100644
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU 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 General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
from tgext.routes import RoutedController
 
from kallithea.lib.base import BaseController
 
from kallithea.controllers.error import ErrorController
 

	
 

	
 
# With TurboGears, the RootController is the controller from which all routing
 
# starts from. It is 'magically' found based on the fact that a controller
 
# 'foo' is expected to have a class name FooController, located in a file
 
# foo.py, inside config['paths']['controllers']. The name 'root' for the root
 
# controller is the default name. The dictionary config['paths'] determines the
 
# directories where templates, static files and controllers are found. It is
 
# set up in tg.AppConfig based on AppConfig['package'] ('kallithea') and the
 
# respective defaults 'templates', 'public' and 'controllers'.
 
# Inherit from RoutedController to allow Kallithea to use regex-based routing.
 
class RootController(RoutedController, BaseController):
 

	
 
    # the following assignment hooks in error handling
 
    error = ErrorController()
kallithea/lib/app_globals.py
Show inline comments
 
@@ -25,9 +25,8 @@ Original author and date, and relevant c
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from beaker.cache import CacheManager
 
from beaker.util import parse_cache_config_options
 
import tg
 
from tg import config
 

	
 

	
 
class Globals(object):
 
@@ -36,11 +35,18 @@ class Globals(object):
 
    life of the application
 
    """
 

	
 
    def __init__(self, config):
 
    def __init__(self):
 
        """One instance of Globals is created during application
 
        initialization and is available during requests via the
 
        'app_globals' variable
 

	
 
        """
 
        self.cache = CacheManager(**parse_cache_config_options(config))
 
        self.available_permissions = None   # propagated after init_model
 

	
 
    @property
 
    def cache(self):
 
        return tg.cache
 

	
 
    @property
 
    def mako_lookup(self):
 
        return config['render_functions']['mako'].normal_loader
kallithea/lib/base.py
Show inline comments
 
@@ -41,9 +41,8 @@ import paste.auth.basic
 
import paste.httpheaders
 
from webhelpers.pylonslib import secure_form
 

	
 
from tg import config, tmpl_context as c, request, response, session
 
from pylons.controllers import WSGIController
 
from pylons.templating import render_mako as render  # don't remove this import
 
from tg import config, tmpl_context as c, request, response, session, render_template
 
from tg import TGController
 
from tg.i18n import ugettext as _
 

	
 
from kallithea import __version__, BACKENDS
 
@@ -66,6 +65,10 @@ from kallithea.model.scm import ScmModel
 
log = logging.getLogger(__name__)
 

	
 

	
 
def render(template_path):
 
    return render_template({'url': url}, 'mako', template_path)
 

	
 

	
 
def _filter_proxy(ip):
 
    """
 
    HEADERS can have multiple ips inside the left-most being the original
 
@@ -101,7 +104,7 @@ def _get_ip_addr(environ):
 

	
 
def _get_access_path(environ):
 
    path = environ.get('PATH_INFO')
 
    org_req = environ.get('pylons.original_request')
 
    org_req = environ.get('tg.original_request')
 
    if org_req:
 
        path = org_req.environ.get('PATH_INFO')
 
    return path
 
@@ -375,14 +378,11 @@ class BaseVCSController(object):
 
            meta.Session.remove()
 

	
 

	
 
class BaseController(WSGIController):
 
class BaseController(TGController):
 

	
 
    def _before(self, *args, **kwargs):
 
        pass
 

	
 
    def __before__(self):
 
        """
 
        __before__ is called before controller methods and after __call__
 
        _before is called before controller methods and after __call__
 
        """
 
        c.kallithea_version = __version__
 
        rc_config = Setting.get_app_settings()
 
@@ -437,13 +437,6 @@ class BaseController(WSGIController):
 

	
 
        self.scm_model = ScmModel()
 

	
 
        # __before__ in Pylons is called _before in TurboGears2. As preparation
 
        # to the migration to TurboGears2, all __before__ methods were already
 
        # renamed to _before.  We call them from here to keep the behavior.
 
        # This is a temporary call that will be removed in the real TurboGears2
 
        # migration commit.
 
        self._before()
 

	
 
    @staticmethod
 
    def _determine_auth_user(api_key, bearer_token, session_authuser):
 
        """
 
@@ -530,12 +523,7 @@ class BaseController(WSGIController):
 
            log.error('%r request with payload parameters; WebOb should have stopped this', request.method)
 
            raise webob.exc.HTTPBadRequest()
 

	
 
    def __call__(self, environ, start_response):
 
        """Invoke the Controller"""
 

	
 
        # WSGIController.__call__ dispatches to the Controller method
 
        # the request is routed to. This routing information is
 
        # available in environ['pylons.routes_dict']
 
    def __call__(self, environ, context):
 
        try:
 
            request.ip_addr = _get_ip_addr(environ)
 
            # make sure that we update permissions each time we call controller
 
@@ -564,11 +552,9 @@ class BaseController(WSGIController):
 
                request.ip_addr, request.authuser,
 
                safe_unicode(_get_access_path(environ)),
 
            )
 
            return WSGIController.__call__(self, environ, start_response)
 
            return super(BaseController, self).__call__(environ, context)
 
        except webob.exc.HTTPException as e:
 
            return e(environ, start_response)
 
        finally:
 
            meta.Session.remove()
 
            return e
 

	
 

	
 
class BaseRepoController(BaseController):
kallithea/lib/paster_commands/common.py
Show inline comments
 
@@ -82,12 +82,12 @@ class BasePasterCommand(gearbox.command.
 
        """
 
        Read the config file and initialize logging and the application.
 
        """
 
        from tg import config as pylonsconfig
 
        from kallithea.config.middleware import make_app
 

	
 
        path_to_ini_file = os.path.realpath(config_file)
 
        conf = paste.deploy.appconfig('config:' + path_to_ini_file)
 
        logging.config.fileConfig(path_to_ini_file)
 
        pylonsconfig.init_app(conf.global_conf, conf.local_conf)
 
        make_app(conf.global_conf, **conf.local_conf)
 

	
 
    def _init_session(self):
 
        """
kallithea/lib/paster_commands/make_index.py
Show inline comments
 
@@ -40,7 +40,7 @@ class Command(BasePasterCommand):
 
    "Kallithea: Create or update full text search index"
 

	
 
    def take_action(self, args):
 
        from pylons import config
 
        from tg import config
 
        index_location = config['index_dir']
 
        load_rcextensions(config['here'])
 

	
kallithea/lib/paster_commands/make_rcextensions.py
Show inline comments
 
@@ -44,7 +44,7 @@ class Command(BasePasterCommand):
 
    takes_config_file = False
 

	
 
    def take_action(self, args):
 
        from pylons import config
 
        from tg import config
 

	
 
        here = config['here']
 
        content = pkg_resources.resource_string(
kallithea/lib/paster_commands/template.ini.mako
Show inline comments
 
@@ -516,7 +516,7 @@ script_location = kallithea:alembic
 
<%text>################################</%text>
 

	
 
[loggers]
 
keys = root, routes, kallithea, sqlalchemy, gearbox, beaker, templates, whoosh_indexer
 
keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer
 

	
 
[handlers]
 
keys = console, console_sql
 
@@ -557,6 +557,12 @@ handlers =
 
qualname = kallithea
 
propagate = 1
 

	
 
[logger_tg]
 
level = DEBUG
 
handlers =
 
qualname = tg
 
propagate = 1
 

	
 
[logger_gearbox]
 
level = DEBUG
 
handlers =
kallithea/lib/utils.py
Show inline comments
 
@@ -32,6 +32,7 @@ import datetime
 
import traceback
 
import beaker
 

	
 
from tg import request, response
 
from webhelpers.text import collapse, remove_formatting, strip_tags
 
from beaker.cache import _cache_decorate
 

	
kallithea/model/notification.py
Show inline comments
 
@@ -29,7 +29,7 @@ Original author and date, and relevant c
 
import logging
 
import traceback
 

	
 
from tg import tmpl_context as c
 
from tg import tmpl_context as c, app_globals
 
from tg.i18n import ugettext as _
 
from sqlalchemy.orm import joinedload, subqueryload
 

	
 
@@ -274,8 +274,8 @@ class EmailNotificationModel(object):
 

	
 
    def __init__(self):
 
        super(EmailNotificationModel, self).__init__()
 
        self._template_root = kallithea.CONFIG['pylons.paths']['templates'][0]
 
        self._tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        self._template_root = kallithea.CONFIG['paths']['templates'][0]
 
        self._tmpl_lookup = app_globals.mako_lookup
 
        self.email_types = {
 
            self.TYPE_CHANGESET_COMMENT: 'changeset_comment',
 
            self.TYPE_PASSWORD_RESET: 'password_reset',
kallithea/model/repo.py
Show inline comments
 
@@ -153,10 +153,10 @@ class RepoModel(object):
 
    @classmethod
 
    def _render_datatable(cls, tmpl, *args, **kwargs):
 
        import kallithea
 
        from tg import tmpl_context as c, request
 
        from tg import tmpl_context as c, request, app_globals
 
        from tg.i18n import ugettext as _
 

	
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        tmpl = template.get_def(tmpl)
kallithea/tests/base.py
Show inline comments
 
@@ -21,14 +21,9 @@ import tempfile
 
import time
 

	
 
from tg import config
 
import pylons
 
from pylons import url
 
from pylons.i18n.translation import _get_translator
 
from pylons.util import ContextObj
 
from routes.util import URLGenerator
 
from webtest import TestApp
 

	
 
from kallithea import is_windows
 
from kallithea import is_windows, model
 
from kallithea.model.db import Notification, User, UserNotification
 
from kallithea.model.meta import Session
 
from kallithea.lib.utils2 import safe_str
 
@@ -42,6 +37,10 @@ log = logging.getLogger(__name__)
 
skipif = pytest.mark.skipif
 
parametrize = pytest.mark.parametrize
 

	
 
# Hack: These module global values MUST be set to actual values before running any tests. This is currently done by conftest.py.
 
url = None
 
testapp = None
 

	
 
__all__ = [
 
    'skipif', 'parametrize', 'environ', 'url', 'TestController',
 
    'ldap_lib_installed', 'pam_lib_installed', 'invalidate_all_caches',
 
@@ -147,17 +146,9 @@ class TestController(object):
 

	
 
    @pytest.fixture(autouse=True)
 
    def app_fixture(self):
 
        config = pylons.test.pylonsapp.config
 
        url._push_object(URLGenerator(config['routes.map'], environ))
 
        pylons.app_globals._push_object(config['pylons.app_globals'])
 
        pylons.config._push_object(config)
 
        pylons.tmpl_context._push_object(ContextObj())
 
        # Initialize a translator for tests that utilize i18n
 
        translator = _get_translator(pylons.config.get('lang'))
 
        pylons.translator._push_object(translator)
 
        h = NullHandler()
 
        logging.getLogger("kallithea").addHandler(h)
 
        self.app = TestApp(pylons.test.pylonsapp)
 
        self.app = TestApp(testapp)
 
        return self.app
 

	
 
    def remove_all_notifications(self):
kallithea/tests/conftest.py
Show inline comments
 
import os
 
import sys
 
import logging
 
import pkg_resources
 

	
 
import pkg_resources
 
from paste.deploy import loadapp
 
import pylons.test
 
from pylons.i18n.translation import _get_translator
 
from routes.util import URLGenerator
 
from tg import config
 

	
 
import pytest
 
from kallithea.model.user import UserModel
 
from kallithea.model.meta import Session
 
from kallithea.model.db import Setting, User, UserIpMap
 
from kallithea.tests.base import invalidate_all_caches, TEST_USER_REGULAR_LOGIN
 
import kallithea.tests.base # FIXME: needed for setting testapp instance!!!
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
def pytest_configure():
 
    path = os.getcwd()
 
@@ -21,14 +23,10 @@ def pytest_configure():
 

	
 
    # Disable INFO logging of test database creation, restore with NOTSET
 
    logging.disable(logging.INFO)
 
    pylons.test.pylonsapp = loadapp('config:kallithea/tests/test.ini', relative_to=path)
 
    kallithea.tests.base.testapp = loadapp('config:kallithea/tests/test.ini', relative_to=path)
 
    logging.disable(logging.NOTSET)
 

	
 
    # Initialize a translator for tests that utilize i18n
 
    translator = _get_translator(pylons.config.get('lang'))
 
    pylons.translator._push_object(translator)
 

	
 
    return pylons.test.pylonsapp
 
    kallithea.tests.base.url = URLGenerator(config['routes.map'], kallithea.tests.base.environ)
 

	
 

	
 
@pytest.fixture
kallithea/tests/functional/test_admin_notifications.py
Show inline comments
 
@@ -6,7 +6,7 @@ from kallithea.model.notification import
 
from kallithea.model.meta import Session
 
from kallithea.lib import helpers as h
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
class TestNotificationsController(TestController):
 
    def setup_method(self, method):
kallithea/tests/functional/test_admin_permissions.py
Show inline comments
 
@@ -5,7 +5,7 @@ from kallithea.model.user import UserMod
 
from kallithea.model.meta import Session
 
from kallithea.tests.base import *
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 

	
 
class TestAdminPermissionsController(TestController):
kallithea/tests/functional/test_admin_users.py
Show inline comments
 
@@ -17,7 +17,6 @@ from sqlalchemy.orm.exc import NoResultF
 
import pytest
 
from kallithea.tests.base import *
 
from kallithea.tests.fixture import Fixture
 
from kallithea.tests.test_context import test_context
 
from kallithea.controllers.admin.users import UsersController
 
from kallithea.model.db import User, Permission, UserIpMap, UserApiKeys
 
from kallithea.lib.auth import check_password
 
@@ -27,6 +26,8 @@ from kallithea.lib import helpers as h
 
from kallithea.model.meta import Session
 
from webob.exc import HTTPNotFound
 

	
 
from tg.util.webtest import test_context
 

	
 
fixture = Fixture()
 

	
 
@pytest.fixture
kallithea/tests/functional/test_login.py
Show inline comments
 
@@ -16,7 +16,7 @@ from kallithea.model.db import User, Not
 
from kallithea.model.meta import Session
 
from kallithea.model.user import UserModel
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
fixture = Fixture()
 

	
kallithea/tests/functional/test_my_account.py
Show inline comments
 
@@ -7,7 +7,7 @@ from kallithea.lib import helpers as h
 
from kallithea.model.user import UserModel
 
from kallithea.model.meta import Session
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
fixture = Fixture()
 

	
kallithea/tests/functional/test_pullrequests.py
Show inline comments
 
import re
 
import pytest
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
from kallithea.tests.base import *
 
from kallithea.tests.fixture import Fixture
kallithea/tests/models/test_notifications.py
Show inline comments
 
@@ -14,7 +14,7 @@ from kallithea.model.notification import
 
import kallithea.lib.celerylib
 
import kallithea.lib.celerylib.tasks
 

	
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
class TestNotifications(TestController):
 

	
kallithea/tests/other/test_libs.py
Show inline comments
 
@@ -31,7 +31,7 @@ import mock
 
from kallithea.tests.base import *
 
from kallithea.lib.utils2 import AttributeDict
 
from kallithea.model.db import Repository
 
from kallithea.tests.test_context import test_context
 
from tg.util.webtest import test_context
 

	
 
proto = 'http'
 
TEST_URLS = [
 
@@ -224,7 +224,7 @@ class TestLibs(TestController):
 
        from kallithea.lib.helpers import gravatar_url
 
        _md5 = lambda s: hashlib.md5(s).hexdigest()
 

	
 
        #mock pylons.tmpl_context
 
        # mock tg.tmpl_context
 
        def fake_tmpl_context(_url):
 
            _c = AttributeDict()
 
            _c.visual = AttributeDict()
 
@@ -236,31 +236,31 @@ class TestLibs(TestController):
 
        fake_url = FakeUrlGenerator(current_url='https://example.com')
 
        with mock.patch('kallithea.config.routing.url', fake_url):
 
            fake = fake_tmpl_context(_url='http://example.com/{email}')
 
            with mock.patch('pylons.tmpl_context', fake):
 
            with mock.patch('tg.tmpl_context', fake):
 
                    from kallithea.config.routing import url
 
                    assert url.current() == 'https://example.com'
 
                    grav = gravatar_url(email_address='test@example.com', size=24)
 
                    assert grav == 'http://example.com/test@example.com'
 

	
 
            fake = fake_tmpl_context(_url='http://example.com/{email}')
 
            with mock.patch('pylons.tmpl_context', fake):
 
            with mock.patch('tg.tmpl_context', fake):
 
                grav = gravatar_url(email_address='test@example.com', size=24)
 
                assert grav == 'http://example.com/test@example.com'
 

	
 
            fake = fake_tmpl_context(_url='http://example.com/{md5email}')
 
            with mock.patch('pylons.tmpl_context', fake):
 
            with mock.patch('tg.tmpl_context', fake):
 
                em = 'test@example.com'
 
                grav = gravatar_url(email_address=em, size=24)
 
                assert grav == 'http://example.com/%s' % (_md5(em))
 

	
 
            fake = fake_tmpl_context(_url='http://example.com/{md5email}/{size}')
 
            with mock.patch('pylons.tmpl_context', fake):
 
            with mock.patch('tg.tmpl_context', fake):
 
                em = 'test@example.com'
 
                grav = gravatar_url(email_address=em, size=24)
 
                assert grav == 'http://example.com/%s/%s' % (_md5(em), 24)
 

	
 
            fake = fake_tmpl_context(_url='{scheme}://{netloc}/{md5email}/{size}')
 
            with mock.patch('pylons.tmpl_context', fake):
 
            with mock.patch('tg.tmpl_context', fake):
 
                em = 'test@example.com'
 
                grav = gravatar_url(email_address=em, size=24)
 
                assert grav == 'https://example.com/%s/%s' % (_md5(em), 24)
kallithea/tests/test.ini
Show inline comments
 
@@ -517,7 +517,7 @@ script_location = kallithea:alembic
 
################################
 

	
 
[loggers]
 
keys = root, routes, kallithea, sqlalchemy, gearbox, beaker, templates, whoosh_indexer
 
keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer
 

	
 
[handlers]
 
keys = console, console_sql
 
@@ -558,6 +558,12 @@ handlers =
 
qualname = kallithea
 
propagate = 1
 

	
 
[logger_tg]
 
level = DEBUG
 
handlers =
 
qualname = tg
 
propagate = 1
 

	
 
[logger_gearbox]
 
level = DEBUG
 
handlers =
setup.py
Show inline comments
 
@@ -39,7 +39,9 @@ requirements = [
 
    "GearBox<1",
 
    "waitress>=0.8.8,<1.0",
 
    "webob>=1.7,<2",
 
    "Pylons>=1.0.0,<=1.0.2",
 
    "backlash >= 0.1.1, < 1.0.0",
 
    "TurboGears2 >= 2.3.10, < 3.0.0",
 
    "tgext.routes >= 0.2.0, < 1.0.0",
 
    "Beaker>=1.7.0,<2",
 
    "WebHelpers==1.3",
 
    "formencode>=1.2.4,<=1.2.6",
 
@@ -56,6 +58,8 @@ requirements = [
 
    "Routes==1.13",
 
    "dulwich>=0.14.1",
 
    "mercurial>=2.9,<4.2",
 
    "decorator >= 3.3.2",
 
    "Paste >= 2.0.3, < 3.0",
 
]
 

	
 
if sys.version_info < (2, 7):
 
@@ -151,9 +155,6 @@ setuptools.setup(
 
    [paste.app_factory]
 
    main = kallithea.config.middleware:make_app
 

	
 
    [paste.app_install]
 
    main = pylons.util:PylonsInstaller
 

	
 
    [gearbox.commands]
 
    make-config=kallithea.lib.paster_commands.make_config:Command
 
    setup-db=kallithea.lib.paster_commands.setup_db:Command
0 comments (0 inline, 0 general)