supermarket dataclasses - python

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 ==.

Related

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.

Pytest: assert a list contains n instances of a class / object

#dataclass
class Component:
name: str
class Assembly:
def __init__():
names = ["Able", "Baker", "Charlie"]
self.components = [Component(name) for name in names]
def test_assembly():
"""Assembly should be initialised with three Components"""
assembly = Assembly()
assert assembly.components == [Component] * 3 # doesn't work
The assert doesn't work because it's comparing a list of class instances (assembly.components) to a list of class types. Is there a good way to write this test?
You could iterate through the list and use isinstance():
def count_instance(lst, cls):
count = 0
for i in lst:
if isinstance(i, cls):
count += 1
return count
assert count_instance(assembly.components, Component) == 3
You can convert all of the elements in assembly.components into types with map() and then you can convert the map into a list and use the list.count function to see how many times the type appears in the list. Something like this:
assert list(map(type,assembly.components)).count(Component) == 3
if each item in assembly.components is already a type, you can just make sure its a list and then use the list.count function immedietly like this:
assert list(assembly.components).count(Component) == 3
Edit:
Also should you find yourself iterating over anything and want to compare the number of times a pattern appear, i believe its far better to use an int variable and increment it for each pattern you find rather than what you were doing which is creating entirely new lists and comparing them which in large quantities will slow down quite quick. (The increment method is how i bleieve the .count function works so where possible try to just use that.)
assert [type(comp) for comp in assembly.components] == [Component] * 3
This does the job - but I suspect there may be a better solution?
You could assert the class types on each element in the list individually, however that's a relatively expensive operation, assuming you already know that each element in components will be a Component type.
Instead, you could just check the length of the list, as shown below; this assumes that you don't try to add an integer value, for example, to components outside of the class constructor. Though you could also assert that each element in the list is a Component type rather easily, using the builtin map operator and then unpacking the iterator result into a list, which is also shown as well.
Note: attributes annotated as ClassVar are assumed to be class or static attributes only, and so will be ignored by the dataclass decorator when building a list of dataclass fields.
from dataclasses import dataclass
from typing import ClassVar, List
#dataclass
class Component:
name: str
#dataclass
class Assembly:
names: ClassVar[List[str]] = ["Able", "Baker", "Charlie"]
components: List[Component]
def __init__(self):
self.components = [Component(name) for name in self.names]
def main():
"""Assembly should be initialised with three Components"""
assembly = Assembly()
assert len(assembly.components) == 3 # works
# bonus: check each element is a Component object also
assert assembly.components == [*map(Component, assembly.names)]
if __name__ == '__main__':
main()

How can I access nested nullable values of a Pydantic object without duplication?

I receive a structure like this from an API:
from pydantic import BaseModel
class Country(BaseModel):
code: Optional[str] = None
class Address(BaseModel):
street: str
country: Optional[Country] = None
class Person(BaseModel):
firstName: str
lastName: str
address: Optional[Address] = None
If I want to get the country code, I need to do this:
def get_country_code(p: Person):
if p.address and p.adress.country:
return p.address.country.code
return None
As you can see, I need to duplicate p.address 3 times when I have a depth of 3. In my actual case the data is even more nested.
This is super annoying and makes the code harder to read. I wish there was something like this:
def get_country_code(p: Person):
return p.?address.?country.code
The ? in front of the attribute access essentially means "if the following attribute exists, take it. If not, return None"
Possible Solution #1: Convert to dict
If it was a dictionary, I could at least do this:
def get_country_code(p: Person):
return p.get("address", {}).get("country", {}).get("code", None)
Hence I'm currently thinking about just converting it back to a dictionary... which is kind of sad. Is there a better way to access the nested attributes?
Possible Solution #2: Dict default values + Modify BaseModel
I could change the Optional[Address] to Union[Address, Dict[str, Any]] and make the default an empty dictionary. However, a Pydantic BaseModel does not have .get(some_str). Hence I would need to modify it.
Question
I cannot change the fact that the data structure is so heavily nested or that there are some many nullable fields. I just have to accept that.
Is there a way to have the .get("attribute", "fallback") syntax without loosing the editor/mypy support?
I usually just use getattr. It's not exactly pretty, but it does the job
return getattr(getattr(p.address, 'country', None), 'code', None)
Building on #Matias Cicero's answer, if your data structure is as heavily nested as you say, maybe something like this becomes more readable. But I would even guess that some neat library alreardy contains a method to iteratively apply the same function like this.
attrs = ["address", "country", "code"]
def get_country_code(p: Person):
i = 0
while p := getattr(p, attrs[i], None) and i < len(attrs):
i += 1
return p
If you can use third party modules, I'd recommend using pydash:
import pydash as _
...
def get_country_code(p: Person):
return _.get(p, 'address.country.code')

How to get back name of the enum element?

I have an enum defined like this:
def enum(**enums):
return type('Enum', (), enums)
Status = enum(
STATUS_OK=0,
STATUS_ERR_NULL_POINTER=1,
STATUS_ERR_INVALID_PARAMETER=2)
I have a function that returns status as Status enum.
How can I get the name of the enum value, and not just value?
>>> cur_status = get_Status()
>>> print(cur_status)
1
I would like to get STATUS_ERR_NULL_POINTER, instead of 1
You'd have to loop through the class attributes to find the matching name:
name = next(name for name, value in vars(Status).items() if value == 1)
The generator expression loops over the attributes and their values (taken from the dictionary produced by the vars() function) then returns the first one that matches the value 1.
Enumerations are better modelled by the enum library, available in Python 3.4 or as a backport for earlier versions:
from enum import Enum
class Status(Enum):
STATUS_OK = 0
STATUS_ERR_NULL_POINTER = 1
STATUS_ERR_INVALID_PARAMETER = 2
giving you access to the name and value:
name = Status(1).name # gives 'STATUS_ERR_NULL_POINTER'
value = Status.STATUS_ERR_NULL_POINTER.value # gives 1
2021 update:
These answers are out of date. Using Python's standard Enum class,
cur_status.name
will return the name. (STATUS_ERR_NULL_POINTER)
To look up the enum knowing the name:
s = Status['STATUS_ERR_NULL_POINTER']
Not sure which python version it was introduced, but the hidden attribute _value2member_map_ gives you what you want.
class Status(Enum):
STATUS_OK=0
STATUS_ERR_NULL_POINTER=1
STATUS_ERR_INVALID_PARAMETER=2
str(Status._value2member_map_[1])
Out:
'Status.STATUS_ERR_NULL_POINTER'
You don't need to loop through the Enum class but just access _member_map_.
>>> Status._member_map_['STATUS_OK']
<Status.STATUS_OK: 0>
For some reason, most of the methods above did not work for me. All methods return the Enum type as an integer. I'm working with Python 3.7.
In my solution, I defined class function to handle this. It's not purely pythonic, but worked well enough for my case.
from enum import Enum
class Status(Enum):
STATUS_OK = 0
STATUS_ERR_NULL_POINTER = 1
STATUS_ERR_INVALID_PARAMETER = 2
#classmethod
def name(cls,val):
return { v:k for k,v in dict(vars(cls)).items() if isinstance(v,int)}.get(val,None)
# test it
stat = Status.STATUS_OK
print(Status.name(stat))
Prints: 'STATUS_OK'
It may seem obvious that we asked for the status after giving it the status, but in my case, this is set programmatically elsewhere

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