Beispiel #1
0
def test_raising_IRetryableError_type_is_caught(config):
    from pyramid_retry import mark_error_retryable
    class MyRetryableError(Exception):
        pass
    mark_error_retryable(MyRetryableError)
    calls = []
    def final_view(request):
        calls.append('ok')
        return 'ok'
    def bad_view(request):
        calls.append('fail')
        raise MyRetryableError
    config.add_view(bad_view, last_retry_attempt=False)
    config.add_view(final_view, last_retry_attempt=True, renderer='string')
    app = config.make_wsgi_app()
    app = webtest.TestApp(app)
    response = app.get('/')
    assert response.body == b'ok'
    assert calls == ['fail', 'fail', 'ok']
Beispiel #2
0
# contains a unique, user provided value, you can get into a race condition where both
# requests check the database, see nothing with that value exists, then both attempt to
# insert it. One of the requests will succeed, the other will fail with an
# IntegrityError. Retrying the request that failed will then have it see the object
# created by the other request, and will have it do the appropriate action in that case.
#
# The most common way to run into this, is when submitting a form in the browser, if the
# user clicks twice in rapid succession, the browser will send two almost identical
# requests at basically the same time.
#
# One possible issue that this raises, is that it will slow down "legitimate"
# IntegrityError because they'll have to fail multiple times before they ultimately
# fail. We consider this an acceptable trade off, because deterministic IntegrityError
# should be caught with proper validation prior to submitting records to the database
# anyways.
pyramid_retry.mark_error_retryable(IntegrityError)


# A generic wrapper exception that we'll raise when the database isn't available, we
# use this so we can catch it later and turn it into a generic 5xx error.
class DatabaseNotAvailableError(Exception):
    ...


# We'll add a basic predicate that won't do anything except allow marking a
# route as read only (or not).
class ReadOnlyPredicate:
    def __init__(self, val, config):
        self.val = val

    def text(self):
Beispiel #3
0
from pyramid.util import DottedNameResolver
import transaction
import warnings
import zope.interface

try:
    from pyramid_retry import IRetryableError
except ImportError:  # pragma: no cover
    IRetryableError = zope.interface.Interface

try:
    from pyramid_retry import mark_error_retryable
except ImportError:  # pragma: no cover
    mark_error_retryable = lambda error: None

mark_error_retryable(transaction.interfaces.TransientError)

from .compat import reraise
from .compat import text_

resolver = DottedNameResolver(None)


