File: compat.py

package info (click to toggle)
python-multipart 0.0.20-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 812 kB
  • sloc: python: 2,226; sh: 17; makefile: 5
file content (116 lines) | stat: -rw-r--r-- 4,171 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from __future__ import annotations

import functools
import os
import re
import sys
import types
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Any, Callable


def ensure_in_path(path: str) -> None:
    """
    Ensure that a given path is in the sys.path array
    """
    if not os.path.isdir(path):
        raise RuntimeError("Tried to add nonexisting path")

    def _samefile(x: str, y: str) -> bool:
        try:
            return os.path.samefile(x, y)
        except OSError:
            return False
        except AttributeError:
            # Probably on Windows.
            path1 = os.path.abspath(x).lower()
            path2 = os.path.abspath(y).lower()
            return path1 == path2

    # Remove existing copies of it.
    for pth in sys.path:
        if _samefile(pth, path):
            sys.path.remove(pth)

    # Add it at the beginning.
    sys.path.insert(0, path)


# We don't use the pytest parametrizing function, since it seems to break
# with unittest.TestCase subclasses.
def parametrize(field_names: tuple[str] | list[str] | str, field_values: list[Any] | Any) -> Callable[..., Any]:
    # If we're not given a list of field names, we make it.
    if not isinstance(field_names, (tuple, list)):
        field_names = (field_names,)
        field_values = [(val,) for val in field_values]

    # Create a decorator that saves this list of field names and values on the
    # function for later parametrizing.
    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        func.__dict__["param_names"] = field_names
        func.__dict__["param_values"] = field_values
        return func

    return decorator


# This is a metaclass that actually performs the parametrization.
class ParametrizingMetaclass(type):
    IDENTIFIER_RE = re.compile("[^A-Za-z0-9]")

    def __new__(klass, name: str, bases: tuple[type, ...], attrs: types.MappingProxyType[str, Any]) -> type:
        new_attrs = attrs.copy()
        for attr_name, attr in attrs.items():
            # We only care about functions
            if not isinstance(attr, types.FunctionType):
                continue

            param_names = attr.__dict__.pop("param_names", None)
            param_values = attr.__dict__.pop("param_values", None)
            if param_names is None or param_values is None:
                continue

            # Create multiple copies of the function.
            for _, values in enumerate(param_values):
                assert len(param_names) == len(values)

                # Get a repr of the values, and fix it to be a valid identifier
                human = "_".join([klass.IDENTIFIER_RE.sub("", repr(x)) for x in values])

                # Create a new name.
                # new_name = attr.__name__ + "_%d" % i
                new_name = attr.__name__ + "__" + human

                # Create a replacement function.
                def create_new_func(
                    func: types.FunctionType, names: list[str], values: list[Any]
                ) -> Callable[..., Any]:
                    # Create a kwargs dictionary.
                    kwargs = dict(zip(names, values))

                    @functools.wraps(func)
                    def new_func(self: types.FunctionType) -> Any:
                        return func(self, **kwargs)

                    # Manually set the name and return the new function.
                    new_func.__name__ = new_name
                    return new_func

                # Actually create the new function.
                new_func = create_new_func(attr, param_names, values)

                # Save this new function in our attrs dict.
                new_attrs[new_name] = new_func

            # Remove the old attribute from our new dictionary.
            del new_attrs[attr_name]

        # We create the class as normal, except we use our new attributes.
        return type.__new__(klass, name, bases, new_attrs)


# This is a class decorator that actually applies the above metaclass.
def parametrize_class(klass: type) -> ParametrizingMetaclass:
    return ParametrizingMetaclass(klass.__name__, klass.__bases__, klass.__dict__)