# HG changeset patch # User Denis Laxalde # Date 1553175234 -3600 # Node ID 9d88e1177c35e04bc8a490f859b9b48c97170ba7 # Parent 7276f1c89ddd207904f879d303ee2b50b51644fb Remove Twisted web server Twisted web server is not used anymore and has been superseded by pyramid many years ago. Furthermore, our usage is not compatible with Python 3. So we drop the "etwist" sub-package. As a consequence, "all-in-one" configuration type gets dropped as it was Twisted-specific. We resurrect it in cubicweb/pyramid/config.py by only keeping options used by the "pyramid". Similarly, we introduce a AllInOneCreateHandler in cubicweb/pyramid/pyramidctl.py that is basically the one that lived in cubicweb/etwist/twctl.py and is used to create the "all-in-one" instance. Added a TODO here about "pyramid.ini" that could be generated at the end of bootstrap() method. In cubicweb/devtools/httptest.py, CubicWebServerTC is now equivalent to CubicWebWsgiTC and the latter is dropped. diff -r 7276f1c89ddd -r 9d88e1177c35 MANIFEST.in --- a/MANIFEST.in Thu Mar 21 12:05:30 2019 +0100 +++ b/MANIFEST.in Thu Mar 21 14:33:54 2019 +0100 @@ -50,8 +50,6 @@ recursive-include cubicweb/devtools/test/data *.py *.txt *.js *.po.ref recursive-include cubicweb/entities/test *.py recursive-include cubicweb/entities/test/data *.py -recursive-include cubicweb/etwist/test *.py -recursive-include cubicweb/etwist/test/data *.py recursive-include cubicweb/ext/test *.py recursive-include cubicweb/ext/test/data *.py recursive-include cubicweb/hooks/test *.py diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb.spec --- a/cubicweb.spec Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb.spec Thu Mar 21 14:33:54 2019 +0100 @@ -30,7 +30,6 @@ Requires: %{python}-passlib Requires: %{python}-lxml Requires: %{python}-unittest2 >= 0.7.0 -Requires: %{python}-twisted-web < 16.0.0 Requires: %{python}-markdown Requires: pytz # the schema view uses `dot'; at least on el5, png output requires graphviz-gd diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/cwconfig.py --- a/cubicweb/cwconfig.py Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb/cwconfig.py Thu Mar 21 14:33:54 2019 +0100 @@ -670,7 +670,7 @@ @classmethod def load_available_configs(cls): - for confmod in ('web.webconfig', 'etwist.twconfig', + for confmod in ('web.webconfig', 'server.serverconfig', 'pyramid.config'): try: __import__('cubicweb.%s' % confmod) @@ -681,7 +681,7 @@ @classmethod def load_cwctl_plugins(cls): cls.cls_adjust_sys_path() - for ctlmod in ('web.webctl', 'etwist.twctl', 'server.serverctl', + for ctlmod in ('web.webctl', 'server.serverctl', 'devtools.devctl', 'pyramid.pyramidctl'): try: __import__('cubicweb.%s' % ctlmod) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/devtools/httptest.py --- a/cubicweb/devtools/httptest.py Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb/devtools/httptest.py Thu Mar 21 14:33:54 2019 +0100 @@ -26,7 +26,6 @@ import threading import socket -from six import PY3 from six.moves import range, http_client from six.moves.urllib.parse import urlparse @@ -64,7 +63,7 @@ class _CubicWebServerTC(CubicWebTC): - """Class for running a Twisted-based test web server. + """Base class for running a test web server. """ ports_range = range(7000, 8000) @@ -133,52 +132,6 @@ class CubicWebServerTC(_CubicWebServerTC): def start_server(self): - if PY3: - self.skipTest('not using twisted on python3') - from twisted.internet import reactor - from cubicweb.etwist.server import run - # use a semaphore to avoid starting test while the http server isn't - # fully initilialized - semaphore = threading.Semaphore(0) - def safe_run(*args, **kwargs): - try: - run(*args, **kwargs) - finally: - semaphore.release() - - reactor.addSystemEventTrigger('after', 'startup', semaphore.release) - t = threading.Thread(target=safe_run, name='cubicweb_test_web_server', - args=(self.config, True), kwargs={'repo': self.repo}) - self.web_thread = t - t.start() - semaphore.acquire() - if not self.web_thread.isAlive(): - # XXX race condition with actual thread death - raise RuntimeError('Could not start the web server') - #pre init utils connection - parseurl = urlparse(self.config['base-url']) - assert parseurl.port == self.config['port'], (self.config['base-url'], self.config['port']) - self._web_test_cnx = http_client.HTTPConnection(parseurl.hostname, - parseurl.port) - self._ident_cookie = None - - def stop_server(self, timeout=15): - """Stop the webserver, waiting for the thread to return""" - from twisted.internet import reactor - if self._web_test_cnx is None: - self.web_logout() - self._web_test_cnx.close() - try: - reactor.stop() - self.web_thread.join(timeout) - assert not self.web_thread.isAlive() - - finally: - reactor.__init__() - - -class CubicWebWsgiTC(CubicWebServerTC): - def start_server(self): from cubicweb.wsgi.handler import CubicWebWSGIApplication from wsgiref import simple_server from six.moves import queue diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/devtools/test/unittest_httptest.py --- a/cubicweb/devtools/test/unittest_httptest.py Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb/devtools/test/unittest_httptest.py Thu Mar 21 14:33:54 2019 +0100 @@ -20,10 +20,10 @@ from six.moves import http_client from logilab.common.testlib import Tags -from cubicweb.devtools.httptest import CubicWebServerTC, CubicWebWsgiTC +from cubicweb.devtools.httptest import CubicWebServerTC -class TwistedCWAnonTC(CubicWebServerTC): +class WsgiCWAnonTC(CubicWebServerTC): def test_response(self): try: @@ -40,47 +40,7 @@ self.fail('no mention of base url in retrieved page') -class TwistedCWIdentTC(CubicWebServerTC): - test_db_id = 'httptest-cwident' - anonymous_allowed = False - tags = CubicWebServerTC.tags | Tags(('auth',)) - - def test_response_denied(self): - response = self.web_get() - self.assertEqual(response.status, http_client.FORBIDDEN) - - def test_login(self): - response = self.web_get() - if response.status != http_client.FORBIDDEN: - self.skipTest('Already authenticated, "test_response_denied" must have failed') - # login - self.web_login(self.admlogin, self.admpassword) - response = self.web_get() - self.assertEqual(response.status, http_client.OK, response.body) - # logout - self.web_logout() - response = self.web_get() - self.assertEqual(response.status, http_client.FORBIDDEN, response.body) - - -class WsgiCWAnonTC(CubicWebWsgiTC): - - def test_response(self): - try: - response = self.web_get() - except http_client.NotConnected as ex: - self.fail("Can't connection to test server: %s" % ex) - - def test_response_anon(self): - response = self.web_get() - self.assertEqual(response.status, http_client.OK) - - def test_base_url(self): - if self.config['base-url'] not in self.web_get().read().decode('ascii'): - self.fail('no mention of base url in retrieved page') - - -class WsgiCWIdentTC(CubicWebWsgiTC): +class WsgiCWIdentTC(CubicWebServerTC): test_db_id = 'httptest-cwident' anonymous_allowed = False tags = CubicWebServerTC.tags | Tags(('auth',)) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/__init__.py --- a/cubicweb/etwist/__init__.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -""" CW - nevow/twisted client - -""" diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/http.py --- a/cubicweb/etwist/http.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -"""twisted server for CubicWeb web instances - -:organization: Logilab -:copyright: 2001-2011 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" - - - - -class HTTPResponse(object): - """An object representing an HTTP Response to be sent to the client. - """ - def __init__(self, twisted_request, code=None, headers=None, stream=None): - self._headers_out = headers - self._twreq = twisted_request - self._stream = stream - self._code = code - - self._init_headers() - self._finalize() - - def _init_headers(self): - if self._headers_out is None: - return - # initialize headers - for k, values in self._headers_out.getAllRawHeaders(): - self._twreq.responseHeaders.setRawHeaders(k, values) - # add content-length if not present - if (self._headers_out.getHeader('content-length') is None - and self._stream is not None): - self._twreq.setHeader('content-length', len(self._stream)) - - def _finalize(self): - # cw_failed is set on errors such as "connection aborted by client". In - # such cases, req.finish() was already called and calling it a twice - # would crash - if getattr(self._twreq, 'cw_failed', False): - return - # we must set code before writing anything, else it's too late - if self._code is not None: - self._twreq.setResponseCode(self._code) - if self._stream is not None: - self._twreq.write(str(self._stream)) - self._twreq.finish() - - def __repr__(self): - return "<%s.%s code=%d>" % (self.__module__, self.__class__.__name__, self._code) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/request.py --- a/cubicweb/etwist/request.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -"""Twisted request handler for CubicWeb""" - -from six import text_type - -from cubicweb.web.request import CubicWebRequestBase - - -class CubicWebTwistedRequestAdapter(CubicWebRequestBase): - """ from twisted .req to cubicweb .form - req.files are put into .form[] - """ - def __init__(self, req, vreg): - self._twreq = req - super(CubicWebTwistedRequestAdapter, self).__init__( - vreg, req.args, headers=req.received_headers) - for key, name_stream_list in req.files.items(): - for name, stream in name_stream_list: - if name is not None: - name = text_type(name, self.encoding) - self.form.setdefault(key, []).append((name, stream)) - # 3.16.4 backward compat - if len(self.form[key]) == 1: - self.form[key] = self.form[key][0] - self.content = self._twreq.content # stream - - def http_method(self): - """returns 'POST', 'GET', 'HEAD', etc.""" - return self._twreq.method - - def relative_path(self, includeparams=True): - """return the normalized path of the request (ie at least relative to - the instance's root, but some other normalization may be needed so that - the returned path may be used to compare to generated urls - - :param includeparams: - boolean indicating if GET form parameters should be kept in the path - """ - path = self._twreq.uri[1:] # remove the root '/' - if not includeparams: - path = path.split('?', 1)[0] - return path diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/server.py --- a/cubicweb/etwist/server.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -"""twisted server for CubicWeb web instances""" - -import sys -import traceback -import threading -from cgi import FieldStorage, parse_header -from functools import partial -import warnings - -from cubicweb.statsd_logger import statsd_timeit - -from twisted.internet import reactor, task, threads -from twisted.web import http, server -from twisted.web import resource -from twisted.web.server import NOT_DONE_YET - - -from logilab.mtconverter import xml_escape -from logilab.common.decorators import monkeypatch - -from cubicweb import ConfigurationError, CW_EVENT_MANAGER -from cubicweb.utils import json_dumps -from cubicweb.web import DirectResponse -from cubicweb.web.application import CubicWebPublisher -from cubicweb.etwist.request import CubicWebTwistedRequestAdapter -from cubicweb.etwist.http import HTTPResponse - - -def start_task(interval, func): - lc = task.LoopingCall(func) - # wait until interval has expired to actually start the task, else we have - # to wait all tasks to be finished for the server to be actually started - lc.start(interval, now=False) - - -class CubicWebRootResource(resource.Resource): - def __init__(self, config, repo): - resource.Resource.__init__(self) - self.config = config - # instantiate publisher here and not in init_publisher to get some - # checks done before daemonization (eg versions consistency) - self.appli = CubicWebPublisher(repo, config) - self.base_url = config['base-url'] - global MAX_POST_LENGTH - MAX_POST_LENGTH = config['max-post-length'] - - def init_publisher(self): - config = self.config - # when we have an in-memory repository, clean unused sessions every XX - # seconds and properly shutdown the server - if config['repository-uri'] == 'inmemory://': - if config.mode != 'test': - reactor.addSystemEventTrigger('before', 'shutdown', - self.shutdown_event) - warnings.warn( - 'twisted server does not start repository looping tasks anymore; ' - 'use the standalone "scheduler" command if needed' - ) - self.set_url_rewriter() - CW_EVENT_MANAGER.bind('after-registry-reload', self.set_url_rewriter) - - def start_service(self): - start_task(self.appli.session_handler.clean_sessions_interval, - self.appli.session_handler.clean_sessions) - - def set_url_rewriter(self): - self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter') - - def shutdown_event(self): - """callback fired when the server is shutting down to properly - clean opened sessions - """ - self.appli.repo.shutdown() - - def getChild(self, path, request): - """Indicate which resource to use to process down the URL's path""" - return self - - def on_request_finished_ko(self, request, reason): - # annotate the twisted request so that we're able later to check for - # failure without having to dig into request's internal attributes such - # as _disconnected - request.cw_failed = True - self.warning('request finished abnormally: %s', reason) - - def render(self, request): - """Render a page from the root resource""" - finish_deferred = request.notifyFinish() - finish_deferred.addErrback(partial(self.on_request_finished_ko, request)) - # reload modified files in debug mode - if self.config.debugmode: - self.config.uiprops.reload_if_needed() - self.appli.vreg.reload_if_needed() - if self.config['profile']: # default profiler don't trace threads - return self.render_request(request) - else: - deferred = threads.deferToThread(self.render_request, request) - return NOT_DONE_YET - - @statsd_timeit - def render_request(self, request): - try: - # processing HUGE files (hundred of megabytes) in http.processReceived - # blocks other HTTP requests processing - # due to the clumsy & slow parsing algorithm of cgi.FieldStorage - # so we deferred that part to the cubicweb thread - request.process_multipart() - return self._render_request(request) - except Exception: - trace = traceback.format_exc() - return HTTPResponse(stream='
%s
' % xml_escape(trace), - code=500, twisted_request=request) - - def _render_request(self, request): - origpath = request.path - host = request.host - if self.url_rewriter is not None: - # XXX should occur before authentication? - path = self.url_rewriter.rewrite(host, origpath, request) - request.uri.replace(origpath, path, 1) - req = CubicWebTwistedRequestAdapter(request, self.appli.vreg) - try: - ### Try to generate the actual request content - content = self.appli.handle_request(req) - except DirectResponse as ex: - return ex.response - # at last: create twisted object - return HTTPResponse(code = req.status_out, - headers = req.headers_out, - stream = content, - twisted_request=req._twreq) - - # these are overridden by set_log_methods below - # only defining here to prevent pylint from complaining - @classmethod - def debug(cls, msg, *a, **kw): - pass - info = warning = error = critical = exception = debug - - -JSON_PATHS = set(('json',)) -FRAME_POST_PATHS = set(('validateform',)) - -orig_gotLength = http.Request.gotLength -@monkeypatch(http.Request) -def gotLength(self, length): - orig_gotLength(self, length) - if length > MAX_POST_LENGTH: # length is 0 on GET - path = self.channel._path.split('?', 1)[0].rstrip('/').rsplit('/', 1)[-1] - self.clientproto = 'HTTP/1.1' # not yet initialized - self.channel.persistent = 0 # force connection close on cleanup - self.setResponseCode(http.REQUEST_ENTITY_TOO_LARGE) - if path in JSON_PATHS: # XXX better json path detection - self.setHeader('content-type',"application/json") - body = json_dumps({'reason': 'request max size exceeded'}) - elif path in FRAME_POST_PATHS: # XXX better frame post path detection - self.setHeader('content-type',"text/html") - body = ('' % json_dumps( (False, 'request max size exceeded', None) )) - else: - self.setHeader('content-type',"text/html") - body = ("Processing Failed" - "request max size exceeded") - self.setHeader('content-length', str(len(body))) - self.write(body) - # see request.finish(). Done here since we get error due to not full - # initialized request - self.finished = 1 - if not self.queued: - self._cleanup() - for d in self.notifications: - d.callback(None) - self.notifications = [] - -@monkeypatch(http.Request) -def requestReceived(self, command, path, version): - """Called by channel when all data has been received. - - This method is not intended for users. - """ - self.content.seek(0, 0) - self.args = {} - self.files = {} - self.stack = [] - self.method, self.uri = command, path - self.clientproto = version - x = self.uri.split('?', 1) - if len(x) == 1: - self.path = self.uri - else: - self.path, argstring = x - self.args = http.parse_qs(argstring, 1) - # cache the client and server information, we'll need this later to be - # serialized and sent with the request so CGIs will work remotely - self.client = self.channel.transport.getPeer() - self.host = self.channel.transport.getHost() - # Argument processing - ctype = self.getHeader('content-type') - self._do_process_multipart = False - if self.method == "POST" and ctype: - key, pdict = parse_header(ctype) - if key == 'application/x-www-form-urlencoded': - self.args.update(http.parse_qs(self.content.read(), 1)) - self.content.seek(0) - elif key == 'multipart/form-data': - # defer this as it can be extremely time consumming - # with big files - self._do_process_multipart = True - self.process() - -@monkeypatch(http.Request) -def process_multipart(self): - if not self._do_process_multipart: - return - form = FieldStorage(self.content, self.received_headers, - environ={'REQUEST_METHOD': 'POST'}, - keep_blank_values=1, - strict_parsing=1) - for key in form: - values = form[key] - if not isinstance(values, list): - values = [values] - for value in values: - if value.filename: - if value.done != -1: # -1 is transfer has been interrupted - self.files.setdefault(key, []).append((value.filename, value.file)) - else: - self.files.setdefault(key, []).append((None, None)) - else: - self.args.setdefault(key, []).append(value.value) - -from logging import getLogger -from cubicweb import set_log_methods -LOGGER = getLogger('cubicweb.twisted') -set_log_methods(CubicWebRootResource, LOGGER) - -def run(config, debug=None, repo=None): - # repo may by passed during test. - # - # Test has already created a repo object so we should not create a new one. - # Explicitly passing the repo object avoid relying on the fragile - # config.repository() cache. We could imagine making repo a mandatory - # argument and receives it from the starting command directly. - if debug is not None: - config.debugmode = debug - config.check_writeable_uid_directory(config.appdatahome) - # create the site - if repo is None: - repo = config.repository() - root_resource = CubicWebRootResource(config, repo) - website = server.Site(root_resource) - # serve it via standard HTTP on port set in the configuration - port = config['port'] or 8080 - interface = config['interface'] - reactor.suggestThreadPoolSize(config['webserver-threadpool-size']) - reactor.listenTCP(port, website, interface=interface) - if not config.debugmode: - if sys.platform == 'win32': - raise ConfigurationError("Under windows, you must use the service management " - "commands (e.g : 'net start my_instance)'") - from logilab.common.daemon import daemonize - LOGGER.info('instance started in the background on %s', root_resource.base_url) - whichproc = daemonize(config['pid-file'], umask=config['umask']) - if whichproc: # 1 = orig process, 2 = first fork, None = second fork (eg daemon process) - return whichproc # parent process - root_resource.init_publisher() # before changing uid - if config['uid'] is not None: - from logilab.common.daemon import setugid - setugid(config['uid']) - root_resource.start_service() - LOGGER.info('instance started on %s', root_resource.base_url) - # avoid annoying warnign if not in Main Thread - signals = threading.currentThread().getName() == 'MainThread' - if config['profile']: - import cProfile - cProfile.runctx('reactor.run(installSignalHandlers=%s)' % signals, - globals(), locals(), config['profile']) - else: - reactor.run(installSignalHandlers=signals) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/service.py --- a/cubicweb/etwist/service.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -from __future__ import print_function - -import os -import sys - -try: - import win32serviceutil - import win32service -except ImportError: - print('Win32 extensions for Python are likely not installed.') - sys.exit(3) - -from os.path import join - -from cubicweb.etwist.server import (CubicWebRootResource, reactor, server) - -from logilab.common.shellutils import rm - -import logging -from logging import getLogger, handlers -from cubicweb import set_log_methods -from cubicweb.cwconfig import CubicWebConfiguration as cwcfg - - -def _check_env(env): - env_vars = ('CW_INSTANCES_DIR', 'CW_INSTANCES_DATA_DIR', 'CW_RUNTIME_DIR') - for var in env_vars: - if var not in env: - raise Exception('The environment variables %s must be set.' % - ', '.join(env_vars)) - if not env.get('USERNAME'): - env['USERNAME'] = 'cubicweb' - - -class CWService(object, win32serviceutil.ServiceFramework): - _svc_name_ = None - _svc_display_name_ = None - instance = None - - def __init__(self, *args, **kwargs): - win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs) - cwcfg.load_cwctl_plugins() - logger = getLogger('cubicweb') - set_log_methods(CubicWebRootResource, logger) - - def SvcStop(self): - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - logger = getLogger('cubicweb.twisted') - logger.info('stopping %s service' % self.instance) - reactor.stop() - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - - def SvcDoRun(self): - self.ReportServiceStatus(win32service.SERVICE_START_PENDING) - logger = getLogger('cubicweb.twisted') - handler = handlers.NTEventLogHandler('cubicweb') - handler.setLevel(logging.INFO) - logger.addHandler(handler) - logger.info('starting %s service' % self.instance) - try: - _check_env(os.environ) - # create the site - config = cwcfg.config_for(self.instance) - config.init_log(force=True) - config.debugmode = False - logger.info('starting cubicweb instance %s ', self.instance) - config.info('clear ui caches') - rm(join(config.appdatahome, 'uicache', '*')) - root_resource = CubicWebRootResource(config, config.repository()) - website = server.Site(root_resource) - # serve it via standard HTTP on port set in the configuration - port = config['port'] or 8080 - logger.info('listening on port %s' % port) - reactor.listenTCP(port, website) - root_resource.init_publisher() - root_resource.start_service() - logger.info('instance started on %s', root_resource.base_url) - self.ReportServiceStatus(win32service.SERVICE_RUNNING) - reactor.run() - except Exception as e: - logger.error('service %s stopped (cause: %s)' % (self.instance, e)) - logger.exception('what happened ...') - self.ReportServiceStatus(win32service.SERVICE_STOPPED) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/test/data/views.py --- a/cubicweb/etwist/test/data/views.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -"""only for unit tests !""" - -from cubicweb.view import View -from cubicweb.predicates import match_http_method - -class PutView(View): - __regid__ = 'put' - __select__ = match_http_method('PUT') | match_http_method('POST') - binary = True - - def call(self): - self.w(self._cw.content.read()) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/test/unittest_server.py --- a/cubicweb/etwist/test/unittest_server.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . - -import os, os.path as osp, glob -import urllib - -from cubicweb.devtools.httptest import CubicWebServerTC - - -class ETwistHTTPTC(CubicWebServerTC): - def test_put_content(self): - data = {'hip': 'hop'} - headers = {'Content-Type': 'application/x-www-form-urlencoded'} - body = urllib.urlencode(data) - response = self.web_request('?vid=put', method='PUT', body=body) - self.assertEqual(body, response.body) - response = self.web_request('?vid=put', method='POST', body=body, - headers=headers) - self.assertEqual(body, response.body) - -if __name__ == '__main__': - from logilab.common.testlib import unittest_main - unittest_main() diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/twconfig.py --- a/cubicweb/etwist/twconfig.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -"""twisted server configurations: - -* the "all-in-one" configuration to get a web instance running in a twisted - web server integrating a repository server in the same process (only available - if the repository part of the software is installed -""" - -from os.path import join - -from logilab.common.configuration import merge_options - -from cubicweb.cwconfig import CONFIGURATIONS -from cubicweb.server.serverconfig import ServerConfiguration -from cubicweb.web.webconfig import WebConfiguration, WebConfigurationBase - - -class AllInOneConfiguration(WebConfigurationBase, ServerConfiguration): - """repository and web instance in the same twisted process""" - name = 'all-in-one' - options = merge_options(( - ('profile', - {'type': 'string', - 'default': None, - 'help': 'profile code and use the specified file to store stats if this option is set', - 'group': 'web', 'level': 3, - }), - ('host', - {'type': 'string', - 'default': None, - 'help': 'host name if not correctly detectable through gethostname', - 'group': 'main', 'level': 1, - }), - ('uid', - {'type': 'string', - 'default': None, - 'help': 'unix user, if this option is set, use the specified user to start \ -the repository rather than the user running the command', - 'group': 'main', 'level': WebConfiguration.mode == 'system' - }), - ('webserver-threadpool-size', - {'type': 'int', - 'default': 4, - 'help': "size of twisted's reactor threadpool. It should probably be not too \ -much greater than connection-poolsize", - 'group': 'web', 'level': 3, - }), - ) + WebConfigurationBase.options + ServerConfiguration.options - ) - - cubicweb_appobject_path = ( - WebConfigurationBase.cubicweb_appobject_path - | ServerConfiguration.cubicweb_appobject_path - ) - cube_appobject_path = ( - WebConfigurationBase.cube_appobject_path - | ServerConfiguration.cube_appobject_path - ) - - def server_file(self): - return join(self.apphome, '%s-%s.py' % (self.appid, self.name)) - - -CONFIGURATIONS.append(AllInOneConfiguration) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/etwist/twctl.py --- a/cubicweb/etwist/twctl.py Thu Mar 21 12:05:30 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb 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 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb 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 CubicWeb. If not, see . -"""cubicweb-clt handlers for twisted""" - -from cubicweb.toolsutils import CommandHandler -from cubicweb.web.webctl import WebCreateHandler, WebUpgradeHandler -from cubicweb.server import serverctl - -# trigger configuration registration -import cubicweb.etwist.twconfig # pylint: disable=W0611 - -class TWCreateHandler(WebCreateHandler): - cfgname = 'twisted' - -class TWStartHandler(CommandHandler): - cmdname = 'start' - cfgname = 'twisted' - - def start_server(self, config): - from cubicweb.etwist import server - return server.run(config) - -class TWStopHandler(CommandHandler): - cmdname = 'stop' - cfgname = 'twisted' - - def poststop(self): - pass - -class TWUpgradeHandler(WebUpgradeHandler): - cfgname = 'twisted' - - -class AllInOneCreateHandler(serverctl.RepositoryCreateHandler, - TWCreateHandler): - """configuration to get an instance running in a twisted web server - integrating a repository server in the same process - """ - cfgname = 'all-in-one' - - def bootstrap(self, cubes, automatic=False, inputlevel=0): - """bootstrap this configuration""" - serverctl.RepositoryCreateHandler.bootstrap(self, cubes, automatic, inputlevel) - TWCreateHandler.bootstrap(self, cubes, automatic, inputlevel) - -class AllInOneStartHandler(TWStartHandler): - cmdname = 'start' - cfgname = 'all-in-one' - subcommand = 'cubicweb-twisted' - -class AllInOneStopHandler(CommandHandler): - cmdname = 'stop' - cfgname = 'all-in-one' - subcommand = 'cubicweb-twisted' - - def poststop(self): - pass - -class AllInOneUpgradeHandler(TWUpgradeHandler): - cfgname = 'all-in-one' diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/misc/migration/3.27.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/misc/migration/3.27.0_Any.py Thu Mar 21 14:33:54 2019 +0100 @@ -0,0 +1,3 @@ +option_removed('host') +option_removed('uid') +option_removed('webserver-threadpool-size') diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/pyramid/config.py --- a/cubicweb/pyramid/config.py Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb/pyramid/config.py Thu Mar 21 14:33:54 2019 +0100 @@ -26,7 +26,7 @@ from cubicweb.cwconfig import CONFIGURATIONS from cubicweb.server.serverconfig import ServerConfiguration from cubicweb.toolsutils import fill_templated_file -from cubicweb.web.webconfig import BaseWebConfiguration +from cubicweb.web.webconfig import BaseWebConfiguration, WebConfigurationBase def get_random_secret_key(): @@ -67,3 +67,29 @@ CONFIGURATIONS.append(CubicWebPyramidConfiguration) + + +class AllInOneConfiguration(WebConfigurationBase, ServerConfiguration): + """repository and web instance in the same Pyramid process""" + name = 'all-in-one' + options = merge_options(( + ('profile', + {'type': 'string', + 'default': None, + 'help': 'profile code and use the specified file to store stats if this option is set', + 'group': 'web', 'level': 3, + }), + ) + WebConfigurationBase.options + ServerConfiguration.options + ) + + cubicweb_appobject_path = ( + WebConfigurationBase.cubicweb_appobject_path + | ServerConfiguration.cubicweb_appobject_path + ) + cube_appobject_path = ( + WebConfigurationBase.cube_appobject_path + | ServerConfiguration.cube_appobject_path + ) + + +CONFIGURATIONS.append(AllInOneConfiguration) diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/pyramid/pyramidctl.py --- a/cubicweb/pyramid/pyramidctl.py Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb/pyramid/pyramidctl.py Thu Mar 21 14:33:54 2019 +0100 @@ -63,6 +63,20 @@ self.config.write_development_ini(cubes) +class AllInOneCreateHandler(serverctl.RepositoryCreateHandler, + WebCreateHandler): + """configuration to get an instance running in a Pyramid web server + integrating a repository server in the same process + """ + cfgname = 'all-in-one' + + def bootstrap(self, cubes, automatic=False, inputlevel=0): + """bootstrap this configuration""" + serverctl.RepositoryCreateHandler.bootstrap(self, cubes, automatic, inputlevel) + WebCreateHandler.bootstrap(self, cubes, automatic, inputlevel) + # TODO: write pyramid.ini file + + class PyramidStartHandler(InstanceCommand): """Start an interactive pyramid server. diff -r 7276f1c89ddd -r 9d88e1177c35 cubicweb/web/test/unittest_web.py --- a/cubicweb/web/test/unittest_web.py Thu Mar 21 12:05:30 2019 +0100 +++ b/cubicweb/web/test/unittest_web.py Thu Mar 21 14:33:54 2019 +0100 @@ -28,7 +28,7 @@ requests = None from logilab.common.testlib import TestCase, unittest_main -from cubicweb.devtools.httptest import CubicWebWsgiTC +from cubicweb.devtools.httptest import CubicWebServerTC from cubicweb.devtools.fake import FakeRequest class AjaxReplaceUrlTC(TestCase): @@ -56,7 +56,7 @@ req.html_headers.post_inlined_scripts[0]) -class FileUploadTC(CubicWebWsgiTC): +class FileUploadTC(CubicWebServerTC): def setUp(self): "Skip whole test class if a suitable requests module is not available" @@ -101,7 +101,7 @@ self.assertDictEqual(expect, loads(webreq.text)) -class LanguageTC(CubicWebWsgiTC): +class LanguageTC(CubicWebServerTC): def test_language_neg(self): headers = {'Accept-Language': 'fr'} @@ -132,7 +132,7 @@ self.assertIn('HttpOnly', webreq.getheader('set-cookie')) -class MiscOptionsTC(CubicWebWsgiTC): +class MiscOptionsTC(CubicWebServerTC): @classmethod def setUpClass(cls): super(MiscOptionsTC, cls).setUpClass() diff -r 7276f1c89ddd -r 9d88e1177c35 debian/control --- a/debian/control Thu Mar 21 12:05:30 2019 +0100 +++ b/debian/control Thu Mar 21 14:33:54 2019 +0100 @@ -75,21 +75,18 @@ Suggests: python-zmq, python-cwclientlib (>= 0.4.0), - python-cubicweb-twisted (= ${source:Version}), python-cubicweb-documentation (= ${source:Version}), w3c-dtd-xhtml, xvfb, Replaces: cubicweb (<< 3.24.0-1~), cubicweb-server (<< 3.24.0-1~), - cubicweb-twisted (<< 3.24.0-1~), cubicweb-web (<< 3.24.0-1~), cubicweb-core, cubicweb-common (<< 3.24.0-1~), Breaks: cubicweb (<< 3.24.0-1~), cubicweb-server (<< 3.24.0-1~), - cubicweb-twisted (<< 3.24.0-1~), cubicweb-inlinedit (<< 1.1.1), cubicweb-bootstrap (<< 0.6.6), cubicweb-folder (<< 1.10.0), @@ -140,18 +137,6 @@ cubicweb repository. -Package: python-cubicweb-twisted -Architecture: all -Depends: - python-cubicweb (= ${source:Version}), - python-twisted-web (<< 16.0.0), -Description: meta package to use Twisted as HTTP server for CubicWeb - CubicWeb is a semantic web application framework. - . - This package includes dependencies to run a Twisted based HTTP server to serve - the adaptative web interface. - - Package: python-cubicweb-pyramid Architecture: all Depends: @@ -235,16 +220,6 @@ This is a transitional package. It can safely be removed. -Package: cubicweb-twisted -Architecture: all -Priority: extra -Section: oldlibs -Depends: - python-cubicweb-twisted, ${misc:Depends} -Description: transitional package - This is a transitional package. It can safely be removed. - - Package: cubicweb-web Architecture: all Priority: extra diff -r 7276f1c89ddd -r 9d88e1177c35 doc/book/admin/setup-windows.rst --- a/doc/book/admin/setup-windows.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/book/admin/setup-windows.rst Thu Mar 21 14:33:54 2019 +0100 @@ -32,10 +32,6 @@ applications (including Eclipse + pydev, which is an arguably good IDE for Python under Windows). -* `Twisted `_ is an event-driven - networking engine - (`Download Twisted `_) - * `lxml `_ library (version >=2.2.1) allows working with XML and HTML (`Download lxml `_) diff -r 7276f1c89ddd -r 9d88e1177c35 doc/book/admin/setup.rst --- a/doc/book/admin/setup.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/book/admin/setup.rst Thu Mar 21 14:33:54 2019 +0100 @@ -67,9 +67,6 @@ # if you want pyramid, the recommended application server apt-get install pyramid-cubicweb - # or if you want twisted (considered deprecated) - apt-get install cubicweb-twisted - ``cubicweb`` installs the framework itself, allowing you to create new instances. ``cubicweb-dev`` installs the development environment allowing you to develop new cubes. @@ -137,8 +134,7 @@ A working compilation chain is needed to build the modules that include C extensions. If you really do not want to compile anything, installing `lxml `_, -`Twisted Web `_ and `libgecode -`_ will help. +and `libgecode `_ will help. For Debian, these minimal dependencies can be obtained by doing:: @@ -155,18 +151,13 @@ - setuptools http://www.lfd.uci.edu/~gohlke/pythonlibs/#setuptools - libxml-python http://www.lfd.uci.edu/~gohlke/pythonlibs/#libxml-python> - lxml http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml and -- twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted Make sure to choose the correct architecture and version of Python. Finally, install |cubicweb| and its dependencies, by running:: - # for pyramid, the recommended application server pip install cubicweb[pyramid] - # or for twisted, considered deprecated (used by "cubicweb-ctl") - pip install cubicweb[etwist] - Many other :ref:`cubes ` are available. A list is available at `PyPI `_ or at the `CubicWeb.org forge`_. diff -r 7276f1c89ddd -r 9d88e1177c35 doc/book/annexes/depends.rst --- a/doc/book/annexes/depends.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/book/annexes/depends.rst Thu Mar 21 14:33:54 2019 +0100 @@ -19,8 +19,6 @@ * lxml - http://codespeak.net/lxml - http://pypi.python.org/pypi/lxml -* twisted - http://twistedmatrix.com/ - http://pypi.python.org/pypi/Twisted - * logilab-common - http://www.logilab.org/project/logilab-common - http://pypi.python.org/pypi/logilab-common/ diff -r 7276f1c89ddd -r 9d88e1177c35 doc/book/devweb/publisher.rst --- a/doc/book/devweb/publisher.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/book/devweb/publisher.rst Thu Mar 21 14:33:54 2019 +0100 @@ -7,11 +7,7 @@ The story begins with the ``CubicWebPublisher.main_publish`` method. We do not get upper in the bootstrap process because it is -dependant on the used HTTP library. With `twisted`_ however, -``cubicweb.etwist.server.CubicWebRootResource.render_request`` is the -real entry point. - -.. _`twisted`: http://twistedmatrix.com/trac/ +dependant on the used HTTP library. What main_publish does: diff -r 7276f1c89ddd -r 9d88e1177c35 doc/book/devweb/request.rst --- a/doc/book/devweb/request.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/book/devweb/request.rst Thu Mar 21 14:33:54 2019 +0100 @@ -107,10 +107,9 @@ *while a request is executed* Please note that this class is abstract and that a concrete implementation -will be provided by the *frontend* web used (in particular *twisted* as of -today). For the views or others that are executed on the server side, -most of the interface of `Request` is defined in the session associated -to the client. +will be provided by the *frontend* web used. For the views or others that are +executed on the server side, most of the interface of `Request` is defined in +the session associated to the client. API ``` diff -r 7276f1c89ddd -r 9d88e1177c35 doc/changes/3.27.rst --- a/doc/changes/3.27.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/changes/3.27.rst Thu Mar 21 14:33:54 2019 +0100 @@ -20,6 +20,8 @@ have been removed because they relied on the Twisted web server backend that is no longer maintained nor working with Python 3. +* Twisted web server support has been removed. + Deprecated code drops --------------------- diff -r 7276f1c89ddd -r 9d88e1177c35 doc/tutorials/advanced/part01_create-cube.rst --- a/doc/tutorials/advanced/part01_create-cube.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/tutorials/advanced/part01_create-cube.rst Thu Mar 21 14:33:54 2019 +0100 @@ -12,7 +12,7 @@ virtualenv python-2.7.5_cubicweb . /python-2.7.5_cubicweb/bin/activate - pip install cubicweb[etwist] + pip install cubicweb[pyramid] Step 2: creating a new cube for my web site diff -r 7276f1c89ddd -r 9d88e1177c35 doc/tutorials/base/blog-in-five-minutes.rst --- a/doc/tutorials/base/blog-in-five-minutes.rst Thu Mar 21 12:05:30 2019 +0100 +++ b/doc/tutorials/base/blog-in-five-minutes.rst Thu Mar 21 14:33:54 2019 +0100 @@ -8,7 +8,7 @@ For Debian or Ubuntu users, first install the following packages (:ref:`DebianInstallation`):: - cubicweb, cubicweb-dev, cubicweb-twisted, cubicweb-blog + cubicweb, cubicweb-dev, cubicweb-pyramid, cubicweb-blog Windows or Mac OS X users must install |cubicweb| from source (see :ref:`SourceInstallation` and :ref:`WindowsInstallation`). @@ -17,7 +17,7 @@ virtualenv venv source venv/bin/activate - pip install cubicweb[etwist] cubicweb-dev cubicweb-blog + pip install cubicweb[pyramid] cubicweb-dev cubicweb-blog Then create and initialize your instance:: @@ -38,19 +38,16 @@ choose `sqlite` when asked for which database driver to use, since it has a much simple setup (no database server needed). +Then, you need to setup the CubicWeb Pyramid interface, as document at +:ref:`pyramid_settings`. + One the process is completed (including database initialisation), you can start your instance by using: :: - cubicweb-ctl start -D myblog + cubicweb-ctl pyramid -D myblog The `-D` option activates the debugging mode. Removing it will launch the instance -as a daemon in the background, and ``cubicweb-ctl stop myblog`` will stop -it in that case. - -.. Note:: - - If you get a traceback when going on the web interface make sure your - version of twisted is **inferior** to 17. +as a daemon in the background. .. _AboutFileSystemPermissions: diff -r 7276f1c89ddd -r 9d88e1177c35 flake8-ok-files.txt --- a/flake8-ok-files.txt Thu Mar 21 12:05:30 2019 +0100 +++ b/flake8-ok-files.txt Thu Mar 21 14:33:54 2019 +0100 @@ -24,10 +24,6 @@ cubicweb/entities/adapters.py cubicweb/entities/authobjs.py cubicweb/entities/test/unittest_base.py -cubicweb/etwist/__init__.py -cubicweb/etwist/request.py -cubicweb/etwist/service.py -cubicweb/etwist/twconfig.py cubicweb/ext/__init__.py cubicweb/ext/test/unittest_rest.py cubicweb/hooks/synccomputed.py diff -r 7276f1c89ddd -r 9d88e1177c35 requirements/test-misc.txt --- a/requirements/test-misc.txt Thu Mar 21 12:05:30 2019 +0100 +++ b/requirements/test-misc.txt Thu Mar 21 14:33:54 2019 +0100 @@ -2,7 +2,6 @@ ## shared by several test folders docutils -Twisted < 16.0.0 webtest ## cubicweb/test diff -r 7276f1c89ddd -r 9d88e1177c35 requirements/test-web.txt --- a/requirements/test-web.txt Thu Mar 21 12:05:30 2019 +0100 +++ b/requirements/test-web.txt Thu Mar 21 14:33:54 2019 +0100 @@ -1,4 +1,3 @@ docutils -Twisted < 16.0.0 requests webtest diff -r 7276f1c89ddd -r 9d88e1177c35 setup.py --- a/setup.py Thu Mar 21 12:05:30 2019 +0100 +++ b/setup.py Thu Mar 21 14:33:54 2019 +0100 @@ -91,9 +91,6 @@ 'crypto': [ 'pycrypto', ], - 'etwist': [ - 'Twisted < 16.0.0', - ], 'ext': [ 'docutils >= 0.6', ], diff -r 7276f1c89ddd -r 9d88e1177c35 tox.ini --- a/tox.ini Thu Mar 21 12:05:30 2019 +0100 +++ b/tox.ini Thu Mar 21 14:33:54 2019 +0100 @@ -13,7 +13,6 @@ commands = misc: {envpython} -m pip install --upgrade --no-deps --quiet git+git://github.com/logilab/yapps@master#egg=yapps misc: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/test {toxinidir}/cubicweb/dataimport/test {toxinidir}/cubicweb/devtools/test {toxinidir}/cubicweb/entities/test {toxinidir}/cubicweb/ext/test {toxinidir}/cubicweb/hooks/test {toxinidir}/cubicweb/sobjects/test {toxinidir}/cubicweb/wsgi/test {toxinidir}/cubicweb/pyramid/test - py27-misc: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/etwist/test server: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/server/test web: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/web/test