Skip to main content

Extends the capabilities of the standard Enum.

Project description

extended-enum

Introduction

Package that expands the capabilities of the standard Enum.

There are times when you want to have constants that also carry additional information. This functionality can be implemented in different ways, one of them is mapping, but there is a better approach - using ExtendedEnum. ExtendedEnum - allows you to store dataclass as a value. This allows you to store the value and additional information in one object and no longer need to use any auxiliary data containers.

It is important to note that the functionality of the standard Enum is preserved. ExtendedEnum is just an add-on.

Install

Installation does not require any additional dependencies. ExtendedEnum was specifically designed to be a lightweight package that uses only standard Python functionality.

pip install extended-enum

Features

  • You can store a value and additional information inside an Enum member. Initially, the ValueWithDescription class is available, which additionally stores a description. You can create a custom class SomeExtendedEnumValue.
from extended_enum import ExtendedEnum, BaseExtendedEnumValue, ValueWithDescription, EnumField

class DetailedEnum(ExtendedEnum):
    CONST1 = EnumField(BaseExtendedEnumValue(value='const1'))
    CONST2 = EnumField(ValueWithDescription(value='const2', description='some description 2'))
    CONST3 = EnumField(ValueWithDescription(value='const3', description='some description 3'))
from dataclasses import dataclass, field
from extended_enum import ExtendedEnum, BaseExtendedEnumValue, EnumField
from typing import Optional

@dataclass(frozen=True)
class SomeExtendedEnumValue(BaseExtendedEnumValue):
    display_name: str = field(compare=False)
    description: Optional[str] = field(default=None, compare=False)

class DetailedEnum(ExtendedEnum):
    CONST1 = EnumField(SomeExtendedEnumValue(value='const1', display_name='ONE'))
    CONST2 = EnumField(SomeExtendedEnumValue(value='const2', display_name='TWO', description='some description 2'))
from uuid import UUID
from extended_enum import ExtendedEnum, EnumField

class MixedEnum(ExtendedEnum):
    CONST1 = EnumField('const1')
    CONST2 = EnumField(2)
    CONST3 = EnumField(UUID('79ff3431-3e98-4bec-9a4c-63ede2580f83'))
  • Additionally created attributes:
    • extended_value - Get the expanded value of an enumeration member.
    • get_values - Get a list of values of an enumeration.
    • get_extended_values - Get a list of values (in expanded form) of an enumeration.
    • get_members - Get the members of the enumeration.
    • get_simple_value_member - Get a mapping of enumeration members to simple values.
>>> from extended_enum import ExtendedEnum, BaseExtendedEnumValue, ValueWithDescription, EnumField
>>> class DetailedEnum(ExtendedEnum):
...     CONST1 = EnumField(ValueWithDescription(value='const1'))
...     CONST2 = EnumField(ValueWithDescription(value='const2', description='some description 2'))
...     CONST3 = EnumField(ValueWithDescription(value='const3', description='some description 3'))
>>> DetailedEnum.CONST2.value
'const2'
>>>
>>> DetailedEnum.CONST2.extended_value
ValueWithDescription(value='const2', description='some description 2')
>>>
>>> DetailedEnum.get_values()
('const1', 'const2', 'const3')
>>>
>>> DetailedEnum.get_extended_values()
(ValueWithDescription(value='const1', description=None), 
 ValueWithDescription(value='const2', description='some description 2'), 
 ValueWithDescription(value='const3', description='some description 3'))
>>>
>>> DetailedEnum.get_members()
mappingproxy({
    'CONST1': <DetailedEnum.CONST1: ValueWithDescription(value='const1', description=None)>, 
    'CONST2': <DetailedEnum.CONST2: ValueWithDescription(value='const2', description='some description 2')>, 
    'CONST3': <DetailedEnum.CONST3: ValueWithDescription(value='const3', description='some description 3')>
})
>>> DetailedEnum.get_simple_value_member()
{
    'const1': <DetailedEnum.CONST1: ValueWithDescription(value='const1', description=None)>, 
    'const2': <DetailedEnum.CONST2: ValueWithDescription(value='const2', description='some description 2')>, 
    'const3': <DetailedEnum.CONST3: ValueWithDescription(value='const3', description='some description 3')>
}
  • You can make unique enumerations using enum.unique in the same way as with a standard Enum.
