<commit_before>import re
import math
import json
import datetime
from aiohttp import web
from cerberus import Validator

import virtool.utils
import virtool.user_permissions


class CustomEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.replace(tzinfo=datetime.timezone.utc).isoformat().replace("+00:00", "Z")

        return json.JSONEncoder.default(self, obj)


def dumps(obj):
    """
    A wrapper for :func:`json.dumps` that applies pretty formatting to the output. Used as ``dumps`` argument for
    :func:`.json_response`.

    :param obj: a JSON-serializable object
    :type obj: object

    :return: a JSON string
    :rtype: str

    """
    return json.dumps(obj, indent=4, sort_keys=False, cls=CustomEncoder)


async def unpack_request(req):
    """
    A shortcut for pulling the app database reference and the request JSON body from a :class:`~aiohttp.web.Request`
    object.

    :param req: a request
    :type req: :class:`~aiohttp.web.Request`

    :return: the app DB connection and the request JSON body
    :rtype: Coroutine[tuple]

    """
    return req.app["db"], await req.json()


def compose_regex_query(term, fields):
    if not isinstance(fields, (list, tuple)):
        raise TypeError("Type of 'fields' must be one of 'list' or 'tuple'")

    # Stringify fields.
    fields = [str(field) for field in virtool.utils.coerce_list(fields)]

    # Compile regex, making is case-insensitive.
    regex = re.compile(str(term), re.IGNORECASE)

    # Compose and return $or-based query.
    return {
        "$or": [{field: {"$regex": regex}} for field in fields]
    }


def json_response(data, status=200, headers=None):
    """
    A wrapper for ``aiohttp.web.json_response`` that uses :func:``.dumps`` to pretty format the JSON response.

    :param data: the data to send in the response as JSON
    :type data: object
<commit_msg>Escape regex pattern before search (fixes #264)<commit_after>import re
import math
import json
import datetime
from aiohttp import web
from cerberus import Validator

import virtool.utils
import virtool.user_permissions


class CustomEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.replace(tzinfo=datetime.timezone.utc).isoformat().replace("+00:00", "Z")

        return json.JSONEncoder.default(self, obj)


def dumps(obj):
    """
    A wrapper for :func:`json.dumps` that applies pretty formatting to the output. Used as ``dumps`` argument for
    :func:`.json_response`.

    :param obj: a JSON-serializable object
    :type obj: object

    :return: a JSON string
    :rtype: str

    """
    return json.dumps(obj, indent=4, sort_keys=False, cls=CustomEncoder)


async def unpack_request(req):
    """
    A shortcut for pulling the app database reference and the request JSON body from a :class:`~aiohttp.web.Request`
    object.

    :param req: a request
    :type req: :class:`~aiohttp.web.Request`

    :return: the app DB connection and the request JSON body
    :rtype: Coroutine[tuple]

    """
    return req.app["db"], await req.json()


def compose_regex_query(term, fields):
    if not isinstance(fields, (list, tuple)):
        raise TypeError("Type of 'fields' must be one of 'list' or 'tuple'")

    # Stringify fields.
    fields = [str(field) for field in virtool.utils.coerce_list(fields)]

    term = re.escape(term)

    # Compile regex, making is case-insensitive.
    regex = re.compile(str(term), re.IGNORECASE)

    # Compose and return $or-based query.
    return {
        "$or": [{field: {"$regex": regex}} for field in fields]
    }


def json_response(data, status=200, headers=None):
    """
    A wrapper for ``aiohttp.web.json_response`` that uses :func:``.dumps`` to pretty format the JSON response.

    :param data: the data to send in the response as JSON
    :type data: object

{'fraction_non_alphanumeric': 0.11407520325203252, 'fraction_numerical': 0.004827235772357724, 'mean_word_length': 3.443566591422122, 'pattern_counts': {'":': 4, '<': 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': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3188809', 'n_tokens_mistral': 1235, 'n_tokens_neox': 1186, 'n_words': 398}