Passed
Push — main ( 4e3485...6d4817 )
by Douglas
01:40
created

FlagEnum._create_pseudo_member()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
import enum
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import logging
3
from typing import AbstractSet
4
5
from pocketutils.core.exceptions import XKeyError
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
6
7
logger = logging.getLogger("pocketutils")
8
9
10
class DisjointEnum(enum.Enum):
11
    """
12
    An enum that does not have combinations.
13
    """
14
15
    @classmethod
16
    def _fix_lookup(cls, s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
17
        return s
18
19
    @classmethod
20
    def or_none(cls, s: str | __qualname__) -> __qualname__ | None:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
21
        """
22
        Returns a choice by name (or returns ``s`` itself).
23
        Returns ``None`` if the choice is not found.
24
        """
25
        try:
26
            return cls.of(s)
27
        except KeyError:
28
            return None
29
30
    @classmethod
31
    def of(cls, s: str | __qualname__) -> __qualname__:
0 ignored issues
show
Coding Style Naming introduced by
Method name "of" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
32
        """
33
        Returns a choice by name (or returns ``s`` itself).
34
        """
35
        if isinstance(s, cls):
36
            return s
37
        return cls[cls._fix_lookup(s)]
38
39
    def __new__(cls, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument args seems to be unused.
Loading history...
Unused Code introduced by
The argument kwargs seems to be unused.
Loading history...
40
        value = len(cls.__members__) + 1
41
        obj = object.__new__(cls)
42
        obj._value_ = value
43
        return obj
44
45
    def __repr__(self):
0 ignored issues
show
introduced by
__repr__ does not return str
Loading history...
46
        return self.name
47
48
    def __str__(self):
0 ignored issues
show
introduced by
__str__ does not return str
Loading history...
49
        return self.name
50
51
52
class FlagEnum(enum.Flag):
53
    """
54
    A bit flag that behaves as a set, has a null set, and auto-sets values and names.
55
56
    Example:
57
        .. code-block::
58
59
            class Flavor(FlagEnum):
60
                NONE = ()
61
                BITTER = ()
62
                SWEET = ()
63
                SOUR = ()
64
                UMAMI = ()
65
            bittersweet = Flavor.BITTER | Flavor.SWEET
66
            print(bittersweet.value)  # 1 + 2 == 3
67
            print(bittersweet.name)   # "bitter|sweet"
68
69
    .. important::
70
        The *first element* must always be the null set ("no flags")
71
        and should be named something like 'none', 'empty', or 'zero'
72
    """
73
74
    @classmethod
75
    def _fix_lookup(cls, s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
76
        return s
77
78
    def __new__(cls, *args, **kwargs):
0 ignored issues
show
Unused Code introduced by
The argument args seems to be unused.
Loading history...
Unused Code introduced by
The argument kwargs seems to be unused.
Loading history...
79
        if len(cls.__members__) == 0:
80
            value = 0
81
        else:
82
            value = 2 ** (len(cls.__members__) - 1)
83
        obj = object.__new__(cls)
84
        obj._value_ = value
85
        return obj
86
87
    @classmethod
88
    def _create_pseudo_member(cls, value):
89
        value = super()._create_pseudo_member(value)
0 ignored issues
show
Bug introduced by
The Super of FlagEnum does not seem to have a member named _create_pseudo_member.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
90
        members, _ = enum._decompose(cls, value)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _decompose was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
91
        value._name_ = "|".join([m.name for m in members])
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _name_ was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
92
        return value
93
94
    @classmethod
95
    def or_none(cls, s: str | __qualname__) -> __qualname__ | None:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
96
        """
97
        Returns a choice by name (or returns ``s`` itself).
98
99
        Returns:
100
            ``None`` if the choice is not found.
101
        """
102
        try:
103
            return cls.of(s)
104
        except KeyError:
105
            return None
106
107
    @classmethod
108
    def of(cls, s: str | __qualname__ | AbstractSet[str | __qualname__]) -> __qualname__:
0 ignored issues
show
Coding Style Naming introduced by
Method name "of" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
109
        """
110
        Returns a choice by name (or ``s`` itself), or a set of those.
111
        """
112
        if isinstance(s, cls):
113
            return s
114
        if isinstance(s, str):
115
            return cls[cls._fix_lookup(s)]
116
        z = cls(0)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "z" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
117
        for m in s:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "m" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
118
            z |= cls.of(m)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "z" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
119
        return z
120
121
    def __repr__(self):
0 ignored issues
show
introduced by
__repr__ does not return str
Loading history...
122
        return self.name
123
124
    def __str__(self):
0 ignored issues
show
introduced by
__str__ does not return str
Loading history...
125
        return self.name
126
127
128
class TrueFalseEither(DisjointEnum):
129
    """
130
    A :class:`pocketutils.core.enums.DisjointEnum` of true, false, or unknown.
131
    """
132
133
    TRUE = ()
134
    FALSE = ()
135
    EITHER = ()
136
137
    @classmethod
138
    def _if_not_found(cls, s: str | __qualname__) -> __qualname__:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The argument s seems to be unused.
Loading history...
139
        return cls.EITHER
140
141
    @classmethod
142
    def _fix_lookup(cls, s: str) -> str:
143
        return s.upper().strip()
144
145
146
class MultiTruth(FlagEnum):
147
    """
148
    A :class:`pocketutils.core.enums.FlagEnum` for true, false, true+false, and neither.
149
    """
150
151
    FALSE = ()
152
    TRUE = ()
153
154
155
class CleverEnum(DisjointEnum):
156
    """
157
    An enum with a :meth:`of` method that finds values with limited string/value fixing.
158
    Replaces ``" "`` and ``"-"``` with ``_`` and ignores case in :meth:`of`.
159
    May support an "unmatched" type via :meth:`_if_not_found`,
160
    which can return a fallback value when there is no match.
161
162
    Example:
163
        class Thing(CleverEnum):
164
            BUILDING = ()
165
            OFFICE_SUPPLY = ()
166
            POWER_OUTLET = ()
167
168
        x = Thing.of("power outlet")
169
170
    Example:
171
        class Color(CleverEnum):
172
            RED = ()
173
            GREEN = ()
174
            BLUE = ()
175
            OTHER = ()
176
177
            @classmethod
178
            def _if_not_found(cls, s: str) -> __qualname__:
179
                # raise XValueError(f"No member for value '{s}'", value=s) from None
180
                #   ^
181
                #   the default implementation
182
                logger.warning(f"Color {s} unknown; using {cls.OTHER}")
183
                return cls.OTHER
184
185
    .. important::
186
187
        If :meth:`_if_not_found` is overridden, it should return a member value.
188
        (In particular, it should never return ``None``.)
189
190
    .. important::
191
192
        To use with non-uppercase enum values (e.g. ``Color.red`` instead of ``Color.RED``),
193
        override :meth:`_fix_lookup` with this::
194
195
            @classmethod
196
            def _fix_lookup(cls, s: str) -> str:
197
                return s.strip().replace(" ", "_").replace("-", "_").lower()
198
                #                                                      ^
199
                #                                                    changed
200
    """
201
202
    @classmethod
203
    def of(cls, s: str | __qualname__) -> __qualname__:
204
        try:
205
            return super().of(s)
206
        except KeyError:
207
            return cls._if_not_found(s)
208
209
    @classmethod
210
    def _if_not_found(cls, s: str | __qualname__) -> __qualname__:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
211
        raise XKeyError(f"No member for value '{s}'", key=s) from None
212
213
    @classmethod
214
    def _fix_lookup(cls, s: str) -> str:
215
        return s.strip().replace(" ", "_").replace("-", "_").upper()
216
217
218
__all__ = ["TrueFalseEither", "DisjointEnum", "FlagEnum", "CleverEnum", "MultiTruth"]
219