##// END OF EJS Templates
configurator: reorganize the code
ergo -
Show More
@@ -1,245 +1,218 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # App Enlight Enterprise Edition, including its added features, Support
19 19 # services, and proprietary license terms, please see
20 20 # https://rhodecode.com/licenses/
21 21
22 22 import datetime
23 23 import logging
24 24 import pyelasticsearch
25 25 import redis
26 26 import os
27 27 from pkg_resources import iter_entry_points
28 28
29 29 import appenlight.lib.jinja2_filters as jinja2_filters
30 30 import appenlight.lib.encryption as encryption
31 31
32 32 from pyramid.config import PHASE3_CONFIG
33 33 from pyramid.authentication import AuthTktAuthenticationPolicy
34 34 from pyramid.authorization import ACLAuthorizationPolicy
35 35 from pyramid_mailer.mailer import Mailer
36 36 from pyramid.renderers import JSON
37 37 from pyramid_redis_sessions import session_factory_from_settings
38 38 from pyramid.settings import asbool, aslist
39 39 from pyramid.security import AllPermissionsList
40 40 from pyramid_authstack import AuthenticationStackPolicy
41 41 from redlock import Redlock
42 42 from sqlalchemy import engine_from_config
43 43
44 44 from appenlight.celery import configure_celery
45 from appenlight.lib.configurator import CythonCompatConfigurator
45 from appenlight.lib.configurator import (CythonCompatConfigurator,
46 register_appenlight_plugin)
46 47 from appenlight.lib import cache_regions
47 48 from appenlight.lib.ext_json import json
48 49 from appenlight.security import groupfinder, AuthTokenAuthenticationPolicy
49 50
50 51 json_renderer = JSON(serializer=json.dumps, indent=4)
51 52
52 53 log = logging.getLogger(__name__)
53 54
54 55
55 56 def datetime_adapter(obj, request):
56 57 return obj.isoformat()
57 58
58 59
59 60 def all_permissions_adapter(obj, request):
60 61 return '__all_permissions__'
61 62
62 63
63 64 json_renderer.add_adapter(datetime.datetime, datetime_adapter)
64 65 json_renderer.add_adapter(AllPermissionsList, all_permissions_adapter)
65 66
66 67
67 68 def main(global_config, **settings):
68 69 """ This function returns a Pyramid WSGI application.
69 70 """
70 71 auth_tkt_policy = AuthTktAuthenticationPolicy(
71 72 settings['authtkt.secret'],
72 73 hashalg='sha512',
73 74 callback=groupfinder,
74 75 max_age=2592000,
75 76 secure=asbool(settings.get('authtkt.secure', 'false')))
76 77 auth_token_policy = AuthTokenAuthenticationPolicy(
77 78 callback=groupfinder
78 79 )
79 80 authorization_policy = ACLAuthorizationPolicy()
80 81 authentication_policy = AuthenticationStackPolicy()
81 82 authentication_policy.add_policy('auth_tkt', auth_tkt_policy)
82 83 authentication_policy.add_policy('auth_token', auth_token_policy)
83 84 # set crypto key
84 85 encryption.ENCRYPTION_SECRET = settings.get('encryption_secret')
85 86 # import this later so encyption key can be monkeypatched
86 87 from appenlight.models import DBSession, register_datastores
87 88 # update config with cometd info
88 89 settings['cometd_servers'] = {'server': settings['cometd.server'],
89 90 'secret': settings['cometd.secret']}
90 91
91 92 # Create the Pyramid Configurator.
92 93 settings['_mail_url'] = settings['mailing.app_url']
93 94 config = CythonCompatConfigurator(
94 95 settings=settings,
95 96 authentication_policy=authentication_policy,
96 97 authorization_policy=authorization_policy,
97 98 root_factory='appenlight.security.RootFactory',
98 99 default_permission='view')
100 # custom registry variables
101
102 # resource type information
103 config.registry.resource_types = ['resource', 'application']
104 # plugin information
105 config.registry.appenlight_plugins = {}
106
99 107 config.set_default_csrf_options(require_csrf=True, header='X-XSRF-TOKEN')
100 108 config.add_view_deriver('appenlight.predicates.csrf_view',
101 109 name='csrf_view')
102 110
103 111 # later, when config is available
104 112 dogpile_config = {'url': settings['redis.url'],
105 113 "redis_expiration_time": 86400,
106 114 "redis_distributed_lock": True}
107 115 cache_regions.regions = cache_regions.CacheRegions(dogpile_config)
108 116 config.registry.cache_regions = cache_regions.regions
109 117 engine = engine_from_config(settings, 'sqlalchemy.',
110 118 json_serializer=json.dumps)
111 119 DBSession.configure(bind=engine)
112 120
113 121 # json rederer that serializes datetime
114 122 config.add_renderer('json', json_renderer)
115 123 config.set_request_property('appenlight.lib.request.es_conn', 'es_conn')
116 124 config.set_request_property('appenlight.lib.request.get_user', 'user',
117 125 reify=True)
118 126 config.set_request_property('appenlight.lib.request.get_csrf_token',
119 127 'csrf_token', reify=True)
120 128 config.set_request_property('appenlight.lib.request.safe_json_body',
121 129 'safe_json_body', reify=True)
122 130 config.set_request_property('appenlight.lib.request.unsafe_json_body',
123 131 'unsafe_json_body', reify=True)
124 132 config.add_request_method('appenlight.lib.request.add_flash_to_headers',
125 133 'add_flash_to_headers')
126 134 config.add_request_method('appenlight.lib.request.get_authomatic',
127 135 'authomatic', reify=True)
128 136
129 137 config.include('pyramid_redis_sessions')
130 138 config.include('pyramid_tm')
131 139 config.include('pyramid_jinja2')
132 140 config.include('appenlight_client.ext.pyramid_tween')
133 141 config.include('ziggurat_foundations.ext.pyramid.sign_in')
134 142 es_server_list = aslist(settings['elasticsearch.nodes'])
135 143 redis_url = settings['redis.url']
136 144 log.warning('Elasticsearch server list: {}'.format(es_server_list))
137 145 log.warning('Redis server: {}'.format(redis_url))
138 146 config.registry.es_conn = pyelasticsearch.ElasticSearch(es_server_list)
139 147 config.registry.redis_conn = redis.StrictRedis.from_url(redis_url)
140 148
141 149 config.registry.redis_lockmgr = Redlock([settings['redis.redlock.url'], ],
142 150 retry_count=0, retry_delay=0)
143 151 # mailer
144 152 config.registry.mailer = Mailer.from_settings(settings)
145 153
146 154 # Configure sessions
147 155 session_factory = session_factory_from_settings(settings)
148 156 config.set_session_factory(session_factory)
149 157
150 158 # Configure renderers and event subscribers
151 159 config.add_jinja2_extension('jinja2.ext.loopcontrols')
152 160 config.add_jinja2_search_path('appenlight:templates')
153 161 # event subscribers
154 162 config.add_subscriber("appenlight.subscribers.application_created",
155 163 "pyramid.events.ApplicationCreated")
156 164 config.add_subscriber("appenlight.subscribers.add_renderer_globals",
157 165 "pyramid.events.BeforeRender")
158 166 config.add_subscriber('appenlight.subscribers.new_request',
159 167 'pyramid.events.NewRequest')
160 168 config.add_view_predicate('context_type_class',
161 169 'appenlight.predicates.contextTypeClass')
162 170
163 171 register_datastores(es_conn=config.registry.es_conn,
164 172 redis_conn=config.registry.redis_conn,
165 173 redis_lockmgr=config.registry.redis_lockmgr)
166 174
167 175 # base stuff and scan
168 176
169 177 # need to ensure webassets exists otherwise config.override_asset()
170 178 # throws exception
171 179 if not os.path.exists(settings['webassets.dir']):
172 180 os.mkdir(settings['webassets.dir'])
173 181 config.add_static_view(path='appenlight:webassets',
174 182 name='static', cache_max_age=3600)
175 183 config.override_asset(to_override='appenlight:webassets/',
176 184 override_with=settings['webassets.dir'])
177 185
178 186 config.include('appenlight.views')
179 187 config.include('appenlight.views.admin')
180 config.scan(ignore=['appenlight.migrations',
181 'appenlight.scripts',
188 config.scan(ignore=['appenlight.migrations', 'appenlight.scripts',
182 189 'appenlight.tests'])
183 190
184 # resource type information
185 config.registry.resource_types = ['resource', 'application']
186
187 # plugin information
188 config.registry.appenlight_plugins = {}
189
190 def register_appenlight_plugin(config, plugin_name, plugin_config):
191 def register():
192 log.warning('Registering plugin: {}'.format(plugin_name))
193 if plugin_name not in config.registry.appenlight_plugins:
194 config.registry.appenlight_plugins[plugin_name] = {
195 'javascript': None,
196 'static': None,
197 'css': None,
198 'top_nav': None,
199 'celery_tasks': None,
200 'celery_beats': None,
201 'fulltext_indexer': None,
202 'sqlalchemy_migrations': None,
203 'default_values_setter': None,
204 'resource_types': [],
205 'url_gen': None
206 }
207 config.registry.appenlight_plugins[plugin_name].update(
208 plugin_config)
209 # inform AE what kind of resource types we have available
210 # so we can avoid failing when a plugin is removed but data
211 # is still present in the db
212 if plugin_config.get('resource_types'):
213 config.registry.resource_types.extend(
214 plugin_config['resource_types'])
215
216 config.action('appenlight_plugin={}'.format(plugin_name), register)
217
218 191 config.add_directive('register_appenlight_plugin',
219 192 register_appenlight_plugin)
220 193
221 194 for entry_point in iter_entry_points(group='appenlight.plugins'):
222 195 plugin = entry_point.load()
223 196 plugin.includeme(config)
224 197
225 198 # include other appenlight plugins explictly if needed
226 199 includes = aslist(settings.get('appenlight.includes', []))
227 200 for inc in includes:
228 201 config.include(inc)
229 202
230 203 # run this after everything registers in configurator
231 204
232 205 def pre_commit():
233 206 jinja_env = config.get_jinja2_environment()
234 207 jinja_env.filters['tojson'] = json.dumps
235 208 jinja_env.filters['toJSONUnsafe'] = jinja2_filters.toJSONUnsafe
236 209
237 210 config.action(None, pre_commit, order=PHASE3_CONFIG + 999)
238 211
239 212 def wrap_config_celery():
240 213 configure_celery(config.registry)
241 214
242 215 config.action(None, wrap_config_celery, order=PHASE3_CONFIG + 999)
243 216
244 217 app = config.make_wsgi_app()
245 218 return app
@@ -1,61 +1,92 b''
1 1 import inspect
2 import logging
2 3
3 4 from pyramid.config import Configurator
4 5
6 log = logging.getLogger(__name__)
5 7
6 8 class InspectProxy(object):
7 9 """
8 10 Proxy to the `inspect` module that allows us to use the pyramid include
9 11 mechanism for cythonized modules without source file.
10 12 """
11 13
12 14 def _get_cyfunction_func_code(self, cyfunction):
13 15 """
14 16 Unpack the `func_code` attribute of a cython function.
15 17 """
16 18 if inspect.ismethod(cyfunction):
17 19 cyfunction = cyfunction.im_func
18 20 return getattr(cyfunction, 'func_code')
19 21
20 22 def getmodule(self, *args, **kwds):
21 23 """
22 24 Simple proxy to `inspect.getmodule`.
23 25 """
24 26 return inspect.getmodule(*args, **kwds)
25 27
26 28 def getsourcefile(self, obj):
27 29 """
28 30 Proxy to `inspect.getsourcefile` or `inspect.getfile` depending on if
29 31 it's called to look up the source file that contains the magic pyramid
30 32 `includeme` callable.
31 33
32 34 For cythonized modules the source file may be deleted. Therefore we
33 35 return the result of `inspect.getfile` instead. In the case of the
34 36 `configurator.include` method this is OK, because the result is passed
35 37 to `os.path.dirname` which strips the file name. So it doesn't matter
36 38 if we return the path to the source file or another file in the same
37 39 directory.
38 40 """
39 41 # Check if it's called to look up the source file that contains the
40 42 # magic pyramid `includeme` callable.
41 43 if getattr(obj, '__name__') == 'includeme':
42 44 try:
43 45 return inspect.getfile(obj)
44 46 except TypeError as e:
45 47 # Cython functions are not recognized as functions by the
46 48 # inspect module. We have to unpack the func_code attribute
47 49 # ourself.
48 50 if 'cyfunction' in e.message:
49 51 obj = self._get_cyfunction_func_code(obj)
50 52 return inspect.getfile(obj)
51 53 raise
52 54 else:
53 55 return inspect.getsourcefile(obj)
54 56
55 57
56 58 class CythonCompatConfigurator(Configurator):
57 59 """
58 60 Customized configurator to replace the inspect class attribute with
59 61 a custom one that is cython compatible.
60 62 """
61 63 inspect = InspectProxy()
64
65
66 def register_appenlight_plugin(config, plugin_name, plugin_config):
67 def register():
68 log.warning('Registering plugin: {}'.format(plugin_name))
69 if plugin_name not in config.registry.appenlight_plugins:
70 config.registry.appenlight_plugins[plugin_name] = {
71 'javascript': None,
72 'static': None,
73 'css': None,
74 'top_nav': None,
75 'celery_tasks': None,
76 'celery_beats': None,
77 'fulltext_indexer': None,
78 'sqlalchemy_migrations': None,
79 'default_values_setter': None,
80 'resource_types': [],
81 'url_gen': None
82 }
83 config.registry.appenlight_plugins[plugin_name].update(
84 plugin_config)
85 # inform AE what kind of resource types we have available
86 # so we can avoid failing when a plugin is removed but data
87 # is still present in the db
88 if plugin_config.get('resource_types'):
89 config.registry.resource_types.extend(
90 plugin_config['resource_types'])
91
92 config.action('appenlight_plugin={}'.format(plugin_name), register)
General Comments 0
You need to be logged in to leave comments. Login now