<commit_before>"""This module provides a framework for generating test methods at runtime."""


from collections import namedtuple
import functools


ATTR_NAME = 'tcm values'
TEST_METHOD_PREFIX = 'test'


class DecoratorException(Exception):
    """Exception raised on invalid use of the "values" decorator."""

    pass


class values():  # noqa: N801 / pylint: disable=invalid-name,too-few-public-methods
    """Parameterized decorator which stores its arguments in the decorated object."""

    def __init__(self, *args, **kwargs):
        """Capture the decorator arguments."""
        if len(args) == 1 and callable(args[0]) and not kwargs:
            raise DecoratorException(
                'Invalid use without parentheses or with a callable as the only argument')
        self.__captured_arguments = _CapturedArguments(args, kwargs)

    def __call__(self, func):
        """Store the captured arguments in the decorated object."""
        if not callable(func):
            raise DecoratorException('The object must be callable')
        if not func.__name__.startswith(TEST_METHOD_PREFIX):
            raise DecoratorException(
                'The object name must start with "{}"'.format(TEST_METHOD_PREFIX))

        try:
            attr = getattr(func, ATTR_NAME)
        except AttributeError:
            setattr(func, ATTR_NAME, self.__captured_arguments)
        else:
            if isinstance(attr, _CapturedArguments):
                raise DecoratorException('Cannot decorate the same object more than once')
            else:
                raise DecoratorException(
                    'The object already has the "{}" attribute'.format(ATTR_NAME))

        return func


_CapturedArguments = namedtuple('CapturedArguments', 'args, kwargs')


class TestCaseMeta(type):
    """Metaclass based runtime generator of the test methods."""

    def __new__(mcs, name, bases, mapping):  # noqa: N804
        """Create the class after expanding the original mapping."""
        new_mapping = dict(_expanded_mapping(mapping))
        return super().__new__(mcs, name, bases, new_mapping)


def _expanded_mapping(mapping):
    """Iterate the mapping while generating new test methods from decorated ones."""
    for key, value in mapping.items():
        try:
            captured_arguments = _extract_captured_arguments(value)
        except AttributeError:
            # Pass non-decorated items unchanged.
            yield key, value
        else:
            # Use the decorated test method as a sample and generate a distinct
            # test method for every captured argument.
            for suffix, arg in _uniformly_named_arguments(captured_arguments):
                generated = _generate_test_method(value, arg)
                generated.__name__ = key + '_' + suffix
                yield generated.__name__, generated


def _extract_captured_arguments(func):
    """Raise AttributeError for non-decorated "func", return the captured arguments otherwise."""
    captured_arguments = getattr(func, ATTR_NAME)
    if type(captured_arguments) is not _CapturedArguments:  # pylint: disable=unidiomatic-typecheck
        # The attribute was not set by tcm, so effectively it does not exist.
        raise AttributeError
    delattr(func, ATTR_NAME)
    return captured_arguments


def _uniformly_named_arguments(captured_arguments):
    """Iterate the captured arguments as uniform name/value pairs."""
    args, kwargs = captured_arguments

    # For positional arguments, the name is 1-based index padded with
    # leading zeroes to the length of the last index.
    width = len(str(len(args)))
    for i, arg in enumerate(args, 1):
        name = str(i).zfill(width)
        yield name, arg

    # For keyword arguments, the keyword is taken as a name.
    yield from kwargs.items()


def _generate_test_method(func, arg):
    """Wrap the original test method by supplying the "arg" as a positional argument."""
    @functools.wraps(func)
    def _wrapper(self):
        return func(self, arg)

    return _wrapper
<commit_msg>Use ordered mapping for all Python versions<commit_after>"""This module provides a framework for generating test methods at runtime."""


from collections import namedtuple
from collections import OrderedDict
import functools


ATTR_NAME = 'tcm values'
TEST_METHOD_PREFIX = 'test'


