<commit_before>from contextlib import contextmanager
from itertools import chain
import logging
import os
from ming.odm import mapper
from pymongo.errors import DuplicateKeyError
from tgext.ecommerce.model import DBSession, Cart, Product, Setting

log = logging.getLogger('tgext.ecommerce')

@contextmanager
def cleanup_session(session):
    yield
    session.flush_all()
    session.close_all()


def clean_expired_carts(clear_all=False):
    with cleanup_session(DBSession):
        if clear_all:
            expired_carts = Cart.query.find().all()
        else:
            expired_carts = Cart.expired_carts().all()

        expired_items = chain(*[c.items.iteritems() for c in expired_carts])
        for sku, item in expired_items:
            DBSession.update(Product, {'configurations.sku': sku},
                             {'$inc': {'configurations.$.qty': item['qty']}})
        [c.delete() for c in expired_carts]


def cart_locked_by_me():
    locked_by_me = Setting.query.find({'setting': 'cart_locked', 'value': os.getpid()}).first()
    return locked_by_me is not None


def lock_carts():
    settings_collection = mapper(Setting).collection

    # Ensure there is the lock row
    try:
        DBSession.impl.insert(settings_collection({'setting': 'cart_locked', 'value': 0}))
    except DuplicateKeyError:
        pass

    # Try to get the lock
    mypid = os.getpid()
    locked = DBSession.impl.update_partial(settings_collection,
                                           {'setting': 'cart_locked',
                                            'value': 0},
                                           {'$set': {'value': mypid}})<commit_msg>Fix carts expiration in concurrent environments<commit_after>from contextlib import contextmanager
from datetime import datetime, timedelta
from itertools import chain
import logging
import os
from ming.odm import mapper
from pymongo.errors import DuplicateKeyError
from tgext.ecommerce.model import DBSession, Cart, Product, Setting

log = logging.getLogger('tgext.ecommerce')

@contextmanager
def cleanup_session(session):
    yield
    session.flush_all()
    session.close_all()


def clean_expired_cart(expired_cart):
    log.warn('Expiring Cart %s for user %s', expired_cart._id, expired_cart.user_id)

    for sku, item in expired_cart.items.iteritems():
        DBSession.update(Product, {'configurations.sku': sku},
                         {'$inc': {'configurations.$.qty': item['qty']}})

    expired_cart.delete()


def clean_expired_carts(clear_all=False):
    with cleanup_session(DBSession):
        if clear_all:
            expired_carts = Cart.query.find().all()
            for c in expired_carts:
                clean_expired_cart(c)
        else:
            now = datetime.utcnow()
            shifted_expire = now + timedelta(minutes=5)
            while True:
                expired_cart = Cart.query.find_and_modify(query={'expires_at': {'$lte': now}},
                                                          update={'$set': {'expires_at': shifted_expire}})
                if expired_cart is None:
                    break

                clean_expired_cart(expired_cart)


def cart_locked_by_me():
    locked_by_me = Setting.query.find({'setting': 'cart_locked', 'value': os.getpid()}).first()
    return locked_by_me is not None


def lock_carts():
    settings_collection = mapper(Setting).collection

    # Ensure there is the lock row
    try:
        DBSession.impl.insert(settings_collection({'setting': 'cart_locked', 'value': 0}))
    except DuplicateKeyError:
        pass

    # Try to get the lock
    mypid = os.getpid()
    locked = DBSession.impl.update_partial(settings_collection,
                                           {'setting': 'cart_locked',
                                            'value': 0},
                                           {'$set': {'value': mypid}})
{'fraction_non_alphanumeric': 0.10593983029056313, 'fraction_numerical': 0.0012856775520699408, 'mean_word_length': 2.4242957746478875, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2250153', 'n_tokens_mistral': 1109, 'n_tokens_neox': 1085, 'n_words': 260}