<commit_before>"""Session implementation for CherryPy.

We use cherrypy.request to store some convenient variables as
well as data about the session for the current request. Instead of
polluting cherrypy.request we use a Session object bound to
cherrypy.session to store these variables.
"""

import datetime
import os
try:
    import cPickle as pickle
except ImportError:
    import pickle
import random
import sha
import time
import threading
import types

import cherrypy
from cherrypy.lib import http


class Session(object):
    """A CherryPy dict-like Session object (one per request).
    
    id: current session ID.
    expiration_time (datetime): when the current session will expire.
    timeout (minutes): used to calculate expiration_time from now.
    clean_freq (minutes): the poll rate for expired session cleanup.
    locked: If True, this session instance has exclusive read/write access
        to session data.
    loaded: If True, data has been retrieved from storage. This should
        happen automatically on the first attempt to access session data.
    """
    
    clean_thread = None
    
    def __init__(self, id=None, **kwargs):
        self.locked = False
        self.loaded = False
        self._data = {}
        
        for k, v in kwargs.iteritems():
            setattr(self, k, v)
        
        self.id = id
        while self.id is None:
            self.id = self.generate_id()
            # Assert that the generated id is not already stored.
            if self._load() is not None:
                self.id = None
    
    def clean_cycle(self):
        """Clean up expired sessions at regular intervals."""
        # clean_thread is both a cancelable Timer and a flag.
        if self.clean_thread:
            self.clean_up()
            t = threading.Timer(self.clean_freq, self.clean_cycle)
            self.__class__.clean_thread = t
            t.start()
    
    def clean_interrupt(cls):
        """Stop the expired-session cleaning cycle."""
        if cls.clean_thread:
            cls.clean_thread.cancel()
            cls.clean_thread = None
    clean_interrupt = classmethod(clean_interrupt)
    
    def clean_up(self):
        """Clean up expired sessions."""
        pass
    
    try:
        os.urandom(20)
    except (AttributeError, NotImplementedError):
        # os.urandom not available until Python 2.4. Fall back to random.random.
        def generate_id(self):
            """Return a new session id."""
            return sha.new('%s' % random.random()).hexdigest()
    else:
        def generate_id(self):
            """Return a new session id."""
            return os.urandom(20).encode('hex')
    
    def save(self):
        """Save session data."""
        try:
            # If session data has never been loaded then it's never been
            #   accessed: no need to delete it
            if self.loaded:
                t = datetime.timedelta(seconds = self.timeout * 60)
                expiration_time = datetime.datetime.now() + t
                self._save(expiration_time)
            
        finally:
            if self.locked:
                # Always release the lock if the user didn't release it
                self.release_lock()
    
    def load(self):
        """Copy stored session data into this session instance."""
        data = self._load()
        # data is either None or a tuple (session_data, expiration_time)
        if data is None or data[1] < datetime.datetime.now():
            # Expired session: flush session data (but keep the same id)
            self._data = {}
        else:
            self._data = data[0]
        self.loaded = True
        
        cls = self.__class__
        if not cls.clean_thread:
            cherrypy.engine.on_stop_engine_list.append(cls.clean_interrupt)
            # Use the instance to call clean_cycle so tool config
            # can be accessed inside the method.
            cls.clean_thread = t = threading.Timer(self.clean_freq,
                                                   self.clean_cycle)
            t.start()
    
    def delete(self):
        """Delete stored session data."""
        self._delete()
    
    def __getitem__(self, key):
        if not self.loaded: self.load()
        return self._data[key]
    
    def __setitem__(self, key, value):
        if not self.loaded: self.load()
        self._data[key] = value
    
    def __delitem__(self, key):
        if not self.loaded: self.load()
        del self._data[key]
    
    def __contains__(self, key):
        if not self.loaded: self.load()<commit_msg>clean_interrupt was calling clean_thread.cancel() but not .join(). Session.clean_thread is also now a PerpetualTimer, so it keeps the same thread rather than spawning new ones on each run.<commit_after>"""Session implementation for CherryPy.

We use cherrypy.request to store some convenient variables as
well as data about the session for the current request. Instead of
polluting cherrypy.request we use a Session object bound to
cherrypy.session to store these variables.
"""

