Cast binary to enum in python for redis conversion - python

I am storing an enum in redis. When I load it, the value is in binary. Howe can I cast it to be a python enum?
Example code:
class Position(Enum):
LEFT = 10
RIGHT = 11
current_position = Position.LEFT
r.set('current_position', Position.LEFT)
loaded_current_position = r.get('current_position_side')
print(current_position) # Position.LEFT
print(loaded_current_position) # b'Position.LEFT'
In this example, I'd like to get loaded_current_position to equal Position.LEFT not b'Position.LEFT'

What's being stored is the name of the Enum member. There may be a redis specific answer, but in general you can mix your Enum with another data type, such as int, and then cast it back to an Enum upon retrieval; something like:
class Position(int, Enum):
LEFT = 10
RIGHT = 11
current_position = Position.LEFT
r.set('current_position', Position.LEFT)
loaded_current_position = Position(r.get('current_position_side'))
print(current_position) # Position.LEFT
print(loaded_current_position) # Position.LEFT
Note: for the above to work, redis must save and return an int

Related

Python type hinting annotation for Dataclass attribute

I have a dataclass and I use it as a constant store.
#dataclass
class MyClass:
CONSTANT_1 = "first"
CONSTANT_2 = "second"
I have a function:
def my_func(value: ?):
print(value)
I want to add annotation to my function to specify that possible value is one of attribute of MyClass
How to do it (I am using python 3.10) ?
Hopefully I not misunderstand the ask, please let me know if so. But I think in this case is best to use Enum type in python.
Here is a simple example:
from enum import Enum
class MyEnum(Enum):
CONSTANT_1 = "first"
CONSTANT_2 = "second"
Then to answer the second part, for annotation the ? becomes a MyEnum. This means any enum member of this type, but not the type (class) itself.
def my_func(value: MyEnum):
print(value, value.name, value.value)
Putting it all together, it becomes like:
from enum import Enum
class MyEnum(Enum):
CONSTANT_1 = "first"
CONSTANT_2 = "second"
def my_func(value: MyEnum):
# technically you can remove this check
if not isinstance(value, MyEnum):
return
print(value, value.name, value.value)
# note below: only type checker or ide complain, but code still runs fine
my_func('hello') # not OK!
my_func('second') # not OK!
my_func(MyEnum) # not OK!
my_func(MyEnum.CONSTANT_1) # OK
I think you're asking an XY problem. From your response in the comments, it seems like what you want is rather:
Have a class-like interface to hold a bunch of constant values.
Constraint the argument to only take the above values.
As as mentioned in rv.kvetch's answer, the conventional way of doing this is to use enums. I'm not sure what you mean by "wanting to skip .value", the value field of an enum simply gives you what's associated with that enum, and I would say that it's not important at all. Here's an example:
class StrEnum(enum.Enum):
FIRST = "first"
SECOND = "second"
class StrEnum2(enum.Enum):
FIRST = "first"
SECOND = "second"
print(StrEnum.FIRST.value) # first
print(StrEnum2.FIRST.value) # first
print(StrEnum.FIRST.value == StrEnum2.FIRST.value) # True
print(StrEnum.FIRST == StrEnum2.FIRST) # False
class IntEnum(enum.Enum):
FIRST = enum.auto()
SECOND = enum.auto()
print(IntEnum.FIRST.value) # 1
print(IntEnum.SECOND.value) # 2
What I want to show with this example are two things:
You don't really need .value at all if you're just comparing enums.
You don't even need to manually assign values to the enums; you can use enum.auto() to auto-assign a unique value to it.
Because at the end of the day, enums themselves already represent a choice among valid choices, so it doesn't matter what values it has.
That said, if what you want is just to put a type constraint on what values an argument can type, and not have to use enums, then you can use the Literal type. See this answer for details. For your example, you could do something like:
from typing import Literal, Final
def my_func(value: Literal["first", "second"]):
print(value)
my_func("first") # ok
my_func("not first") # bad
x = "first"
y: Final = "first"
my_func(x) # bad, because `x` is not final
my_func(y) # ok
But note that type annotations don't really prevent you from calling a function with an invalid value, it's just a hint for IDEs and type checkers.

Arbitrary type conversion in python

