monkeypatch quixote with thread-aware functions (#6735)

This commit is contained in:
Frédéric Péters 2017-07-04 08:34:26 +02:00
parent 01b280aa2c
commit 079ccafc75
14 changed files with 164 additions and 43 deletions

View File

@ -1361,7 +1361,7 @@ def test_sms_with_passerelle(pub):
item.body = 'my message'
with mock.patch('wcs.wscalls.get_secret_and_orig') as mocked_secret_and_orig:
mocked_secret_and_orig.return_value = ('secret', 'localhost')
with mock.patch('wcs.wscalls.http_post_request') as mocked_http_post:
with mock.patch('qommon.misc.http_post_request') as mocked_http_post:
mocked_http_post.return_value = ('response', '200', 'data', 'headers')
item.perform(formdata)
url, payload = mocked_http_post.call_args[0]

View File

@ -7,6 +7,7 @@ import random
import psycopg2
import pytest
import shutil
import sys
import threading
import urlparse
@ -22,6 +23,7 @@ from wcs import publisher, compat
from wcs.qommon.http_request import HTTPRequest
from wcs.users import User
from wcs.tracking_code import TrackingCode
import wcs.qommon.emails
import wcs.qommon.sms
import qommon.sms
from qommon.errors import ConnectionError
@ -195,21 +197,19 @@ class EmailsMocking(object):
return len(self.emails)
def __enter__(self):
import wcs.qommon.emails
import qommon.emails
self.wcs_create_smtp_server = wcs.qommon.emails.create_smtp_server
self.qommon_create_smtp_server = qommon.emails.create_smtp_server
self.wcs_create_smtp_server = sys.modules['wcs.qommon.emails'].create_smtp_server
self.qommon_create_smtp_server = sys.modules['qommon.emails'].create_smtp_server
wcs.qommon.emails.create_smtp_server = self.create_smtp_server
qommon.emails.create_smtp_server = self.create_smtp_server
sys.modules['wcs.qommon.emails'].create_smtp_server = self.create_smtp_server
sys.modules['qommon.emails'].create_smtp_server = self.create_smtp_server
self.emails = {}
return self
def __exit__(self, exc_type, exc_value, tb):
del self.emails
wcs.qommon.emails.create_smtp_server = self.wcs_create_smtp_server
qommon.emails.create_smtp_server = self.qommon_create_smtp_server
sys.modules['wcs.qommon.emails'].create_smtp_server = self.wcs_create_smtp_server
sys.modules['qommon.emails'].create_smtp_server = self.qommon_create_smtp_server
class MockSubstitutionVariables(object):

View File

@ -18,7 +18,10 @@ import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
import monkeypatch
import qommon
sys.modules['qommon'] = sys.modules['wcs.qommon']
import qommon.form
sys.modules['form'] = qommon.form

View File

@ -41,11 +41,11 @@ from qommon.evalutils import make_datetime
from qommon.misc import C_, ellipsize
from qommon.afterjobs import AfterJob
from qommon import emails
import qommon.sms
from qommon import errors
from qommon import ezt
from qommon import ods
from qommon.form import *
from qommon.sms import SMS
from qommon.storage import (Equal, NotEqual, LessOrEqual, GreaterOrEqual, Or,
Intersects, ILike, FtsMatch, Contains, Null)
@ -131,7 +131,7 @@ class SendCodeFormdefDirectory(Directory):
if get_publisher().use_sms_feature:
sms_cfg = get_cfg('sms', {})
mode = sms_cfg.get('mode', 'none')
sms_class = SMS.get_sms_class(mode)
sms_class = qommon.sms.SMS.get_sms_class(mode)
if sms_class:
form.add(StringWidget, 'sms', title=_('SMS Number'), required=False)
form.add(RadiobuttonsWidget, 'method',

View File

@ -36,21 +36,6 @@ from .publisher import WcsPublisher
from .qommon.http_request import HTTPRequest
from .qommon.http_response import HTTPResponse
def init_publisher_if_needed():
if get_publisher() is not None:
return get_publisher()
# initialize publisher in first request
config = ConfigParser.ConfigParser()
if settings.WCS_LEGACY_CONFIG_FILE:
config.read(settings.WCS_LEGACY_CONFIG_FILE)
if hasattr(settings, 'WCS_EXTRA_MODULES') and settings.WCS_EXTRA_MODULES:
if not config.has_section('extra'):
config.add_section('extra')
for i, extra in enumerate(settings.WCS_EXTRA_MODULES):
config.set('extra', 'cmd_line_extra_%d' % i, extra)
CompatWcsPublisher.configure(config)
return CompatWcsPublisher.create_publisher()
class TemplateWithFallbackView(TemplateView):
quixote_response = None
@ -192,7 +177,8 @@ class CompatWcsPublisher(WcsPublisher):
quixote_lock = Lock()
def quixote(request):
with quixote_lock:
#with quixote_lock:
if True:
pub = get_publisher()
compat_request = CompatHTTPRequest(request)
return pub.process_request(compat_request)

View File

@ -14,12 +14,19 @@
# 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 .compat import init_publisher_if_needed, CompatHTTPRequest
import thread
import threading
from quixote import get_publisher
from .compat import CompatHTTPRequest, CompatWcsPublisher
class PublisherInitialisationMiddleware(object):
'''Initializes the publisher according to the request server name.'''
def process_request(self, request):
pub = init_publisher_if_needed()
pub = get_publisher()
if not pub:
pub = CompatWcsPublisher.create_publisher()
compat_request = CompatHTTPRequest(request)
pub.init_publish(compat_request)
pub._set_request(compat_request)

90
wcs/monkeypatch.py Normal file
View File

@ -0,0 +1,90 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2017 Entr'ouvert
#
# 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 2 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/>.
import threading
import types
import urlparse
import quixote
import quixote.publish
_thread_local = threading.local()
cleanup_orig = quixote.publish.cleanup
PublisherOrig = quixote.publish.Publisher
class Publisher(quixote.publish.Publisher):
def __init__(self, root_directory, *args, **kwargs):
try:
PublisherOrig.__init__(self, root_directory, *args, **kwargs)
except RuntimeError:
pass
_thread_local.publisher = self
self.root_directory = root_directory
self._request = None
def get_publisher():
return getattr(_thread_local, 'publisher', None)
def get_request():
return _thread_local.publisher.get_request()
def get_response():
return _thread_local.publisher.get_request().response
def get_field(name, default=None):
return _thread_local.publisher.get_request().get_field(name, default)
def get_cookie(name, default=None):
return _thread_local.publisher.get_request().get_cookie(name, default)
def get_path(n=0):
return _thread_local.publisher.get_request().get_path(n)
def redirect(location, permanent=False):
"""(location : string, permanent : boolean = false) -> string
Create a redirection response. If the location is relative, then it
will automatically be made absolute. The return value is an HTML
document indicating the new URL (useful if the client browser does
not honor the redirect).
"""
request = _thread_local.publisher.get_request()
location = urlparse.urljoin(request.get_url(), str(location))
return request.response.redirect(location, permanent)
def get_session():
return _thread_local.publisher.get_request().session
def get_session_manager():
return _thread_local.publisher.session_manager
def get_user():
session = _thread_local.publisher.get_request().session
if session is None:
return None
else:
return session.user
def cleanup():
cleanup_orig()
_thread_local.publisher = None
for key, value in locals().items():
if type(value) in (types.FunctionType, types.TypeType, types.ClassType):
setattr(quixote, key, value)
setattr(quixote.publish, key, value)

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import ConfigParser
import django.apps
from django.conf import settings
from quixote import get_publisher
try:
@ -33,7 +36,30 @@ def ngettext(*args):
return message
return unicode(pub.ngettext(*args), 'utf-8').encode(pub.site_charset)
from publisher import get_cfg, get_logger
from publisher import get_cfg, get_logger, get_publisher_class
import publisher
publisher._ = _
class AppConfig(django.apps.AppConfig):
name = 'wcs.qommon'
def ready(self):
config = ConfigParser.ConfigParser()
if settings.WCS_LEGACY_CONFIG_FILE:
config.read(settings.WCS_LEGACY_CONFIG_FILE)
if hasattr(settings, 'WCS_EXTRA_MODULES') and settings.WCS_EXTRA_MODULES:
if not config.has_section('extra'):
config.add_section('extra')
for i, extra in enumerate(settings.WCS_EXTRA_MODULES):
config.set('extra', 'cmd_line_extra_%d' % i, extra)
get_publisher_class().configure(config)
get_publisher_class().register_tld_names = True
get_publisher_class().init_publisher_class()
default_app_config = 'wcs.qommon.AppConfig'
if lasso:
if not hasattr(lasso, 'SAML2_SUPPORT'):

View File

@ -21,7 +21,6 @@ import quixote
from quixote.errors import *
from quixote.html import TemplateIO, htmltext
from qommon import _
import template
@ -31,6 +30,7 @@ class AccessForbiddenError(AccessError):
self.location_hint = location_hint
def render(self):
from qommon import _
if self.public_msg:
return template.error_page(self.public_msg, _('Access Forbidden'),
continue_to = (get_publisher().get_root_url(), _('the homepage')),
@ -63,6 +63,7 @@ class EmailError(Exception):
class InternalServerError(object):
def render(self):
from qommon import _
template.html_top(_('Oops, the server borked severely'))
r = TemplateIO(html=True)
@ -115,6 +116,7 @@ TraversalError.description = N_(
def format_publish_error(exc):
from qommon import _
if getattr(exc, 'public_msg', None):
return template.error_page(exc.format(), _(exc.title))
else:

View File

@ -1187,7 +1187,7 @@ class PasswordAuthMethod(AuthMethod):
@classmethod
def register(cls):
rdb = get_publisher().backoffice_directory_class
rdb = get_publisher_class().backoffice_directory_class
if rdb:
rdb.register_directory('accounts', AccountsDirectory())

View File

@ -57,8 +57,6 @@ import storage
import strftime
import urllib
from qommon import _
class ImmediateRedirectException(Exception):
def __init__(self, location):
self.location = location
@ -802,16 +800,25 @@ class QommonPublisher(Publisher, object):
cls.register_cronjob(CronJob(cls.clean_afterjobs, minutes=[random.randint(0, 59)]))
cls.register_cronjob(CronJob(cls.clean_tempfiles, minutes=[random.randint(0, 59)]))
register_cron = False
register_tld_names = False
_initialized = False
@classmethod
def create_publisher(cls, register_cron=True, register_tld_names=True):
def init_publisher_class(cls):
if cls._initialized:
return
cls._initialized = True
cls.load_extra_dirs()
cls.load_translations()
if register_cron:
if cls.register_cron:
cls.register_cronjobs()
if register_tld_names:
if cls.register_tld_names:
cls.load_effective_tld_names()
@classmethod
def create_publisher(cls, **kwargs):
publisher = cls(cls.root_directory_class(),
session_cookie_name = cls.APP_NAME,
session_cookie_path = '/',

View File

@ -15,7 +15,6 @@
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from quixote.html import htmltext, TemplateIO
from qommon import _
class Substitutions(object):
substitutions_dict = {}
@ -61,6 +60,7 @@ class Substitutions(object):
@classmethod
def get_substitution_html_table(cls):
from qommon import _
r = TemplateIO(html=True)
r += htmltext('<table id="substvars">')
r += htmltext('<thead><tr><th>%s</th><th>%s</th><th>%s</th></tr></thead>' % (

View File

@ -24,7 +24,6 @@ from quixote.directory import Directory
from quixote.util import StaticDirectory, StaticFile
from quixote.html import htmltext, htmlescape, TemplateIO
from qommon import _
import errors
import ezt
@ -227,6 +226,7 @@ def html_top(title=None, default_org=None):
def error_page(error_message, error_title = None, exception = None, continue_to = None,
location_hint = None):
from qommon import _
if not error_title:
error_title = _('Error')
if exception:

View File

@ -24,11 +24,11 @@ import xml.etree.ElementTree as ET
from quixote import get_publisher
from qommon import _
from qommon.misc import (simplify, http_get_page, http_post_request,
get_variadic_url, JSONEncoder, json_loads)
from qommon.misc import simplify, get_variadic_url, JSONEncoder, json_loads
from qommon.xml_storage import XmlStorableObject
from qommon.form import (CompositeWidget, StringWidget, WidgetDict,
ComputedExpressionWidget, RadiobuttonsWidget, CheckboxWidget)
import qommon.misc
from wcs.api_utils import sign_url, get_secret_and_orig, MissingSecret
from wcs.workflows import WorkflowStatusItem
@ -106,10 +106,10 @@ def call_webservice(url, qs_data=None, request_signature_key=None,
# increase timeout for huge loads, one second every 65536
# bytes, to match a country 512kbps DSL line.
timeout += len(payload) / 65536
response, status, data, auth_header = http_post_request(
response, status, data, auth_header = qommon.misc.http_post_request(
url, payload, headers=headers, timeout=timeout)
else:
response, status, data, auth_header = http_get_page(
response, status, data, auth_header = qommon.misc.http_get_page(
url, headers=headers, timeout=TIMEOUT)
return (response, status, data)