author Philippe Pepiot <>
Tue, 31 Mar 2020 18:22:05 +0200
changeset 12966 6cd938c29ca3
parent 12567 26744ad37953
permissions -rw-r--r--
[server] Make connection pooler configurable and set better default values Drop the configuration connections-pool-size and add new configurations options: * connections-pool-min-size. Set to 0 by default so we open connections only when needed. This avoid opening min-size*processes connections at startup, which is, it think, a good default. * connections-pool-max-size. Set to 0 (unlimited) by default, so we move the bottleneck to postgresql. * connections-idle-timeout. Set to 10 minutes. I don't have arguments about this except that this is the default in pgbouncer.

# copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact --
# 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 <>.
"""Workflow setup utilities.

These functions work with a declarative workflow definition:

.. code-block:: python

            'etypes': 'CWGroup',
            'default': True,
            'initial_state': u'draft',
            'states': [u'draft', u'published'],
            'transitions': {
                u'publish': {
                    'fromstates': u'draft',
                    'tostate': u'published',
                    'requiredgroups': u'managers'
                    'conditions': (
                        'U in_group X',
                        'X owned_by U'

.. autofunction:: setup_workflow
.. autofunction:: cleanupworkflow

import collections

from cubicweb import NoResultError

def get_tuple_or_list(value):
    if value is None:
        return None
    if not isinstance(value, (tuple, list)):
        value = (value,)
    return value

def cleanupworkflow(cnx, wf, wfdef):
    """Cleanup an existing workflow by removing the states and transitions that
    do not exist in the given definition.

    :param cnx: A connexion with enough permissions to define a workflow
    :param wf: A `Workflow` entity
    :param wfdef: A workflow definition
        'DELETE State S WHERE S state_of WF, WF eid %%(wf)s, '
        'NOT S name IN (%s)' % (
            ', '.join('"%s"' % s for s in wfdef['states'])),
        {'wf': wf.eid})

        'DELETE Transition T WHERE T transition_of WF, WF eid %%(wf)s, '
        'NOT T name IN (%s)' % (
            ', '.join('"%s"' % s for s in wfdef['transitions'])),
        {'wf': wf.eid})

def setup_workflow(cnx, name, wfdef, cleanup=True):
    """Create or update a workflow definition so it matches the given

    :param cnx: A connexion with enough permissions to define a workflow
    :param name: The workflow name. Used to create the `Workflow` entity, or
                 to find an existing one.
    :param wfdef: A workflow definition.
    :param cleanup: Remove extra states and transitions. Can be done separatly
                    by calling :func:`cleanupworkflow`.
    :return: The created/updated workflow entity
        wf = cnx.find('Workflow', name=name).one()
    except NoResultError:
        wf = cnx.create_entity('Workflow', name=name)

    etypes = get_tuple_or_list(wfdef['etypes'])
    cnx.execute('DELETE WF workflow_of ETYPE WHERE WF eid %%(wf)s, '
                'NOT ETYPE name IN (%s)' % ','.join('"%s"' for e in etypes),
                {'wf': wf.eid})
    cnx.execute('SET WF workflow_of ETYPE WHERE'
                ' NOT WF workflow_of ETYPE, WF eid %%(wf)s, ETYPE name IN (%s)'
                % ','.join('"%s"' % e for e in etypes),
                {'wf': wf.eid})
    if wfdef['default']:
            'SET ETYPE default_workflow X '
            'WHERE '
            'NOT ETYPE default_workflow X, '
            'X eid %%(x)s, ETYPE name IN (%s)' % ','.join(
                '"%s"' % e for e in etypes),
            {'x': wf.eid})

    states = {}
    states_transitions = collections.defaultdict(list)
    for state in wfdef['states']:
        st = wf.state_by_name(state) or wf.add_state(state)
        states[state] = st

    if 'initial_state' in wfdef:

    for trname, trdef in wfdef['transitions'].items():
        tr = (wf.transition_by_name(trname)
              or cnx.create_entity('Transition', name=trname))
        if trdef.get('tostate'):
        fromstates = get_tuple_or_list(trdef.get('fromstates', ()))
        for stname in fromstates:

        requiredgroups = get_tuple_or_list(trdef.get('requiredgroups', ()))
        conditions = get_tuple_or_list(trdef.get('conditions', ()))

        tr.set_permissions(requiredgroups, conditions, reset=True)

    for stname, transitions in states_transitions.items():
        state = states[stname]

    if cleanup:
        cleanupworkflow(cnx, wf, wfdef)

    return wf