>>> from enum import unique
>>> from extended_enum import ExtendedEnum, EnumField
>>> @unique
... class Mistake(ExtendedEnum):
...     ONE = EnumField(1)
...     TWO = EnumField(2)
...     THREE = EnumField(2)
...     FOUR = EnumField('four')
...     FIVE = EnumField('five')
...     SIX = EnumField('four')
...     SEVEN = EnumField(UUID('1a882a33-f0e2-4b9f-a880-30db10c2c7dc'))
...     EIGHT = EnumField(UUID('1a882a33-f0e2-4b9f-a880-30db10c2c7dc'))
...     NINE = EnumField(UUID('f0602460-77fb-4980-9900-4e3f50093b78'))
... 
ValueError: duplicate values found in <enum 'Mistake'>: THREE -> TWO, SIX -> FOUR, EIGHT -> SEVEN
>>> 
>>> # Or without decorator
>>> unique(Mistake)
ValueError: duplicate values found in <enum 'Mistake'>: THREE -> TWO, SIX -> FOUR, EIGHT -> SEVEN
  • You can make a nice display in automatic documentation, for example in /redoc. Below is an example for FastAPI
extended-enum_in_fastapi
from typing import Literal

import uvicorn
from fastapi import FastAPI
from pydantic import Field, BaseModel

from extended_enum import ExtendedEnum, EnumField, ValueWithDescription
from extended_enum.tools import format_to_markdown

app = FastAPI()


class CompressedFileExtension(ExtendedEnum):
    LZ = EnumField(ValueWithDescription(
        value='.lz',
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    ))
    IZO = EnumField(ValueWithDescription(
        value='.izo',
        description='Lossless data compression algorithm that is focused on decompression speed'
    ))
    IZMA = EnumField(ValueWithDescription(
        value='.izma',
        description='Uses a dictionary compression scheme and features a high compression ratio while still maintaining decompression speed'
    ))
    ZIP = EnumField('.zip')


FileExtension2Type = Literal[CompressedFileExtension.IZMA, CompressedFileExtension.ZIP]


class SomeRequestBody(BaseModel):
    file_path: str
    file_extension1: CompressedFileExtension = Field(
        default=CompressedFileExtension.ZIP,
        description='The following file extensions are supported.\n'
                    '{0}'.format(format_to_markdown(CompressedFileExtension))
    )
    file_extension2: FileExtension2Type = Field(
        description='The following file extensions are supported.\n'
                    '{0}'.format(format_to_markdown(FileExtension2Type))
    )


@app.get('/')
async def example(body: SomeRequestBody):
    return {}


if __name__ == '__main__':
    uvicorn.run(app=app, host='localhost', port=8000, workers=1)

Usage

Quick Start

ExtendedEnum is very easy to use. Switching from standard Enum is not difficult.

Let's look at an example. You have an enum that denotes file extensions declared like this:

from enum import Enum

class FilenameExtension(Enum):
    LZ = '.lz'
    IZO = '.izo'
    IZMA = '.izma'
    ZIP = '.zip'

Let's clarify these incomprehensible character sets. We will additionally need an EnumField wrapper for each value and a data class - ValueWithDescription to store additional information. Let's add a description for 3 members, a .zip will be left without a description because everyone knows it.

from extended_enum import ExtendedEnum, EnumField, ValueWithDescription

class CompressedFileExtension(ExtendedEnum):
    LZ = EnumField(ValueWithDescription(
        value='.lz',
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    ))
    IZO = EnumField(ValueWithDescription(
        value='.izo',
        description='Lossless data compression algorithm that is focused '
                    'on decompression speed'
    ))
    IZMA = EnumField(ValueWithDescription(
        value='.izma',
        description='Uses a dictionary compression scheme and features '
                    'a high compression ratio while still maintaining '
                    'decompression speed'
    ))
    ZIP = EnumField('.zip')

That's it, we have completed the transition. Let's see what is stored inside the class.

>>> from pprint import pprint
>>> pprint(list(CompressedFileExtension))
[
    <CompressedFileExtension.LZ: ValueWithDescription(
        value='.lz',
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    )>,
    <CompressedFileExtension.IZO: ValueWithDescription(
        value='.izo', 
        description='Lossless data compression algorithm that is focused on decompression speed'
    )>,
    <CompressedFileExtension.IZMA: ValueWithDescription(
        value='.izma', 
        description='Uses a dictionary compression scheme and features a high compression ratio while still maintaining decompression speed'
    )>,
    <CompressedFileExtension.ZIP: BaseExtendedEnumValue(value='.zip')>
]

Serializing values is also not difficult.

