Giter Site home page Giter Site logo

aenum's People

Contributors

adamshapiro0 avatar ethanfurman avatar lambacck avatar schesis avatar subaruarai avatar wimglenn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

aenum's Issues

AttributeError: referencing enum from a class function

The following code fails with aenum (3.1.2)

The error is a class function can not reference the enumerated attributes.

This works with standard library Enum (both python 2.7 and 3.7).

AttributeError: E: no attribute 'AAA'
AttributeError: E: no attribute 'BBB'
import aenum

class E(aenum.Enum):
    AAA=1
    BBB=4

    @property
    def is_aaa(self):
        return self == self.AAA

    def is_bbb(self):
        return self == self.BBB
f = F.AAA

f.is_aaa
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in is_aaa
  File "/home/brendan/.local/lib/python2.7/site-packages/aenum/__init__.py", line 272, in __get__
    '%s: no attribute %r' % (ownerclass.__name__, self.name)
AttributeError: E: no attribute 'AAA'

f.is_bbb()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in is_bbb
  File "/home/brendan/.local/lib/python2.7/site-packages/aenum/__init__.py", line 272, in __get__
    '%s: no attribute %r' % (ownerclass.__name__, self.name)
AttributeError: E: no attribute 'BBB'
```

Allow creating NoAlias enums from enum members

Allow something like this: MyNoAliasEnum(MyNoAliasEnum.FOO). It makes sense why NoAlias enums can't be looked up by the raw value since they may not be unique, but unless I'm missing something I don't see a reason they couldn't be looked up by actual enum members. It should be as simple as switching these two if blocks: https://github.com/ethanfurman/aenum/blob/master/aenum/__init__.py#L3059-L3064 If there are no objections I could definitely open a PR with this change.

Motivation

Currently NoAlias enums are unusable with Pydantic as demonstrated in this example:

from aenum import NoAliasEnum
from pydantic import BaseModel

class MyEnum(NoAliasEnum):
    FOO = 1
    BAR = 1

class MyModel(BaseModel):
    val: MyEnum

MyModel(val=MyEnum.FOO)

which raises the following error:

ValidationError                           Traceback (most recent call last)
Cell In [104], line 1
----> 1 MyModel(val=MyEnum.FOO)

File ~/.pyenv/versions/3.8.13/envs/multistep-forecaster/lib/python3.8/site-packages/pydantic/main.py:341, in pydantic.main.BaseModel.__init__()

ValidationError: 1 validation error for MyModel
val
  NoAlias enumerations cannot be looked up by value (type=type_error)

This is because Pydantic automatically calls MyEnum(input_val) regardless of the actual type of input_val.

[BUG] RecursionError when using aenum along with with custom enum member access hook

I discovered this bug when using aenum along with pytorch-lightning 1.6, which caused a strange recursion error upon import. I've included below code to reproduce the issue and my root cause analysis.

Code to Reproduce Issue

from typing import Any
from enum import Enum, EnumMeta
import aenum


# metaclass copied directly from pytorch-lightning
class _OnAccessEnumMeta(EnumMeta):
    """Enum with a hook to run a function whenever a member is accessed.
    Adapted from:
    https://www.buzzphp.com/posts/how-do-i-detect-and-invoke-a-function-when-a-python-enum-member-is-accessed
    """

    def __getattribute__(cls, name: str) -> Any:
        obj = super().__getattribute__(name)
        if isinstance(obj, Enum):
            obj.deprecate()
        return obj

    def __getitem__(cls, name: str) -> Any:
        member: _OnAccessEnumMeta = super().__getitem__(name)
        member.deprecate()
        return member

    def __call__(cls, *args: Any, **kwargs: Any) -> Any:
        obj = super().__call__(*args, **kwargs)
        if isinstance(obj, Enum):
            obj.deprecate()
        return obj


class MyEnum(Enum, metaclass=_OnAccessEnumMeta):
    FOO = "bar"

Cause of Error

With v1.6, pytorch-lightning added a hook to their enums to check for deprecated members, which adds an isinstance() call inside of __getattribute__: https://github.com/PyTorchLightning/pytorch-lightning/blob/663216fe3edaed12205066a5601f3449b6cc50b8/pytorch_lightning/utilities/enums.py#L46-L68

Meanwhile aenum overwrites the standard enum module's implementation of __instancecheck__ to include code that calls __getattribute__:

aenum/aenum/__init__.py

Lines 3924 to 3925 in d80fba8

StdlibEnumMeta.__subclasscheck__ = __subclasscheck__
StdlibEnumMeta.__instancecheck__ = __instancecheck__

Therefore when accessing a member of a lightning enum, it calls __getattribute__, which if aenum has been imported, unexpectedly calls the aenum implementation of __instancecheck__ rather than the standard one, which calls __getattribute__, which calls aenum's __instancecheck__ again, etc., causing infinite recursion.

Discussion

I was very surprised to see this package overwriting part of the standard library. I feel that is very dangerous as it can break other packages depending on it, as seen here. I'd really prefer an alternative implementation where the aenum __instancecheck__ and __subclasscheck__ are only used on aenums rather than clobbering all enums used in a project.

MultiValueEnum with attribute as another enum

why wouldn't this work:

class WarningLevel(enum.Enum):
    INFO = 1
    WARNING = 2
    ERROR = 3

class WarningType2(aenum.MultiValueEnum):
    ONE = 1, "This is rule #1", WarningLevel.INFO
    TWO = 2, "This is rule #2", WarningLevel.ERROR
    THREE = 3, "This is rule #3", WarningLevel.INFO

it fails here but i don't see why this is necessary constraint and further more, how do i get around it?

aenum/_enum.py:973:

if enum_class._multivalue_ and (
        value in enum_class._value2member_map_
        or any(v == value for (v, m) in enum_class._value2member_seq_)
    raise ValueError('%r has already been used' % (value, ))

Not even this works:

class X1(aenum.MultiValueEnum):
    ONE = 1, 1

Direct support ABC/Protocol mixin

I suggest to add direct support of abstract base classes and protocols mixins to aenum (and stdlib enum if possible).

Currently addition of ABC/Protocol mixin to Enum raises errors:

  • For ABC : "TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases"
  • For Protocols: "TypeError: Protocols cannot be instantiated"

and require workarounds. As far as I know there are no workaround for Protocol mixin (if one wants to explicitly indicate implementation in Enum).

Composite Flag entries aren't listed as Enum members

When using the Flag enum class, entries that are composite with other entries omits them from the list of all enum entries, even if all entries are unique.

from aenum import Flag, auto
class SBInterfaceType(Flag):
    ALL = 0
    UNKNOWN = auto()
    URL = auto()
    PROD = auto()
    DEV = auto()
    S3 = auto()
    T3 = auto() | PROD | S3

[i for i in SBInterfaceTypes]

Result:
[<SBInterfaceTypes.UNKNOWN: 1>, <SBInterfaceTypes.URL: 2>, <SBInterfaceTypes.PROD: 4>, <SBInterfaceTypes.DEV: 8>, <SBInterfaceTypes.S3: 16>]
T3 is missing

T3 does appear in SBInterfaceType's _member_map_ but not in its _member_names_. Because it's not in the member_names, the Flag class's __iter__ method will skip it (it loops through all member_names to use as a key look up for member_map).

A fix that worked for me was to remove the is_single_bit check in the elif check in the following line of code:

and is_single_bit(value)

Though I'm not sure what the ramifications outside my use-case are for this change, or why that check was done in the first place.

mypy error when trying to iterate over aenum.Enum subclass

aenum: aenum-3.1.12
Mypy: mypy-1.3.0
Python: Python 3.11.3

mypy complains Type[IterFailingEnum]" has no attribute "__iter__" (not iterable) [attr-defined] when trying to iterate over anum.Enum subclass. mypy does not complain when iterating over an standard library Enum subclass. The code works still.

Is this an error in mypy or consequence of missing proper type hinting support in aenum?

Reproduceable error case

import aenum # type: ignore

class IterFailingEnum(aenum.Enum):
	_init_ = 'value fullname'
	_settings_ = aenum.MultiValue
	one = 1, 'One'
	two = 2, 'Two'
	three = 3, 'Three'

for e in IterFailingEnum:  # "Type[IterFailingEnum]" has no attribute "__iter__" (not iterable)  [attr-defined]
	print(e.fullname)

aenum 3.1.13 fails to import if sqlite3 does not exist

On a build of Python that lacks the _sqlite extension module, aenum._enum.SqliteEnum does not exist but it is still in __all__, breaking import:

>>> import aenum
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/.pyenv/versions/3.9.13/lib/python3.9/site-packages/aenum/__init__.py", line 10, in <module>
    from ._enum import *
AttributeError: module 'aenum._enum' has no attribute 'SqliteEnum'

how to get the precise value for the multi values?

here is my code as below

from aenum import MultiValueEnum

class BC(MultiValueEnum):
    TRUE = "ok", "right"
    FALSE = "no ok", "wrong"

print(BC("ok").value)   # actual: ok
print(BC("right").value)   # actual: ok,  but expected: right

add Licence

Hay,
Thanks for the extension of enum in python. Could you add a Licence to this Repo?

Can't set Flag enum from integer if MSB is set with any other bits (Python 2)

The following code works on Python 3 but not Python 2.

I get the error:

AttributeError: type object 'Permission' has no attribute '__qualname__'

From what I can deduce, the problem is with the most significant bit of the natural integer size of the system (bit-31 for 32-bit system, or bit-63 for 64-bit system). I can set that msb if that's the only bit present in the integer mask, but if others are set then the exception occurs.

On further investigation, anything that is a Python long type (Python 2) is not settable unless it is the only bit in the mask.

from aenum import Flag

class Permission( Flag ):
    EXEC    = 1 << 0
    WRITE   = 1 << 1
    READ    = 1 << 2
    MSB32   = 1 << 31
    MSB64   = 1 << 63

# set individual bits
print( Permission( 0x00000001 ) )
print( Permission( 0x00000002 ) )
print( Permission( 0x00000004 ) )

# set multiple bits
print( Permission( 0x00000007 ) )

# 32-bit system test
print( Permission( 0x80000000 ) )
print( Permission( 0x80000002 ) )

# 64-bit system test
print( Permission( 0x8000000000000000 ) )
print( Permission( 0x8000000000000002 ) )

On Python 2.7 (32-bit arm linux), I get the following output and error message.

# python2 test_aenum.py
Permission.EXEC
Permission.WRITE
Permission.READ
Permission.EXEC|READ|WRITE
Permission.MSB32
Traceback (most recent call last):
  File "test_aenum.py", line 21, in <module>
    print( Permission( 0x80000002 ) )
  File "/tmp/altfs/usr/local/lib/python2.7/dist-packages/aenum/__init__.py", line 2546, in __call__
    return cls.__new__(cls, value)
  File "/tmp/altfs/usr/local/lib/python2.7/dist-packages/aenum/__init__.py", line 2905, in __new__
    raise exc
AttributeError: type object 'Permission' has no attribute '__qualname__'

On Python 3.7 (64-bit linux), I get the following output.

$ python3 test_aenum.py 
Permission.EXEC
Permission.WRITE
Permission.READ
Permission.EXEC|WRITE|READ
Permission.MSB32
Permission.WRITE|MSB32
Permission.MSB64
Permission.WRITE|MSB64

`extend_enum` doesn't work with MultiValueEnum

Here's code to check:

from aenum import Enum, MultiValueEnum, extend_enum


class MyEnum(str, Enum):
    VALUE_1 = 'value_1'
    VALUE_2 = 'value_2'
    VALUE_3 = 'value_3'


print(repr(MyEnum.VALUE_1))
extend_enum(MyEnum, 'VALUE_4', 'value_4')
print(repr(MyEnum.VALUE_4))


class MyMultiValueEnum(str, MultiValueEnum):
    VALUE_1 = 'value_1', 'VALUE_1'
    VALUE_2 = 'value_2', 'VALUE_2'
    VALUE_3 = 'value_3', 'VALUE_3'


print(repr(MyMultiValueEnum.VALUE_1))
extend_enum(MyMultiValueEnum, 'VALUE_4', 'value_4', 'VALUE_4')
print(repr(MyMultiValueEnum.VALUE_4))

Result:

<MyEnum.VALUE_1: 'value_1'>
<MyEnum.VALUE_4: 'value_4'>
<MyMultiValueEnum.VALUE_1: 'value_1'>
<MyMultiValueEnum.VALUE_4: ''>

aenum 3.1.12 is on PyPi but not GitHub

3.1.12 is on PyPi (released 30 Mar 2023) but I can't find out what has changed since 3.1.11 as the GitHub repository has no changelog or commits or tags for 3.1.12.

Are they hiding somewhere else?

Where should I be looking for the change details?

I installed a venv on a Linux (Debian 10 Buster) system. The CHANGES file only goes up to 3.1.8

$ pip list | grep aenum
aenum                3.1.12


$ head venv_3/lib/python3.7/site-packages/aenum/CHANGES
3.1.8
=====

recalculate bits used after all flags created (sometimes needed when a custom
`__new__` is in place.


3.1.7
=====

UnboundLocalError: local variable '_unique_' referenced before assignment

Hi,

I'm currently running into an exception calling extend_enum() on an existing enum in version 3.0.0:

  File ".../site-packages/aenum/__init__.py", line 3353, in extend_enum
    if _unique_ or _multi_value_:
UnboundLocalError: local variable '_unique_' referenced before assignment

It looks like the exception handler at the start of the function may be missing a _unique_ = False - all the other variables defined in the try are set in the exception handler.

except AttributeError:

I looked back a bit and it looks like the _unique_ assignment used to be in there but was commented out for some time, and then got removed completely in commit e1d5de4 a couple weeks ago. Is it meant to be there? Any chance it could be fixed?

Cheers,

Adam

mypy complains with aenum.Enum, but not enum.Enum

Using Python 3.7 on Debian Buster 10.9 development host (target system is actually python2.7 based system).

The following code snippet gives a mypy error when I use aenum.Enum.

Argument "reg_id" to "register_get" has incompatible type "int"; expected "Register_Address"

No mypy error is reported when useing enum.Enum.

# from enum import Enum
from aenum import Enum

class Register_Address( Enum ):
    STATUS       = 0x0000
    GPIO_CTRL    = 0x0004

def register_get(
        reg_id,                 # type: Register_Address
    ) :                         # type: (...) -> int
    """Get a 32-bit register value."""
    # value = my_ioctl( reg_id, ... )
    value = reg_id.value
    return value

value = register_get( reg_id=Register_Address.GPIO_CTRL )

Test failures in Python 3.10

It seems imports like os, unittest and shutil are also missing in test_v3.py when the file is ran standalone. See also for a report in fedora : https://bugzilla.redhat.com/show_bug.cgi?id=1899122

PYTHONPATH=. ~/cpython/python aenum/test.py
.....................................................E................E...................................................E.........................................................................F.E.............................................................................................................................................................E../root/checked_repos/aenum/aenum/test.py:64: DeprecationWarning: invalid escape sequence \.
  'inspect\.getargspec\(\) is deprecated',
/root/checked_repos/aenum/aenum/test.py:3054: DeprecationWarning: invalid escape sequence \.
  with self.assertRaisesRegex(TypeError, 'Field\.BLAH: number of fields provided do not match init'):
F
======================================================================
ERROR: test_enum_with_value_name (__main__.TestEnum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/checked_repos/aenum/aenum/test.py", line 1264, in test_enum_with_value_name
    class Huh(Enum):
  File "/root/checked_repos/aenum/aenum/__init__.py", line 2391, in __new__
    enum_class.__dict__[name].__set_name__(enum_class, name)
KeyError: 'name'

======================================================================
ERROR: test_extending5 (__main__.TestEnum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/checked_repos/aenum/aenum/test.py", line 2539, in test_extending5
    class Color(Enum):
  File "/root/checked_repos/aenum/aenum/__init__.py", line 2391, in __new__
    enum_class.__dict__[name].__set_name__(enum_class, name)
KeyError: 'value'

======================================================================
ERROR: test_no_duplicates_kinda (__main__.TestEnum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/checked_repos/aenum/aenum/test.py", line 2757, in test_no_duplicates_kinda
    class Silly(UniqueEnum):
  File "/root/checked_repos/aenum/aenum/__init__.py", line 2391, in __new__
    enum_class.__dict__[name].__set_name__(enum_class, name)
KeyError: 'name'

======================================================================
ERROR: test_no_duplicates_kinda (aenum.test_v3.TestEnumV3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/checked_repos/aenum/aenum/test_v3.py", line 448, in test_no_duplicates_kinda
    class Silly(UniqueEnum):
  File "/root/checked_repos/aenum/aenum/__init__.py", line 2391, in __new__
    enum_class.__dict__[name].__set_name__(enum_class, name)
KeyError: 'name'

======================================================================
ERROR: test_unique_with_name (__main__.TestUnique)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/checked_repos/aenum/aenum/test.py", line 5714, in test_unique_with_name
    class Silly(Enum):
  File "/root/checked_repos/aenum/aenum/__init__.py", line 2391, in __new__
    enum_class.__dict__[name].__set_name__(enum_class, name)
KeyError: 'name'

======================================================================
FAIL: test_nested_classes_in_enum_do_not_create_members (aenum.test_v3.TestEnumV3)
Support locally-defined nested classes.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/checked_repos/aenum/aenum/test_v3.py", line 713, in test_nested_classes_in_enum_do_not_create_members
    self.assertTrue(isinstance(Outer.Inner, type))
AssertionError: False is not true

======================================================================
FAIL: /root/checked_repos/aenum/aenum/doc/aenum.rst
Doctest: aenum.rst
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/cpython/Lib/doctest.py", line 2208, in runTest
    raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for aenum.rst
  File "/root/checked_repos/aenum/aenum/doc/aenum.rst", line 0

----------------------------------------------------------------------
File "/root/checked_repos/aenum/aenum/doc/aenum.rst", line 1247, in aenum.rst
Failed example:
    class FieldTypes(Enum):
        name = 1
        value = 2
        size = 3
Exception raised:
    Traceback (most recent call last):
      File "/root/cpython/Lib/doctest.py", line 1340, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest aenum.rst[148]>", line 1, in <module>
        class FieldTypes(Enum):
      File "/root/checked_repos/aenum/aenum/__init__.py", line 2391, in __new__
        enum_class.__dict__[name].__set_name__(enum_class, name)
    KeyError: 'name'
----------------------------------------------------------------------
File "/root/checked_repos/aenum/aenum/doc/aenum.rst", line 1252, in aenum.rst
Failed example:
    FieldTypes.size.value
Exception raised:
    Traceback (most recent call last):
      File "/root/cpython/Lib/doctest.py", line 1340, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest aenum.rst[149]>", line 1, in <module>
        FieldTypes.size.value
    NameError: name 'FieldTypes' is not defined
----------------------------------------------------------------------
File "/root/checked_repos/aenum/aenum/doc/aenum.rst", line 1254, in aenum.rst
Failed example:
    FieldTypes.size
Exception raised:
    Traceback (most recent call last):
      File "/root/cpython/Lib/doctest.py", line 1340, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest aenum.rst[150]>", line 1, in <module>
        FieldTypes.size
    NameError: name 'FieldTypes' is not defined
----------------------------------------------------------------------
File "/root/checked_repos/aenum/aenum/doc/aenum.rst", line 1256, in aenum.rst
Failed example:
    FieldTypes.value.size
Expected:
    Traceback (most recent call last):
    ...
    AttributeError: FieldTypes: no attribute 'size'
Got:
    Traceback (most recent call last):
      File "/root/cpython/Lib/doctest.py", line 1340, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest aenum.rst[151]>", line 1, in <module>
        FieldTypes.value.size
    NameError: name 'FieldTypes' is not defined


----------------------------------------------------------------------
Ran 360 tests in 1.478s

FAILED (failures=2, errors=5)

Python 3.10.0a5+ (heads/master:cd80f430da, Feb 17 2021, 14:08:19)

Why does `_generate_next_value_` method of `StrEnum` returns lower case?

Hello,

I notice that the _generate_next_value_ method of StrEnum returns the lower case of its name by default. I found it a bit inconvenient when I use auto to generate the string representation while I also want it to be exactly the same as the name. On the other hand, if one wants lower case by default, there's already LowerStrEnum. So may I ask what's the point behind this, please?

Thanks in advance!

aenum/aenum/__init__.py

Lines 3193 to 3197 in d80fba8

def _generate_next_value_(name, start, count, last_values):
"""
Return the lower-cased version of the member name.
"""
return name.lower()

Tag releases

Hi there, could you please tag releases from now on, and at least the current one? I'm going to maintain this in the arch linux repositories and it would be very nice to have. Thanks in advance.

Flag auto() produces wrong value with multiple OR '|' operations.

auto() only factors in the last | operation on the right side. I've found a work around is to move the |'s to the left of auto but a fix should probably be done to resolve the code bug as well.

from aenum import Flag, auto
class SBInterfaceTypes(Flag):
    ALL = 0
    UNKNOWN = auto()
    URL = auto()
    PROD = auto()
    DEV = auto()
    S3 = auto()
    T3 = auto() | PROD | S3

list(SBInterfaceTypes._member_map_.values())
print(SBInterfaceTypes.T3)

result: [<SBInterfaceTypes.ALL: 0>, <SBInterfaceTypes.UNKNOWN: 1>, <SBInterfaceTypes.URL: 2>, <SBInterfaceTypes.PROD: 4>, <SBInterfaceTypes.DEV: 8>, <SBInterfaceTypes.S3: 16>, <SBInterfaceTypes.T3: 48>]
<SBInterfaceTypes.T3: 48> but should be 52.

32 | 4 | 16
= 52

Using T3 = PROD| S3 | auto() works as expected though.

Deprecation warnings due to invalid escape sequences in tests

Seems to be similar to #2

PYTHONPATH=. python3.9 -Wall aenum/test.py
/root/checked_repos/aenum/aenum/test.py:64: DeprecationWarning: invalid escape sequence \.
  'inspect\.getargspec\(\) is deprecated',
/root/checked_repos/aenum/aenum/test.py:3054: DeprecationWarning: invalid escape sequence \.
  with self.assertRaisesRegex(TypeError, 'Field\.BLAH: number of fields provided do not match init'):
............................................................................................................................................................................................................................................................................................................................................................................
----------------------------------------------------------------------
Ran 364 tests in 1.331s

OK

Unable to use `MultiValueEnum` with `pydantic>=2.7`

Hi,

After bumping pydantic to 2.7 version I've noticed that it's not possible to instantiate a Pydantic class having an attr of type aenum.MultiValueEnum.

Pydantic detects only the canonical value and raises a validation error when given other accepted values.

An example Model definition:

from pydantic import BaseModel
from aenum import MultiValueEnum

class MultiEnum(MultiValueEnum):
    NAME = "VAL", "value"

class SomeModel(BaseModel):
    attr: MultiEnum

When using canonical value VAL the model is created:

SomeModel(attr="VAL")
SomeModel(attr=<MultiEnum.NAME: 'VAL'>)

When using other value the model fails:

SomeModel(attr="value")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/pydantic/main.py", line 175, in __init__
    self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for SomeModel
attr
  Input should be 'VAL' [type=enum, input_value='value', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/enum

Could this problem be resolved by a fix in the aenum package or would it require action from Pydantic maintainers?

Test failure with Python 3.11

The build process of aenum for nixpkgs fails on Python 3.11 while it works on Python 3.10.

running install tests
.....................................................................................................................................................................................................................................E................................F....................................................................................................................................................................................
======================================================================
ERROR: test_stdlib_inheritence (aenum.test_v3.TestEnumV3.test_stdlib_inheritence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/4aps5crh9miyrkxvxs8z3p51iy9i6mh0-python3.11-aenum-3.1.11/lib/python3.11/site-packages/aenum/test_v3.py", line 91, in test_stdlib_inheritence
    class AStrEnum(StrFlag):
                   ^^^^^^^
NameError: name 'StrFlag' is not defined

======================================================================
FAIL: test_extend_flag_backwards_stdlib (aenum.test_v3.TestExtendEnumV3.test_extend_flag_backwards_stdlib)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/nix/store/4aps5crh9miyrkxvxs8z3p51iy9i6mh0-python3.11-aenum-3.1.11/lib/python3.11/site-packages/aenum/test_v3.py", line 1899, in test_extend_flag_backwards_stdlib
    self.assertTrue(Color(16) is Color.MAGENTA)
AssertionError: False is not true

----------------------------------------------------------------------
Ran 443 tests in 0.594s

FAILED (failures=1, errors=1)

extend an IntEnum with documentation fails

Following the example from https://stackoverflow.com/questions/28126314/adding-members-to-python-enums
but using IntEnum instead of Enum, yield an error.

from aenum import IntEnum
from aenum import extend_enum

class ColorHelp(IntEnum):
     _init_ = 'value __doc__'
     black = 0, 'the absence of color'

extend_enum(ColorHelp, 'white', 1, 'the presence of every color')

Traceback (most recent call last):

File "/tmp/ipykernel_13921/3054020738.py", line 1, in <cell line: 1>
extend_enum(ColorHelp, 'white', 1, 'the presence of every color')

File "/usr/local/lib/python3.10/dist-packages/aenum/init.py", line 3572, in extend_enum
new_member = _new(enumeration, *args, **kwds)

ValueError: invalid literal for int() with base 10: 'the presence of every color'

Pylint complains unsubscriptable-object when using AutoNumberEnum

16:01 ~ $ cat test.py
"""Test module"""

from aenum import AutoNumberEnum

class Status(AutoNumberEnum, init='task_idx'):
"""Status enum"""
STARTED = ()
RUNNING = ()
FINISTED = ()
FAILED = ()

def test():
"""test method"""
print(Status['STARTED'].task_idx)

16:01 ~ $ pylint test.py
************* Module test
test.py:14:10: E1136: Value 'Status' is unsubscriptable (unsubscriptable-object)

16:01 ~ $ pylint --version
pylint 2.13.9
astroid 2.11.5
Python 3.9.10 (main, Jan 15 2022, 11:48:00)
[Clang 13.0.0 (clang-1300.0.29.3)]

Autocomplete on Windows fails with

  File "C:\Users\...\.pyenv\pyenv-win\versions\3.11.5\Lib\rlcompleter.py", line 94, in complete
    self.matches = self.global_matches(text)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\...\.pyenv\pyenv-win\versions\3.11.5\Lib\rlcompleter.py", line 135, in global_matches
    matches.append(self._callable_postfix(val, word))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\...\.pyenv\pyenv-win\versions\3.11.5\Lib\rlcompleter.py", line 104, in _callable_postfix
    if not inspect.signature(val).parameters:
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\...\.pyenv\pyenv-win\versions\3.11.5\Lib\inspect.py", line 3280, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\...\.pyenv\pyenv-win\versions\3.11.5\Lib\inspect.py", line 3028, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\...\.pyenv\pyenv-win\versions\3.11.5\Lib\inspect.py", line 2480, in _signature_from_callable
    raise TypeError(
TypeError: unexpected object <bound method __signature__ of <aenum 'Language'>> in __signature__ attribute
class Language(aenum.MultiValueEnum):
    SI = "SI", _("Slovene")
    EN = "EN", _("English")
    HR = "HR", _("Croatian")
    DE = "DE", _("German")
    RU = "RU", _("Russian")

    def __init__(self, code: str, name: str):
        self._code = code
        self._name = name

    @property
    def code(self):
        return self._code

    @property
    def name(self):
        return self._name

    @property
    def value(self):
        return self.code

import Language
Lang... and then tab tab tab

`aenum>=3.1.13` fails to import on PyPy3

Hi,

I'm getting this NoneType releated error when using recent aenum releases with PyPy:

$ pypy3 -m pip install -qqq aenum==3.1.12 && pypy3 -c 'import aenum'

$ pypy3 -m pip install -qqq aenum==3.1.13 && pypy3 -c 'import aenum'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/josiah/.local/lib/pypy3.9/site-packages/aenum/__init__.py", line 7, in <module>
    from ._common import *
AttributeError: module 'aenum._common' has no attribute 'NoneType'

$ pypy3 -V
Python 3.9.16 (feeb267ead3e6771d3f2f49b83e1894839f64fb7, Feb 21 2023, 19:39:22)
[PyPy 7.3.11 with GCC 12.2.1 20230201]

It only occurs in PyPy, CPython is fine.

Package is broken in 3.1.14 ModuleNotFoundError: No module named 'aenum._common'

aenum 3.1.12 works fine, the now automatically pulled pip dependency 3.1.14 is broken.

  File "/home/liam/klippy-env/lib/python3.9/site-packages/aenum/__init__.py", line 7, in <module>
    from ._common import *
ModuleNotFoundError: No module named 'aenum._common'

Environment:
Raspberry Pi 3+, Raspbian Lite 32-bit (debian bullseye) Linux 6.1.21-v7+ #1642 SMP Mon Apr 3 17:20:52 BST 2023 armv7l GNU/Linux

Not throwing TypeError if `extend_enum` is called without the required number of arguments

There is an undefined variable name undefined in _enum.py causing errors if there are not enough arguments given to extend_enum (might be in other cases too but haven't tested)

To recreate:

from aenum import Enum, extend_enum

class MyEnum(Enum, init="var1 var2"):
    pass

extend_enum(MyEnum, "NEW_MEMBER", "my_var1")

Output:

Traceback (most recent call last):
  ...
  File "...\aenum\_enum.py", line 2722, in extend_enum
    new_member.__init__(*args)
  File "...\aenum\_enum.py", line 2203, in __init__
    value = kwds.pop(name, undefined)
NameError: name 'undefined' is not defined

Relevant lines from __init__ of _enum.py:

2200        if len(args) < len(_auto_init_):
2201            remaining_args = _auto_init_[len(args):]
2202            for name in remaining_args:
2203                value = kwds.pop(name, undefined)
2204                if value is undefined:
2205                    raise TypeError('missing value for: %r' % (name, ))
2206                setattr(self, name, value)

Add default docstring to Enum class in aenum

Hi,
I'm using aenum with Pydantic models which generates an Openapi schema to use with Swagger UI. Pydantic will by default use the Enum class docstring as description in Swagger UI schema models.(https://github.com/samuelcolvin/pydantic/blob/13a5c7d676167b415080de5e6e6a74bea095b239/pydantic/schema.py#L620).

In the regular enum library the default docstring "An enumeration" is set for the Enum class .(https://github.com/python/cpython/blob/ee9f98d9f4b881ee15868a836a2b99271df1bc0e/Lib/enum.py#L226)

Is it possible to add a similar default docstring for the Enum class in aenum? e.g. "An advanced enumeration".

Pydantic expects a docstring to be set for Enum class, without it an error string will show in the generated Swagger UI schema.

Regression - extend_enum broken on 3.1.1 using py36

Here is a sample code :


from aenum import Enum, extend_enum


class TestEnum(Enum):
    ABC = {
        'id': 0,
        'value': 'abc'
    }
    DEF = {
        'id': 1,
        'value': 'def'
    }

rand = uuid.uuid4().hex
new_value = {
    'id': 99,
    'value': 'new',
}
extend_enum(TestEnum, rand, new_value)

That generated this exception :

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/Workspace/tools/mesos-services-configuration/.venv/lib/python3.6/site-packages/aenum/__init__.py in _finalize_extend_enum(enumeration, new_member, name, bits, mask, is_alias)
   3544         try:
-> 3545             enumeration._value2member_map_[v] = new_member
   3546         except TypeError:

TypeError: unhashable type: 'dict'

During handling of the above exception, another exception occurred:

UnboundLocalError                         Traceback (most recent call last)
<ipython-input-6-28ca1a4c681b> in <module>
     18     'value': 'new',
     19 }
---> 20 extend_enum(TestEnum, rand, new_value)

~/Workspace/tools/mesos-services-configuration/.venv/lib/python3.6/site-packages/aenum/__init__.py in extend_enum(enumeration, name, *args, **kwds)
   3521         else:
   3522             # if we get here, we have a brand new member
-> 3523             return _finalize_extend_enum(enumeration, new_member)
   3524 
   3525 def _finalize_extend_enum(enumeration, new_member, name=None, bits=None, mask=None, is_alias=False):

~/Workspace/tools/mesos-services-configuration/.venv/lib/python3.6/site-packages/aenum/__init__.py in _finalize_extend_enum(enumeration, new_member, name, bits, mask, is_alias)
   3545             enumeration._value2member_map_[v] = new_member
   3546         except TypeError:
-> 3547             _value2member_seq_ += ((v, new_member), )
   3548     if bits:
   3549         enumeration._all_bits_ = bits

UnboundLocalError: local variable '_value2member_seq_' referenced before assignment```