import datetime
import os
try:
    import cPickle as pickle
except ImportError:
    import pickle
import random
import sha
import time
import threading
import types

import cherrypy
from cherrypy.lib import http


class PerpetualTimer(threading._Timer):
    
    def run(self):
        while True:
            self.finished.wait(self.interval)
            if self.finished.isSet():
                return
            self.function(*self.args, **self.kwargs)


class Session(object):
    """A CherryPy dict-like Session object (one per request).
    
    id: current session ID.
    expiration_time (datetime): when the current session will expire.
    timeout (minutes): used to calculate expiration_time from now.
    clean_freq (minutes): the poll rate for expired session cleanup.
    locked: If True, this session instance has exclusive read/write access
        to session data.
    loaded: If True, data has been retrieved from storage. This should
        happen automatically on the first attempt to access session data.
    """
    
    clean_thread = None
    
    def __init__(self, id=None, **kwargs):
        self.locked = False
        self.loaded = False
        self._data = {}
        
        for k, v in kwargs.iteritems():
            setattr(self, k, v)
        
        self.id = id
        while self.id is None:
            self.id = self.generate_id()
            # Assert that the generated id is not already stored.
            if self._load() is not None:
                self.id = None
    
    def clean_interrupt(cls):
        """Stop the expired-session cleaning timer."""
        if cls.clean_thread:
            cls.clean_thread.cancel()
            cls.clean_thread.join()
            cls.clean_thread = None
    clean_interrupt = classmethod(clean_interrupt)
    
    def clean_up(self):
        """Clean up expired sessions."""
        pass
    
    try:
        os.urandom(20)
    except (AttributeError, NotImplementedError):
        # os.urandom not available until Python 2.4. Fall back to random.random.
        def generate_id(self):
            """Return a new session id."""
            return sha.new('%s' % random.random()).hexdigest()
    else:
        def generate_id(self):
            """Return a new session id."""
            return os.urandom(20).encode('hex')
    
    def save(self):
        """Save session data."""
        try:
            # If session data has never been loaded then it's never been
            #   accessed: no need to delete it
            if self.loaded:
                t = datetime.timedelta(seconds = self.timeout * 60)
                expiration_time = datetime.datetime.now() + t
                self._save(expiration_time)
            
        finally:
            if self.locked:
                # Always release the lock if the user didn't release it
                self.release_lock()
    
    def load(self):
        """Copy stored session data into this session instance."""
        data = self._load()
        # data is either None or a tuple (session_data, expiration_time)
        if data is None or data[1] < datetime.datetime.now():
            # Expired session: flush session data (but keep the same id)
            self._data = {}
        else:
            self._data = data[0]
        self.loaded = True
        
        # Stick the clean_thread in the class, not the instance.
        # The instances are created and destroyed per-request.
        cls = self.__class__
        if not cls.clean_thread:
            cherrypy.engine.on_stop_engine_list.append(cls.clean_interrupt)
            # clean_up is in instancemethod and not a classmethod,
            # so tool config can be accessed inside the method.
            t = PerpetualTimer(self.clean_freq, self.clean_up)
            t.setName("CP Session Cleanup")
            cls.clean_thread = t
            t.start()
    
    def delete(self):
        """Delete stored session data."""
        self._delete()
    
    def __getitem__(self, key):
        if not self.loaded: self.load()
        return self._data[key]
    
    def __setitem__(self, key, value):
        if not self.loaded: self.load()
        self._data[key] = value
    
    def __delitem__(self, key):
        if not self.loaded: self.load()
        del self._data[key]
    
    def __contains__(self, key):
        if not self.loaded: self.load()
{'fraction_non_alphanumeric': 0.08302446256486286, 'fraction_numerical': 0.002117970983797522, 'mean_word_length': 2.0019071837253657, 'pattern_counts': {'":': 0, '<': 5, '<?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': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1969144', 'n_tokens_mistral': 2528, 'n_tokens_neox': 2470, 'n_words': 885}