>>> import json
>>> from enum import Enum
>>> from typing import Any
>>> def dump_enum(value: Any) -> str:
...    if isinstance(value, Enum):
...        return value.value
...    raise TypeError()
>>> json.dumps(
...     {
...         "file_extension1": CompressedFileExtension.IZO, 
...         "file_extension2": CompressedFileExtension.ZIP
...     },
...     default=dump_enum
... )
'{"file_extension1": ".izo", "file_extension2": ".zip"}'
>>> CompressedFileExtension('.lz')
<CompressedFileExtension.LZ: ValueWithDescription(
    value='.lz', 
    description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
)>
>>> CompressedFileExtension('.unknown')
ValueError: '.unknown' is not a valid CompressedFileExtension

Easily works with orjson.

>>> import orjson
>>> orjson.dumps(
...     {
...         "file_extension1": CompressedFileExtension.IZO, 
...         "file_extension2": CompressedFileExtension.ZIP
...     }
... )
b'{"file_extension1":".izo","file_extension2":".zip"}'

Also works great with Pydantic v1.

>>> from extended_enum import ExtendedEnum, EnumField, ValueWithDescription
>>> from pydantic import BaseModel
>>> class SomeModel(BaseModel):
...     file_path: str
...     file_extension: CompressedFileExtension
>>> obj = SomeModel(
...    file_path='/path/to/compressed_file.lz',
...    file_extension=CompressedFileExtension.LZ
...)
>>> obj.json()
'{"file_path": "/path/to/compressed_file.lz", "file_extension": ".lz"}'
>>> 
>>> SomeModel.parse_raw('{"file_path": "/path/to/compressed_file.lz", "file_extension": ".lz"}')
SomeModel(
    file_path='/path/to/compressed_file.lz', 
    file_extension=<CompressedFileExtension.LZ: ValueWithDescription(
        value='.lz', 
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    )>
)
>>> 
>>> import orjson
>>> data = orjson.loads('{"file_path": "/path/to/compressed_file.lz", "file_extension": ".lz"}')
>>> SomeModel(**data)
SomeModel(
    file_path='/path/to/compressed_file.lz', 
    file_extension=<CompressedFileExtension.LZ: ValueWithDescription(
        value='.lz', 
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    )>
)

Also works great with Pydantic v2 with minor differences from the previous example.

>>> obj.model_dump_json()
'{"file_path":"/path/to/compressed_file.lz","file_extension":".lz"}'
>>> 
>>> SomeModel.model_validate_json('{"file_path": "/path/to/compressed_file.lz", "file_extension": ".lz"}')
SomeModel(
    file_path='/path/to/compressed_file.lz', 
    file_extension=<CompressedFileExtension.LZ: ValueWithDescription(
        value='.lz', 
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    )>
)
>>> 
>>> import orjson
>>> data = orjson.loads('{"file_path": "/path/to/compressed_file.lz", "file_extension": ".lz"}')
>>> SomeModel(**data)
SomeModel(
    file_path='/path/to/compressed_file.lz', 
    file_extension=<CompressedFileExtension.LZ: ValueWithDescription(
        value='.lz', 
        description='Employs the Lempel–Ziv–Markov chain algorithm (LZMA)'
    )>
)

License

This project is licensed under the Apache-2.0 License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

extended_enum-1.1.0.tar.gz (10.5 kB view details)

Uploaded Source

Built Distribution

extended_enum-1.1.0-py3-none-any.whl (18.4 kB view details)

Uploaded Python 3

File details

Details for the file extended_enum-1.1.0.tar.gz.

File metadata

  • Download URL: extended_enum-1.1.0.tar.gz
  • Upload date:
  • Size: 10.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.9.19

File hashes

Hashes for extended_enum-1.1.0.tar.gz
Algorithm Hash digest
SHA256 d720b56447a975b0eefbd0bd5071d709ac2ab6589f407caf35088e950f6ebb81
MD5 82fbbbe5804c8aae65e6936cdc34751d
BLAKE2b-256 f7bf2c65b61af2e4bacbc4db84f1a97d87d9fc89caf971677c6b32d5138aba7c

See more details on using hashes here.

File details

Details for the file extended_enum-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for extended_enum-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 38e602fbd69fb0aef69c0e1363eff066ce0fa46e69f92c2fe3ae0d98072d08a4
MD5 80cfa26fd4339e26791eea19b03cfb76
BLAKE2b-256 abd697c84de4535a9ca28eb3e3879341d0b4140b2ea5fc9c833ef061888187f7

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page