class DecoratorException(Exception):
    """Exception raised on invalid use of the "values" decorator."""

    pass


class values():  # noqa: N801 / pylint: disable=invalid-name,too-few-public-methods
    """Parameterized decorator which stores its arguments in the decorated object."""

    def __init__(self, *args, **kwargs):
        """Capture the decorator arguments."""
        if len(args) == 1 and callable(args[0]) and not kwargs:
            raise DecoratorException(
                'Invalid use without parentheses or with a callable as the only argument')
        self.__captured_arguments = _CapturedArguments(args, kwargs)

    def __call__(self, func):
        """Store the captured arguments in the decorated object."""
        if not callable(func):
            raise DecoratorException('The object must be callable')
        if not func.__name__.startswith(TEST_METHOD_PREFIX):
            raise DecoratorException(
                'The object name must start with "{}"'.format(TEST_METHOD_PREFIX))

        try:
            attr = getattr(func, ATTR_NAME)
        except AttributeError:
            setattr(func, ATTR_NAME, self.__captured_arguments)
        else:
            if isinstance(attr, _CapturedArguments):
                raise DecoratorException('Cannot decorate the same object more than once')
            else:
                raise DecoratorException(
                    'The object already has the "{}" attribute'.format(ATTR_NAME))

        return func


_CapturedArguments = namedtuple('CapturedArguments', 'args, kwargs')


class TestCaseMeta(type):
    """Metaclass based runtime generator of the test methods."""

    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):  # noqa: N804 pylint: disable=unused-argument
        """Use ordered mapping for the namespace regardless of the Python version."""
        # Note: absent the __prepare__() method, Python 3.6 would use an ordered
        # mapping while prior versions would stick with a regular dict().
        return OrderedDict()

    def __new__(mcs, name, bases, mapping):  # noqa: N804
        """Create the class after expanding the original mapping."""
        new_mapping = dict(_expanded_mapping(mapping))
        return super().__new__(mcs, name, bases, new_mapping)


def _expanded_mapping(mapping):
    """Iterate the mapping while generating new test methods from decorated ones."""
    for key, value in mapping.items():
        try:
            captured_arguments = _extract_captured_arguments(value)
        except AttributeError:
            # Pass non-decorated items unchanged.
            yield key, value
        else:
            # Use the decorated test method as a sample and generate a distinct
            # test method for every captured argument.
            for suffix, arg in _uniformly_named_arguments(captured_arguments):
                generated = _generate_test_method(value, arg)
                generated.__name__ = key + '_' + suffix
                yield generated.__name__, generated


def _extract_captured_arguments(func):
    """Raise AttributeError for non-decorated "func", return the captured arguments otherwise."""
    captured_arguments = getattr(func, ATTR_NAME)
    if type(captured_arguments) is not _CapturedArguments:  # pylint: disable=unidiomatic-typecheck
        # The attribute was not set by tcm, so effectively it does not exist.
        raise AttributeError
    delattr(func, ATTR_NAME)
    return captured_arguments


def _uniformly_named_arguments(captured_arguments):
    """Iterate the captured arguments as uniform name/value pairs."""
    args, kwargs = captured_arguments

    # For positional arguments, the name is 1-based index padded with
    # leading zeroes to the length of the last index.
    width = len(str(len(args)))
    for i, arg in enumerate(args, 1):
        name = str(i).zfill(width)
        yield name, arg

    # For keyword arguments, the keyword is taken as a name.
    yield from kwargs.items()


def _generate_test_method(func, arg):
    """Wrap the original test method by supplying the "arg" as a positional argument."""
    @functools.wraps(func)
    def _wrapper(self):
        return func(self, arg)

    return _wrapper

{'fraction_non_alphanumeric': 0.08200163303394378, 'fraction_numerical': 0.0029161320424588824, 'mean_word_length': 2.9621072088724585, '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': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6209394', 'n_tokens_mistral': 2264, 'n_tokens_neox': 2167, 'n_words': 794}