def default_commit_veto(request, response):
    """
    When used as a commit veto, the logic in this function will cause the
    transaction to be aborted if:

    - An ``X-Tm`` response header with the value ``abort`` (or any value
      other than ``commit``) exists.
Beispiel #4
0
from pyramid_retry import mark_error_retryable
from sqlalchemy.dialects import postgresql as pg
from sqlalchemy.ext.hybrid import hybrid_property

from h.db import Base, mixins
from h.models.annotation import Annotation
from h.util.uri import normalize as uri_normalize

log = logging.getLogger(__name__)


class ConcurrentUpdateError(Exception):
    """Raised when concurrent updates to document data conflict."""


mark_error_retryable(ConcurrentUpdateError)


class Document(Base, mixins.Timestamps):
    __tablename__ = "document"

    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)

    #: The denormalized value of the first DocumentMeta record with type title.
    title = sa.Column("title", sa.UnicodeText())

    #: The denormalized value of the "best" http(s) DocumentURI for this Document.
    web_uri = sa.Column("web_uri", sa.UnicodeText())

    # FIXME: This relationship should be named `uris` again after the
    #        dependency on the annotator-store is removed, as it clashes with
Beispiel #5
0
    ContenuArticleModifie, PresentationArticleModifiee, TitreArticleModifie,
)
from .events.base import Event  # noqa
from .events.lecture import (  # noqa
    AmendementsAJour, AmendementsNonRecuperes, AmendementsNonTrouves,
    AmendementsRecuperes, AmendementsRecuperesLiasse, ArticlesRecuperes,
    LectureCreee, ReponsesImportees, ReponsesImporteesJSON, SharedTableCreee,
    SharedTableRenommee, SharedTableSupprimee,
)
from .lecture import Lecture  # noqa
from .phase import Phase  # noqa
from .table import SharedTable, UserTable  # noqa
from .texte import Texte, TypeTexte  # noqa
from .users import AllowedEmailPattern, Team, User  # noqa

mark_error_retryable(IntegrityError)


def _get_one(model: Any,
             options: Any = None,
             **kwargs: Any) -> Tuple[Any, bool]:
    query = DBSession.query(model).filter_by(**kwargs)
    if options is not None:
        query = query.options(options)
    return query.one(), False


def _create(model: Any,
            create_kwargs: Any = None,
            **kwargs: Any) -> Tuple[Any, bool]:
    kwargs.update(create_kwargs or {})
Beispiel #6
0
def test_mark_error_retryable_on_non_error():
    from pyramid_retry import mark_error_retryable
    with pytest.raises(ValueError):
        mark_error_retryable('some string')
Beispiel #7
0
 def bad_view(request):
     calls.append('fail')
     ex = Exception()
     mark_error_retryable(ex)
     raise ex
Beispiel #8
0
def create_app(global_config, **settings):  # pylint: disable=unused-argument
    config = configure(settings=settings)

    # Make sure that pyramid_exclog's tween runs under pyramid_tm's tween so
    # that pyramid_exclog doesn't re-open the DB session after pyramid_tm has
    # already closed it.
    config.add_tween(
        "pyramid_exclog.exclog_tween_factory", under="pyramid_tm.tm_tween_factory"
    )
    config.add_settings({"exclog.extra_info": True})
    config.include("pyramid_exclog")

    config.include("pyramid_jinja2")
    config.include("pyramid_services")

    # Use pyramid_tm's explicit transaction manager.
    #
    # This means that trying to access a request's transaction after pyramid_tm
    # has closed the request's transaction will crash, rather than implicitly
    # opening a new transaction that doesn't get closed (and potentially
    # leaking open DB connections).
    #
    # This is recommended in the pyramid_tm docs:
    #
    # https://docs.pylonsproject.org/projects/pyramid_tm/en/latest/#custom-transaction-managers
    config.registry.settings["tm.manager_hook"] = pyramid_tm.explicit_manager

    config.include("pyramid_tm")
    config.include("pyramid_retry")

    # Mark all sqlalchemy IntegrityError's as retryable.
    #
    # This means that if any request fails with any IntegrityError error then
    # pyramid_retry will re-try the request up to two times. No error response
    # will be sent back to the client, and no crash reported to Sentry, unless
    # the request fails three times in a row (or one of the re-tries fails with
    # a non-retryable error).
    #
    # This does mean that if a request is failing with a non-transient
    # IntegrityError (so the request has no hope of succeeding on retry) then
    # we will pointlessly retry the request twice before failing.
    #
    # But we shouldn't have too many non-transient IntegrityError's anyway
    # (sounds like a bug) and marking all IntegrityError's as retryable means
    # that in all cases when an IntegrityError *is* transient and the request
    # *can* succeed on retry, it will be retried, without having to mark those
    # IntegrityErrors as retryable on a case-by-case basis.
    #
    # Examples of transient/retryable IntegrityError's are when doing either
    # upsert or create-if-not-exists logic when entering rows into the DB:
    # concurrent requests can both see that the DB row doesn't exist yet and
    # try to create the DB row at the same time and one of them will fail. If
    # retried the failed request will now see that the DB row already exists
    # and not try to create it, and the request will succeed.
    pyramid_retry.mark_error_retryable(IntegrityError)

    config.include("lms.authentication")
    config.include("lms.extensions.feature_flags")
    config.add_feature_flag_providers(
        "lms.extensions.feature_flags.config_file_provider",
        "lms.extensions.feature_flags.envvar_provider",
        "lms.extensions.feature_flags.cookie_provider",
        "lms.extensions.feature_flags.query_string_provider",
    )

    config.include("lms.sentry")
    config.include("lms.session")
    config.include("lms.models")
    config.include("lms.db")
    config.include("lms.routes")
    config.include("lms.assets")
    config.include("lms.views")
    config.include("lms.services")
    config.include("lms.validation")
    config.include("lms.tweens")
    config.add_static_view(name="export", path="lms:static/export")
    config.add_static_view(name="static", path="lms:static")

    config.registry.settings["jinja2.filters"] = {
        "static_path": "pyramid_jinja2.filters:static_path_filter",
        "static_url": "pyramid_jinja2.filters:static_url_filter",
    }

    config.action(None, configure_jinja2_assets, args=(config,))

    config.scan()

    return config.make_wsgi_app()
Beispiel #9
0
# contains a unique, user provided value, you can get into a race condition where both
# requests check the database, see nothing with that value exists, then both attempt to
# insert it. One of the requests will succeed, the other will fail with an
# IntegrityError. Retrying the request that failed will then have it see the object
# created by the other request, and will have it do the appropriate action in that case.
#
# The most common way to run into this, is when submitting a form in the browser, if the
# user clicks twice in rapid succession, the browser will send two almost identical
# requests at basically the same time.
#
# One possible issue that this raises, is that it will slow down "legitimate"
# IntegrityError because they'll have to fail multiple times before they ultimately
# fail. We consider this an acceptable trade off, because deterministic IntegrityError
# should be caught with proper validation prior to submitting records to the database
# anyways.
pyramid_retry.mark_error_retryable(IntegrityError)


# A generic wrapper exception that we'll raise when the database isn't available, we
# use this so we can catch it later and turn it into a generic 5xx error.
class DatabaseNotAvailable(Exception):
    ...


# We'll add a basic predicate that won't do anything except allow marking a
# route as read only (or not).
class ReadOnlyPredicate:
    def __init__(self, val, config):
        self.val = val

    def text(self):