An arbitrary typecasting function (shown below as cast) seems like a fairly straightforward function:
print(type(variable))
variable = cast(variable,type) # where type is any type included in __builtins__
print(type(variable))
And the result:
>>> <original_type>
>>> <type>
Does such a function exist in python? I can't seem to find any reference to it if it does. If this function does not exist, please explain the rationale for why it does not.
As one example usage, I have a config with arbitrarily many values, and a schema with the desired type of each. I want to check that specified value for each config variable can be cast as corresponding type specified in the schema. Treating each as a dict below for convenience:
for variable in config.keys():
val = config[variable]
type_name = schema[variable]
try:
config[variable] = cast(val,type_name)
except TypeError:
print("Schema checking failed for variable {}".format(variable))
Ok, I think the comments have covered the matter in enough detail so I'll just try to summarize my best understanding of them here. Most of this is by way of #juanpa.arrivillaga.
A standard python casting operation like int(x) (or more precisely, a type conversion operation), is actually a call to the __call__() function of an object. Types like int, float, str, etc are all object classes and are all instances of the metaclass type. A call to one of these instance of type e.g. int.__call__() calls the int object constructor which creates a new instance of that type and initializes it with the inputs to __call__().
In short, there is nothing special or different about the common python "type conversions" (e.g. int(x), str(40)) other than that the int and str objects are included in __builtins__.
And to answer the original question, if type_name is an instance of the type class then the type_name.__call__() function simply declares and initializes a new instance of that type. Thus, one can simply do:
# convert x to type type_name
x = type_name(x)
however this may cause an exception if x is not a valid input to the type_name constructor.
To cast a value in another type you can use the type itself, you can pass the type as an argument and call it into a function and you can get it from the builtins module if you sure that the type is a builtin:
value = "1"
value = int(value) # set value to 1
value = 1
value = str(value) # set value to "1"
def cast(value, type_):
return type_(value)
import buitlins
def builtin_cast(value, type_):
type_ = getattr(buitlins, type_, None)
if isinstance(type_, type):
return type_(value)
raise ValueError(f"{type_!r} is not a builtins type.")
value = cast("1", int) # set value to 1
value = cast(1, str) # set value to "1"
value = builtin_cast("1", "int") # set value to 1
value = builtin_cast(1, "str") # set value to "1"

Python equivalent of `final field` in java in dataclass

Is it possible to have something like
class MyAbstract {
final int myFieldSomebodyHasToDefine;
}
class MyAbstractImplementation extends MyAbstract {
final int myFieldSomebodyHasToDefine = 5;
}
using dataclasses in python?
If you are working with a python interpreter before version 3.8, there is no straightforward way. However, since python 3.8, the final decorator has been added to the language. After importing it from the typing module in python, you can use it for methods and classes.
You may also use FINAL type for values.
Here is an example
from typing import final, Final
#final
class Base:
#final
def h(self)->None:
print("old")
class Child(Base):
# Bad overriding
def h(self) -> None:
print("new")
if __name__ == "__main__":
b = Base()
b.h()
c = Child()
c.h()
RATE: Final = 3000
# Bad value assignment
RATE = 7
print(RATE)
Important note: Python does not force the developer with final and FINAL. You can yet change the values upon your wish. The decorators of mostly informative for developers.
For more information, you may visit: https://peps.python.org/pep-0591/
Update: This is also an instance for dataclass
#dataclass
class Item:
"""Class for keeping track of an item in inventory."""
price: float
quantity_on_hand: int = 0
name:Final[str] = "ItemX"
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
As you can see, name is a final field. However, you must put the final values with a default value below all of the fields without an initial value.

supermarket dataclasses

from dataclasses import dataclass
from typing import Union
#dataclass
class OtherStock:
name:str = "gold"
units:int = 7
price_per_unit : float = 150000
#dataclass
class FoodStock:
name:str = "bread"
units:int = 2
price_per_unit: float = 700
expiration_date: str = "2021-02-16"
stock = Union[OtherStock,FoodStock]
def is_expired(inventory:stock,date:str)->bool:
if type(inventory) == OtherStock:
return False
if date == inventory.expiration_date:
return True
else:
return False
print(is_expired("bread","2021-02-16"))
print(is_expired("gold","2021-02-16"))
The code should compare both attributes in date and expiration_date and should return True if the product is expired. OtherStock can't expire so it returns false every time. If I try to run it I get an AttributeError: 'str' object has no attribute 'expiration_date'.
Your code passes strings to is_expired, not objects. "bread" is a string.
Maybe you want print(is_expired(FoodStock(), "2021-02-16"))?
Like you said, the problem is with inventory.expiration_date, and like the error says, inventory is a str. And the first arguments you pass ("bread" and "gold") are indeed strings.
You'll need to create your stock objects first. Python won't do it for you. However, according to documentation, Python automatically creates initialisers for us. That's the FoodStock(...) and OtherStock(...) calls you see below.
today = "2021-02-16"
print(is_expired(FoodStock(name="bread", expiration_date="2021-02-12"), today))
print(is_expired(OtherStock(name="gold"), today))
Here, we used keyword arguments to specify individual fields.
You may also want to consider checking date >= inventory.expiration_date instead of ==.