Regression - extend_enum broken for Flag and IntFlag as of version 3.0.0

extend_enum is broken for Flag and IntFlag under versions 3.0.0 and 3.1.0.

The following works in version 2.2.6:

>>> class FlagTest(Flag): # Or IntFlag
...     NONE = 0
...     LOW = 1
...     MID = 2
...
>>> extend_enum(FlagTest, 'HIGH', 4)
>>> FlagTest.LOW | FlagTest.HIGH
<FlagTest.HIGH|LOW: 5>
>>> FlagTest(5)
<FlagTest.HIGH|LOW: 5>

It fails under later versions, both when OR'ing two values together and when constructing from a value:

  • Flag enums produce a ValueError: FlagTest: invalid value: 5\n given 0b0 101\n allowed 0b0 011
  • IntFlag enums produce no error but return an int, not an enum

More detailed test cases attached, suitable for running with doctest:

$ python -m doctest testcases.txt

aenum/__init__.py:220: DeprecationWarning: invalid escape sequence \w

In python3.8, under pytest, I started seeing the following warning:

... /lib/python3.8/site-packages/aenum/__init__.py:220
... /lib/python3.8/site-packages/aenum/__init__.py:220: DeprecationWarning: invalid escape sequence \w

Trivial fix:

Change line 220 in aenum/__init__.py to use a raw string (r prefix):

