<commit_before>"""Analysis module for Databench."""

import os
import json
import random
import string
import fnmatch
import logging
import tornado.web
import tornado.websocket
from .datastore import Datastore

# utilities
try:
    from markdown import markdown
except ImportError:
    markdown = None

try:
    from docutils.core import publish_parts as rst
except ImportError:
    rst = None

from . import __version__ as DATABENCH_VERSION

log = logging.getLogger(__name__)


class Analysis(object):
    """Databench's analysis class.

    This contains the analysis code. Every browser connection corresponds to
    and instance of this class.

    **Initialize:** add an ``on_connect(self)`` or
    ``on_connect(self, request_args)`` method to your analysis class. The
    optional argument ``request_args`` contains a dictionary of parameters
    from the request url.

    **Incoming messages** are captured by specifying a class method starting
    with ``on_`` followed by the signal name. To capture the frontend signal
    ``run`` that is emitted with the JavaScript code

    .. code-block:: js

        // on the JavaScript frontend
        databench.emit('run', {my_param: 'helloworld'});

    use

    .. code-block:: python

        # here in Python
        def on_run(self, my_param):

    here. The entries of a dictionary will be used as keyword arguments in the
    function call; as in this example. If the emitted message is an array,
    the entries will be used as positional arguments in the function call.
    If the message is neither of type ``list`` nor ``dict`` (for example a
    plain ``string`` or ``float``), the function will be called with that
    as its first parameter.

    **Outgoing messages** are sent using ``emit(signal_name, message)``.
    For example, use

    .. code-block:: python

        self.emit('result', {'msg': 'done'})

    to send the signal ``result`` with the message ``{'msg': 'done'}`` to
    the frontend.

    """

    datastore_class = Datastore

    def __init__(self, id_=None):
        self.id_ = id_ if id_ else Analysis.__create_id()
        self.emit = lambda s, pl: log.error('emit called before Analysis '
                                            'setup complete')

        self.data = Analysis.datastore_class(self.id_)
        self.global_data = Analysis.datastore_class(type(self).__name__)

        self.data.on_change(self.data_change)
        self.global_data.on_change(self.global_data_change)

    @staticmethod
    def __create_id():
        return ''.join(random.choice(string.ascii_letters + string.digits)
                       for _ in range(8))

    def set_emit_fn(self, emit_fn):
        """Sets what the emit function for this analysis will be."""
        self.emit = emit_fn
        return self

    """Events."""

    def on_connect(self):
        log.debug('on_connect called.')

    def on_disconnect(self):
        log.debug('on_disconnect called.')

    def on_data(self, **kwargs):
        self.data.update(kwargs)

    def on_global_data(self, **kwargs):
        self.global_data.update(kwargs)

    """Data callbacks."""

    def data_change(self, key, value):
        self.emit('data', {key: value})

    def global_data_change(self, key, value):
        self.emit('global_data', {key: value})

<commit_msg>introduce data_* and global_data_* callbacks<commit_after>"""Analysis module for Databench."""

import os
import json
import random
import string
import fnmatch
import logging
import tornado.web
import tornado.websocket
from .datastore import Datastore

# utilities
try:
    from markdown import markdown
except ImportError:
    markdown = None

try:
    from docutils.core import publish_parts as rst
except ImportError:
    rst = None

from . import __version__ as DATABENCH_VERSION

log = logging.getLogger(__name__)


class Analysis(object):
    """Databench's analysis class.

    This contains the analysis code. Every browser connection corresponds to
    and instance of this class.

    **Initialize:** add an ``on_connect(self)`` or
    ``on_connect(self, request_args)`` method to your analysis class. The
    optional argument ``request_args`` contains a dictionary of parameters
    from the request url.

    **Incoming messages** are captured by specifying a class method starting
    with ``on_`` followed by the signal name. To capture the frontend signal
    ``run`` that is emitted with the JavaScript code

    .. code-block:: js

        // on the JavaScript frontend
        databench.emit('run', {my_param: 'helloworld'});

    use

    .. code-block:: python

        # here in Python
        def on_run(self, my_param):

    here. The entries of a dictionary will be used as keyword arguments in the
    function call; as in this example. If the emitted message is an array,
    the entries will be used as positional arguments in the function call.
    If the message is neither of type ``list`` nor ``dict`` (for example a
    plain ``string`` or ``float``), the function will be called with that
    as its first parameter.

    **Outgoing messages** are sent using ``emit(signal_name, message)``.
    For example, use

    .. code-block:: python

        self.emit('result', {'msg': 'done'})

    to send the signal ``result`` with the message ``{'msg': 'done'}`` to
    the frontend.

    """

    datastore_class = Datastore

    def __init__(self, id_=None):
        self.id_ = id_ if id_ else Analysis.__create_id()
        self.emit = lambda s, pl: log.error('emit called before Analysis '
                                            'setup complete')

        self.data = Analysis.datastore_class(self.id_)
        self.global_data = Analysis.datastore_class(type(self).__name__)

        self.data.on_change(self.data_change)
        self.global_data.on_change(self.global_data_change)

    @staticmethod
    def __create_id():
        return ''.join(random.choice(string.ascii_letters + string.digits)
                       for _ in range(8))

    def set_emit_fn(self, emit_fn):
        """Sets what the emit function for this analysis will be."""
        self.emit = emit_fn
        return self

    """Events."""

    def on_connect(self):
        log.debug('on_connect called.')

    def on_disconnect(self):
        log.debug('on_disconnect called.')

    def on_data(self, **kwargs):
        self.data.update(kwargs)

    def on_global_data(self, **kwargs):
        self.global_data.update(kwargs)

    """Data callbacks."""

    def data_change(self, key, value):
        self.emit('data', {key: value})
        if hasattr(self, 'data_{}'.format(key)):
            getattr(self, 'data_{}'.format(key))(value)

    def global_data_change(self, key, value):
        self.emit('global_data', {key: value})
        if hasattr(self, 'global_data_{}'.format(key)):
            getattr(self, 'global_data_{}'.format(key))(value)


{'fraction_non_alphanumeric': 0.10376398779247202, 'fraction_numerical': 0.0002906554279901177, 'mean_word_length': 2.9080068143100513, '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': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '472997', 'n_tokens_mistral': 2004, 'n_tokens_neox': 1911, 'n_words': 666}