How to convert int to Enum in python?

Using the new Enum feature (via backport enum34) with python 2.7.6.
Given the following definition, how can I convert an int to the corresponding Enum value?
from enum import Enum
class Fruit(Enum):
Apple = 4
Orange = 5
Pear = 6
I know I can hand craft a series of if-statements to do the conversion but is there an easy pythonic way to convert? Basically, I'd like a function ConvertIntToFruit(int) that returns an enum value.
My use case is I have a csv file of records where I'm reading each record into an object. One of the file fields is an integer field that represents an enumeration. As I'm populating the object I'd like to convert that integer field from the file into the corresponding Enum value in the object.
You 'call' the Enum class:
Fruit(5)
to turn 5 into Fruit.Orange:
>>> from enum import Enum
>>> class Fruit(Enum):
... Apple = 4
... Orange = 5
... Pear = 6
...
>>> Fruit(5)
<Fruit.Orange: 5>
From the Programmatic access to enumeration members and their attributes section of the documentation:
Sometimes it’s useful to access members in enumerations
programmatically (i.e. situations where Color.red won’t do because the
exact color is not known at program-writing time). Enum allows such
access:
>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>
In a related note: to map a string value containing the name of an enum member, use subscription:
>>> s = 'Apple'
>>> Fruit[s]
<Fruit.Apple: 4>
I think it is in simple words is to convert the int value into Enum by calling EnumType(int_value), after that access the name of the Enum object:
my_fruit_from_int = Fruit(5) #convert to int
fruit_name = my_fruit_from_int.name #get the name
print(fruit_name) #Orange will be printed here
Or as a function:
def convert_int_to_fruit(int_value):
try:
my_fruit_from_int = Fruit(int_value)
return my_fruit_from_int.name
except:
return None
class Status(IntEnum):
UPLOADED = 1
DOWNLOADED = 5
SEGMENTED = 10
DIRECTED = 15
READYTODEEP = 20
to get the enum;
statusId = 5
Status(statusId)
to get the enum's string value;
statusId = 5
print(Status(statusId).name)
I wanted something similar so that I could access either part of the value pair from a single reference. The vanilla version:
#!/usr/bin/env python3
from enum import IntEnum
class EnumDemo(IntEnum):
ENUM_ZERO = 0
ENUM_ONE = 1
ENUM_TWO = 2
ENUM_THREE = 3
ENUM_INVALID = 4
#endclass.
print('Passes')
print('1) %d'%(EnumDemo['ENUM_TWO']))
print('2) %s'%(EnumDemo['ENUM_TWO']))
print('3) %s'%(EnumDemo.ENUM_TWO.name))
print('4) %d'%(EnumDemo.ENUM_TWO))
print()
print('Fails')
print('1) %d'%(EnumDemo.ENUM_TWOa))
The failure throws an exception as would be expected.
A more robust version:
#!/usr/bin/env python3
class EnumDemo():
enumeration = (
'ENUM_ZERO', # 0.
'ENUM_ONE', # 1.
'ENUM_TWO', # 2.
'ENUM_THREE', # 3.
'ENUM_INVALID' # 4.
)
def name(self, val):
try:
name = self.enumeration[val]
except IndexError:
# Always return last tuple.
name = self.enumeration[len(self.enumeration) - 1]
return name
def number(self, val):
try:
index = self.enumeration.index(val)
except (TypeError, ValueError):
# Always return last tuple.
index = (len(self.enumeration) - 1)
return index
#endclass.
print('Passes')
print('1) %d'%(EnumDemo().number('ENUM_TWO')))
print('2) %s'%(EnumDemo().number('ENUM_TWO')))
print('3) %s'%(EnumDemo().name(1)))
print('4) %s'%(EnumDemo().enumeration[1]))
print()
print('Fails')
print('1) %d'%(EnumDemo().number('ENUM_THREEa')))
print('2) %s'%(EnumDemo().number('ENUM_THREEa')))
print('3) %s'%(EnumDemo().name(11)))
print('4) %s'%(EnumDemo().enumeration[-1]))
When not used correctly this avoids creating an exception and, instead, passes back a fault indication. A more Pythonic way to do this would be to pass back "None" but my particular application uses the text directly.

Categories

Resources