pattern = r'^_%s__\w+[^_]_?$' % (cls_name, )

Thank you.

Convert test script to proper unittest test module

When packaging aenum for openSUSE I would like to switch from running the script directly to using our packaging macros which use python3 -munittest (and some environmental variables set up for our purposes), so I have created this tempdir_missing.patch, which moves
all fixture settings from if __name__=="__main__": construct to proper setUp methods.

Unfortunately, apparently I have done something wrong because two test cases fail:

[   27s]
[   27s] ======================================================================
[   27s] ERROR: test_convert_ (aenum.test.TestIntEnumConvert)
[   27s] ----------------------------------------------------------------------
[   27s] Traceback (most recent call last):
[   27s]   File "/home/abuild/rpmbuild/BUILD/aenum-3.1.0/aenum/test.py", line 6247, in test_convert_
[   27s]     self.assertEqual(test_type.CONVERT_TEST_NAME_F,
[   27s]   File "/usr/lib64/python3.6/enum.py", line 326, in __getattr__
[   27s]     raise AttributeError(name) from None
[   27s] AttributeError: CONVERT_TEST_NAME_F
[   27s]
[   27s] ======================================================================
[   27s] ERROR: test_convert_value_lookup_priority (aenum.test.TestIntEnumConvert)
[   27s] ----------------------------------------------------------------------
[   27s] Traceback (most recent call last):
[   27s]   File "/home/abuild/rpmbuild/BUILD/aenum-3.1.0/aenum/test.py", line 6229, in test_convert_value_lookup_priority
[   27s]     self.assertEqual(test_type(5).name, 'CONVERT_TEST_NAME_A')
[   27s]   File "/home/abuild/rpmbuild/BUILD/aenum-3.1.0/aenum/__init__.py", line 2576, in __call__
[   27s]     return cls.__new__(cls, value)
[   27s]   File "/home/abuild/rpmbuild/BUILD/aenum-3.1.0/aenum/__init__.py", line 2927, in __new__
[   27s]     raise ve_exc
[   27s] ValueError: 5 is not a valid UnittestConvert

What do you think, please? Both about my patch (and the idea behind it) and what I do wrong.

Complete build log with all packages used and all steps taken.

[question] How would one make value comparisons between separate enums possible?

If I have 3 enums as such:

from aenum import Enum

class QuadSidesEnum(Enum):
    TOP = "TOP"
    RIGHT = "RIGHT"
    BOTTOM = "BOTTOM"
    LEFT = "LEFT"

class QuadCornersEnum(Enum):
    TOPLEFT = "TOPLEFT"
    TOPRIGHT = "TOPRIGHT"
    BOTTOMLEFT = "BOTTOMLEFT"
    BOTTOMRIGHT = "BOTTOMRIGHT"

class QuadCorndersNSidesEnum(Enum):
    TOPLEFT = "TOPLEFT"
    TOPRIGHT = "TOPRIGHT"
    BOTTOMLEFT = "BOTTOMLEFT"
    BOTTOMRIGHT = "BOTTOMRIGHT"
    TOP = "TOP"
    RIGHT = "RIGHT"
    BOTTOM = "BOTTOM"
    LEFT = "LEFT"

When designing an API, how would one go about making value comparisons between my QuadCorndersNSidesEnum and QuadCornersEnum/QuadSidesEnum possible? Do I have any options beyond using .value directly in my expression, like QuadCorndersNSidesEnum.TOPLEFT.value == QuadCornersEnum.TOPLEFT.value ?

successive auto()'s not functioning correctly with _init_

If auto has non-value arguments and is called successively all following calls will be set to the previous value. e.g.

from aenum import Enum, auto
class A(Enum):
    _init_ = 'value comment'
    a = 1, 'comment for a'
    b = auto('comment for b')
    c = auto('comment for c')

In the above A.a and A.b are unique, but A.b == A.c - but if you do the following, everything is fine:

from aenum import Enum, auto
class A(Enum):
    _init_ = 'value comment'
    a = 1, 'comment for a'
    b = auto('comment for b')
    d = 3, 'comment for d'
    c = auto('comment for c')

Also, if in above you set d = 2, 'comment for d', A.c is fine, but A.d == A.b.

Somewhat related, it would be nice to be able to write a custom function to handle empty auto's / provide default arguments for init.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.