Passed
Push — main ( 60119b...e5c7f7 )
by Douglas
02:02
created

pocketutils.core.enums   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 183
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 99
dl 0
loc 183
rs 10
c 0
b 0
f 0
wmc 27

18 Methods

Rating   Name   Duplication   Size   Complexity  
A CleverEnum.of() 0 10 3
A FlagEnum.or_none() 0 10 2
A FlagEnum._fix_lookup() 0 3 1
A TrueFalseUnknown._fix_lookup() 0 4 1
A DisjointEnum.__new__() 0 5 1
A DisjointEnum.__str__() 0 2 1
A FlagEnum._create_pseudo_member_() 0 6 1
A CleverEnum._fix_lookup() 0 3 1
A FlagEnum.__new__() 0 8 2
A DisjointEnum.or_none() 0 10 2
A DisjointEnum._fix_lookup() 0 3 1
A CleverEnum._unmatched_type() 0 3 1
A FlagEnum.of() 0 13 4
A DisjointEnum.of() 0 8 2
A DisjointEnum.__repr__() 0 2 1
A FlagEnum.__str__() 0 2 1
A FlagEnum.__repr__() 0 2 1
A TrueFalseUnknown._unmatched_type() 0 3 1
1
import enum
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import logging
3
from typing import AbstractSet, Optional, Union
4
5
from pocketutils.core.exceptions import XValueError
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: Union[str, __qualname__]) -> Optional[__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...
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: Union[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...
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...
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)
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: Union[str, __qualname__]) -> Optional[__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...
96
        """
97
        Returns a choice by name (or returns ``s`` itself).
98
        Returns ``None`` if the choice is not found.
99
        """
100
        try:
101
            return cls.of(s)
102
        except KeyError:
103
            return None
104
105
    @classmethod
106
    def of(cls, s: Union[str, __qualname__, AbstractSet[Union[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...
107
        """
108
        Returns a choice by name (or ``s`` itself), or a set of those.
109
        """
110
        if isinstance(s, cls):
111
            return s
112
        if isinstance(s, str):
113
            return cls[cls._fix_lookup_(s)]
0 ignored issues
show
Bug introduced by
Class 'FlagEnum' has no '_fix_lookup_' member; maybe '_fix_lookup'?

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...
114
        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...
115
        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...
116
            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...
117
        return z
118
119
    def __repr__(self):
0 ignored issues
show
introduced by
__repr__ does not return str
Loading history...
120
        return self.name
121
122
    def __str__(self):
0 ignored issues
show
introduced by
__str__ does not return str
Loading history...
123
        return self.name
124
125
126
class TrueFalseUnknown(DisjointEnum):
127
    """
128
    A :class:`pocketutils.core.enums.DisjointEnum` of true, false, or unknown.
129
    """
130
131
    true = ()
132
    false = ()
133
    unknown = ()
134
135
    @classmethod
136
    def _unmatched_type(cls) -> Optional[__qualname__]:
137
        return cls.unknown
138
139
    @classmethod
140
    def _fix_lookup(cls, s: str) -> str:
141
        s = s.lower().strip()
142
        return dict(t="true", false="false").get(s, s)
143
144
145
class MultiTruth(FlagEnum):
146
    """
147
    A :class:`pocketutils.core.enums.FlagEnum` for true, false, true+false, and neither.
148
    """
149
150
    false = ()
151
    true = ()
152
153
154
class CleverEnum(DisjointEnum):
155
    """
156
    An enum with a ``.of`` method that finds values with limited string/value fixing.
157
    Replaces ``" "``, ``"-"``, and ``"."`` with ``_`` and ignores case in :meth:`of`.
158
    May support an "unmatched" type -- a fallback value when there is no match.
159
    This is similar to the simpler :class:`pocketutils.core.SmartEnum`.
160
    """
161
162
    @classmethod
163
    def of(cls, s: Union[str, __qualname__]) -> __qualname__:
164
        try:
165
            return super().of(s)
166
        except KeyError:
167
            unknown = cls._unmatched_type()
168
            logger.error(f"Value {s} not found. Using {unknown}")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
169
            if unknown is None:
170
                raise XValueError(f"Value {s} not found and unmatched_type is None")
171
            return unknown
172
173
    @classmethod
174
    def _unmatched_type(cls) -> Optional[__qualname__]:
175
        return None
176
177
    @classmethod
178
    def _fix_lookup(cls, s: str) -> str:
179
        return s.strip().replace(" ", "_").replace(".", "_").replace("-", "_").lower()
180
181
182
__all__ = ["TrueFalseUnknown", "DisjointEnum", "FlagEnum", "CleverEnum"]
183