Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/app.py: 100%
66 statements
« prev ^ index » next coverage.py v7.6.10, created at 2024-12-28 21:19 -0600
« prev ^ index » next coverage.py v7.6.10, created at 2024-12-28 21:19 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Application
25"""
27import logging
28import os
30from wuttjamaican.app import AppProvider
31from wuttjamaican.conf import make_config
33from asgiref.wsgi import WsgiToAsgi
34from pyramid.config import Configurator
36import wuttaweb.db
37from wuttaweb.auth import WuttaSecurityPolicy
40log = logging.getLogger(__name__)
43class WebAppProvider(AppProvider):
44 """
45 The :term:`app provider` for WuttaWeb. This adds some methods to
46 the :term:`app handler`, which are specific to web apps. It also
47 registers some :term:`email templates <email template>` for the
48 app, etc.
49 """
50 email_modules = ['wuttaweb.emails']
51 email_templates = ['wuttaweb:email-templates']
53 def get_web_handler(self, **kwargs):
54 """
55 Get the configured "web" handler for the app.
57 Specify a custom handler in your config file like this:
59 .. code-block:: ini
61 [wutta]
62 web.handler_spec = poser.web.handler:PoserWebHandler
64 :returns: Instance of :class:`~wuttaweb.handler.WebHandler`.
65 """
66 if 'web_handler' not in self.__dict__:
67 spec = self.config.get(f'{self.appname}.web.handler_spec',
68 default='wuttaweb.handler:WebHandler')
69 self.web_handler = self.app.load_object(spec)(self.config)
70 return self.web_handler
73def make_wutta_config(settings, config_maker=None, **kwargs):
74 """
75 Make a WuttaConfig object from the given settings.
77 Note that ``settings`` dict will (typically) correspond to the
78 ``[app:main]`` section of your config file.
80 Regardless, the ``settings`` must contain a special key/value
81 which is needed to identify the location of the config file.
82 Assuming the typical scenario then, your config file should have
83 an entry like this:
85 .. code-block:: ini
87 [app:main]
88 wutta.config = %(__file__)s
90 The ``%(__file__)s`` is auto-replaced with the config file path,
91 so ultimately ``settings`` would contain something like (at
92 minimum)::
94 {'wutta.config': '/path/to/config/file'}
96 If this config file path cannot be discovered, an error is raised.
97 """
98 wutta_config = settings.get('wutta_config')
99 if not wutta_config:
101 # validate config file path
102 path = settings.get('wutta.config')
103 if not path or not os.path.exists(path):
104 raise ValueError("Please set 'wutta.config' in [app:main] "
105 "section of config to the path of your "
106 "config file. Lame, but necessary.")
108 # make config, add to settings
109 config_maker = config_maker or make_config
110 wutta_config = config_maker(path, **kwargs)
111 settings['wutta_config'] = wutta_config
113 # configure database sessions
114 if hasattr(wutta_config, 'appdb_engine'):
115 wuttaweb.db.Session.configure(bind=wutta_config.appdb_engine)
117 return wutta_config
120def make_pyramid_config(settings):
121 """
122 Make and return a Pyramid config object from the given settings.
124 The config is initialized with certain features deemed useful for
125 all apps.
127 :returns: Instance of
128 :class:`pyramid:pyramid.config.Configurator`.
129 """
130 settings.setdefault('fanstatic.versioning', 'true')
131 settings.setdefault('mako.directories', ['wuttaweb:templates'])
132 settings.setdefault('pyramid_deform.template_search_path',
133 'wuttaweb:templates/deform')
135 pyramid_config = Configurator(settings=settings)
137 # configure user authorization / authentication
138 pyramid_config.set_security_policy(WuttaSecurityPolicy())
140 # require CSRF token for POST
141 pyramid_config.set_default_csrf_options(require_csrf=True,
142 token='_csrf',
143 header='X-CSRF-TOKEN')
145 pyramid_config.include('pyramid_beaker')
146 pyramid_config.include('pyramid_deform')
147 pyramid_config.include('pyramid_fanstatic')
148 pyramid_config.include('pyramid_mako')
149 pyramid_config.include('pyramid_tm')
151 # add some permissions magic
152 pyramid_config.add_directive('add_wutta_permission_group',
153 'wuttaweb.auth.add_permission_group')
154 pyramid_config.add_directive('add_wutta_permission',
155 'wuttaweb.auth.add_permission')
157 return pyramid_config
160def main(global_config, **settings):
161 """
162 Make and return the WSGI application, per given settings.
164 This function is designed to be called via Paste, hence it does
165 require params and therefore can't be used directly as app factory
166 for general WSGI servers. For the latter see
167 :func:`make_wsgi_app()` instead.
169 And this *particular* function is not even that useful, it only
170 constructs an app with minimal views built-in to WuttaWeb. Most
171 apps will define their own ``main()`` function (e.g. as
172 ``poser.web.app:main``), similar to this one but with additional
173 views and other config.
174 """
175 wutta_config = make_wutta_config(settings)
176 pyramid_config = make_pyramid_config(settings)
178 pyramid_config.include('wuttaweb.static')
179 pyramid_config.include('wuttaweb.subscribers')
180 pyramid_config.include('wuttaweb.views')
182 return pyramid_config.make_wsgi_app()
185def make_wsgi_app(main_app=None, config=None):
186 """
187 Make and return a WSGI app, using the given Paste app factory.
189 See also :func:`make_asgi_app()` for the ASGI equivalent.
191 This function could be used directly for general WSGI servers
192 (e.g. uvicorn), ***if*** you just want the built-in :func:`main()`
193 app factory.
195 But most likely you do not, in which case you must define your own
196 function and call this one with your preferred app factory::
198 from wuttaweb.app import make_wsgi_app
200 def my_main(global_config, **settings):
201 # TODO: build your app
202 pass
204 def make_my_wsgi_app():
205 return make_wsgi_app(my_main)
207 So ``make_my_wsgi_app()`` could then be used as-is for general
208 WSGI servers. However, note that this approach will require
209 setting the ``WUTTA_CONFIG_FILES`` environment variable, unless
210 running via :ref:`wutta-webapp`.
212 :param main_app: Either a Paste-compatible app factory, or
213 :term:`spec` for one. If not specified, the built-in
214 :func:`main()` is assumed.
216 :param config: Optional :term:`config object`. If not specified,
217 one is created based on ``WUTTA_CONFIG_FILES`` environment
218 variable.
219 """
220 if not config:
221 config = make_config()
222 app = config.get_app()
224 # extract pyramid settings
225 settings = config.get_dict('app:main')
227 # keep same config object
228 settings['wutta_config'] = config
230 # determine the app factory
231 if isinstance(main_app, str):
232 make_wsgi_app = app.load_object(main_app)
233 elif callable(main_app):
234 make_wsgi_app = main_app
235 else:
236 raise ValueError("main_app must be spec or callable")
238 # construct a pyramid app "per usual"
239 return make_wsgi_app({}, **settings)
242def make_asgi_app(main_app=None, config=None):
243 """
244 Make and return a ASGI app, using the given Paste app factory.
246 This works the same as :func:`make_wsgi_app()` and should be
247 called in the same way etc.
248 """
249 wsgi_app = make_wsgi_app(main_app, config=config)
250 return WsgiToAsgi(wsgi_app)