Rant first, code will be after!
Right now, I'm attempting to create a command line interface module for a generic shop system I can use in any project going forward. I'm using MyPy since I frankly don't trust myself to keep types straight and all that, so due to the attempted genericness of this project that's lead to me using some stuff I've never used before. The goal is that I can have a store hold and sell whatever items I want as long as they have a .name.
Anyway, while writing the function to remove an item from the user's cart by checking an entered name (for example, if I have an item with name "sword" in my cart, I would type "remove sword" into the input), I realized I had no way of narrowing down the inputted types so that I could be sure that the ItemClass (the name of my TypeVar) has a .name. Since I had not narrowed this down, attempting to access .name leads to MyPy throwing a fit. I know type-checker driven development is a bad habit, but I really don't want to have to slap an ugly "Any" on there.
For testing purposes, the class I'm using for ItemClass is simply a hashable dataclass with a .name attribute, but ideally it could be any hashable class with a .name atrribute.
Here is the offending function. Note that this sits inside a Shop class, which has a self.cart attribute which stores ItemClass objects as keys and some associated metadata in a tuple as the value:
def remove_from_cart(self, item_name):
match: list[ItemClass] = [
item.name for item in self.cart.keys() if item.name == item_name
]
if match:
self.cart.pop(match[0])
MyPy throws a fit on the middle line of the list comprehension, because I have not ensured that the type used for ItemClass has a .name. Ideally, I would want to make it so that the type checker will throw an error if I even try to use a class which doesn't have the required attribute. If at all possible, I'd like to try my absolute best to avoid having to make this check at runtime, just because I'd prefer to be able to have the checker say "Hey, this type you're attempting to sell-it doesn't have a .name!" before I've even made it to the F5 button.
Thanks in advance, HobbitJack
EDIT 1: My Shop class:
class Shop(Generic[ItemClass, PlayerClass]):
def __init__(self, items: dict[ItemClass, tuple[Decimal, int]]):
self.inventory: dict[ItemClass, tuple[Decimal, int]] = items
self.cart: dict[ItemClass, tuple[Decimal, int]] = {}
def inventory_print(self):
for item, metadata in self.inventory.items():
print(f"{item}: {metadata}")
def add_to_cart(self, item: ItemClass, price: Decimal, amount: int):
self.cart[item] = (price, amount)
def remove_from_cart(self, item_name):
match: list[ItemClass] = [
item.name for item in self.cart if item.name == item_name
]
if match:
self.cart.pop(match[0])
def print_inventory(self):
for item, metadata in self.inventory.items():
print(f"{item}: {metadata[1]} * ${metadata[0]:.2f}")
def cart_print(self):
total_price = Decimal(0)
for item, metadata in self.inventory.items():
total_price += metadata[0]
print(f"{item}: {metadata[1]} * ${metadata[0]:.2f}")
print(f"Total price: {total_price:.2f}")
and my test Item class:
@dataclass(frozen=True)
class Item:
name: str
durability: float
with the MyPy error message:
"ItemClass" has no attribute "name"
EDIT 2: Here goes for an attempt at a simple version of what I've got above: In module 1, simple_test.py, we have a simple dataclass:
from dataclasses import dataclass
@dataclass
class MyDataclass:
my_hello_world: str
In module 2, we attempt to use a TypeVar to print the .my_hello_world of a MyDataclass object:
from typing import TypeVar
import simple_test
MyTypeVar = TypeVar("MyTypeVar")
def hello_print(string_container: MyTypeVar):
print(string_container.my_hello_world)
hello_print(simple_test.MyDataclass("Hello, World!"))
MyPy complains on the print line of hello_print() with the error message "MyTypeVar" has no attribute "my_hello_world".
My goal is that I would be able to narrow down the type being passed into hello_print into one which definitely has a .my_hello_world so that it stops complaining.