In the first example below are some classes representing nodes and node items in a node tree with a corresponding string path. I wanted to convert these classes to generics so I can use them for paths with different path separators. The following example is the classes converted to generic classes. I got most of it working (functionally it's good) but now in some sections there are typing errors showing. How can I fix these?
### without generic classes
import logging
logger = logging.getLogger(__name__)
class ItemRegistry[K, I](dict[K, I]):
"""
generic base class implementing a registry for accessing and storing items with a unique key\n
provides protection against overwriting existing items
"""
ITEM_TYPE : str = 'item'
item_count: int
def __init__(self):
logger.debug(f"creating {self}")
super().__init__()
self.item_count = 0
def __str__(self):
return f"{self.__class__.__name__}"
def add_item(self, key: K, item: I, overwrite: bool = False) -> None:
"""
adds an item to the registry\n
overwrites the existing item when overwrite is True\n
raises KeyError if an item with given key already exists and overwrite is False
"""
logger.debug(f"{self.ITEM_TYPE} added - key: {key}, item: {item}")
try:
self[key]
except KeyError:
self[key] = item
self.item_count += 1
return
else:
if overwrite:
self[key] = item
self.item_count += 1
logger.warning(f"{self.ITEM_TYPE} overwritten - key: {key}, item: {item}")
return
else:
msg = f"{self.ITEM_TYPE} already exists - key: {key}, item: {item}"
logger.error(msg)
raise KeyError(msg)
def remove_item(self, key: K):
"""removes item from the registry - raises KeyError if an item does not exist for the given key"""
try:
del self[key]
except KeyError:
msg = f"{self.ITEM_TYPE} does not exist with key: {key}"
logger.error(msg)
raise KeyError(msg)
else:
self.item_count -= 1
logger.debug(f"{self.ITEM_TYPE} removed - key: {key}")
class NodeRegistry(ItemRegistry[str, 'Node | NodeItem']):
ITEM_TYPE : str = 'node'
class NodePath(str):
"""class representing a node path"""
SEPARATOR = '.'
@property
def parts(self):
return self.get_parts(self)
def __add__(self, value: str) -> NodePath:
return NodePath(f"{self}{self.SEPARATOR}{value}")
@classmethod
def get_parts(cls, path: str) -> list[str]:
return path.split(cls.SEPARATOR)
class NodeItem:
"""class representing a node item (a node with no children)"""
parent: Node | NodeTree
path: NodePath
def __init__(self, name: str, parent: Node | NodeTree) -> None:
self.parent = parent
self.path = parent.path + name
logger.debug(f"creating {self}")
parent.add_node(self)
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.path})"
__repr__ = __str__
@property
def name(self):
return self.path.parts[-1]
def get_root_node(self) -> Node | NodeItem:
node = self
while not isinstance(node.parent, NodeTree):
node = node.parent
return node
def get_tree(self) -> NodeTree:
parent = self.parent
while not isinstance(parent, NodeTree):
parent = parent.parent
return parent
class Node(NodeItem, NodeRegistry):
"""class representing a node"""
parent: Node | NodeTree
path: NodePath
def __init__(self, name: str, parent: Node | NodeTree) -> None:
NodeItem.__init__(self, name, parent)
NodeRegistry.__init__(self)
def add_node(self, node: Node | NodeItem) -> None:
self.add_item(node.name, node)
class NodeTree(NodeRegistry):
"""class representing a node tree"""
path: NodePath
def __init__(self, path: NodePath) -> None:
self.path = path
super().__init__()
def __str__(self):
return f"{self.__class__.__name__}({self.path})"
__repr__ = __str__
def add_node(self, node: Node | NodeItem) -> None:
self.add_item(node.name, node)
def get_node(self, path: NodePath) -> Node | NodeItem:
node = self[path.parts[1]]
for part in path.parts[2:]:
if isinstance(node, Node):
node = node[part]
return node
### with generic classes
import logging
from typing import Self
logger = logging.getLogger(__name__)
class ItemRegistry[K, I](dict[K, I]):
"""
generic base class implementing a registry for accessing and storing items with a unique key\n
provides protection against overwriting existing items
"""
ITEM_TYPE : str = 'item'
item_count: int
def __init__(self):
logger.debug(f"creating {self}")
super().__init__()
self.item_count = 0
def __str__(self):
return f"{self.__class__.__name__}"
def add_item(self, key: K, item: I, overwrite: bool = False) -> None:
"""
adds an item to the registry\n
overwrites the existing item when overwrite is True\n
raises KeyError if an item with given key already exists and overwrite is False
"""
logger.debug(f"{self.ITEM_TYPE} added - key: {key}, item: {item}")
try:
self[key]
except KeyError:
self[key] = item
self.item_count += 1
return
else:
if overwrite:
self[key] = item
self.item_count += 1
logger.warning(f"{self.ITEM_TYPE} overwritten - key: {key}, item: {item}")
return
else:
msg = f"{self.ITEM_TYPE} already exists - key: {key}, item: {item}"
logger.error(msg)
raise KeyError(msg)
def remove_item(self, key: K):
"""removes item from the registry - raises KeyError if an item does not exist for the given key"""
try:
del self[key]
except KeyError:
msg = f"{self.ITEM_TYPE} does not exist with key: {key}"
logger.error(msg)
raise KeyError(msg)
else:
self.item_count -= 1
logger.debug(f"{self.ITEM_TYPE} removed - key: {key}")
class NodeRegistry[N: Node, I: NodeItem](ItemRegistry[str, N | I]):
ITEM_TYPE : str = 'node'
class NodePath(str):
"""class representing a node path"""
SEPARATOR = '.'
@property
def parts(self):
return self.get_parts(self)
def __add__(self, value: str) -> Self:
return self.__class__(f"{self}{self.SEPARATOR}{value}")
@classmethod
def get_parts(cls, path: str) -> list[str]:
return path.split(cls.SEPARATOR)
class NodeItem[P: NodePath, I: NodeItem, N: Node, T: NodeTree]:
"""class representing a node item (a node with no children)"""
parent: N | T
path: P
def __init__(self, name: str, parent: N | T) -> None:
self.parent = parent
self.path = parent.path + name
logger.debug(f"creating {self}")
parent.add_node(self)
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.path})"
__repr__ = __str__
@property
def name(self):
return self.path.parts[-1]
def get_root_node(self) -> N | I:
node = self
while not isinstance(node.parent, NodeTree):
node = node.parent
return node
def get_tree(self) -> T:
parent = self.parent
while not isinstance(parent, NodeTree):
parent = parent.parent
return parent
class Node[P: NodePath, I: NodeItem, N: Node, T: NodeTree](NodeItem[P, I, N, T], NodeRegistry[N, I]):
"""class representing a node"""
parent: N | T
path: P
def __init__(self, name: str, parent: N | T) -> None:
NodeItem.__init__(self, name, parent)
NodeRegistry.__init__(self)
def add_node(self, node: N | I) -> None:
self.add_item(node.name, node)
class NodeTree[P: NodePath, I: NodeItem, N: Node](NodeRegistry[N, I]):
"""class representing a node tree - a hierarchial structure of nodes and node items"""
path: P
def __init__(self, path: P) -> None:
self.path = path
super().__init__()
def __str__(self):
return f"{self.__class__.__name__}({self.path})"
__repr__ = __str__
def add_node(self, node: N | I) -> None:
self.add_item(node.name, node)
def get_node(self, path: P) -> N | I:
node = self[path.parts[1]]
for part in path.parts[2:]:
if isinstance(node, Node):
node = node[part]
return node
class SlashPath(NodePath):
SEPARATOR: str = '/'
class SlashNodeItem(NodeItem[SlashPath, 'SlashNodeItem', 'SlashNode', 'SlashTree']): ...
class SlashNode(Node[SlashPath, SlashNodeItem, 'SlashNode', 'SlashTree']): ...
class SlashTree(NodeTree[SlashPath, SlashNodeItem, SlashNode]): ...
path1 = SlashPath('root')
tree = SlashTree(path1)
a = SlashNode('a', tree)
b = SlashNodeItem('b', a)
a_root = a.get_root_node()
b_tree = b.get_tree()
The errors:
self.path = parent.path + name # line 85
Cannot assign to attribute "path" for class "NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem]*"
Type "P@Node | Unknown" is not assignable to type "P@NodeItem"
return node # line 102
Type "Self@NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem] | Node[P@Node, I@Node, N@Node, T@Node]*" is not assignable to return type "N@NodeItem | I@NodeItem"
Type "Self@NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem] | Node[P@Node, I@Node, N@Node, T@Node]*" is not assignable to type "N@NodeItem | I@NodeItem"
Type "Self@NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem] | Node[P@Node, I@Node, N@Node, T@Node]*" is not assignable to type "I@NodeItem"
Type "Self@NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem]" is not assignable to type "N@NodeItem | I@NodeItem"
Type "Self@NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem]" is not assignable to type "I@NodeItem"
Type "Self@NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem]" is not assignable to type "N@NodeItem"
return parent # line 108
Type "NodeTree[Unknown, Unknown, Unknown]*" is not assignable to return type "T@NodeItem"
Type "NodeTree[Unknown, Unknown, Unknown]*" is not assignable to type "T@NodeItem"
return node # line 145
Type "N@NodeTree | I@NodeTree | N@Node | I@Node | Unknown | NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem]* | NodeItem[Unknown, Unknown, Unknown, Unknown]*" is not assignable to return type "N@NodeTree | I@NodeTree"
Type "N@NodeTree | I@NodeTree | N@Node | I@Node | Unknown | NodeItem[P@NodeItem, I@NodeItem, N@NodeItem, T@NodeItem]* | NodeItem[Unknown, Unknown, Unknown, Unknown]*" is not assignable to type "N@NodeTree | I@NodeTree"
Type "I@Node" is not assignable to type "N@NodeTree | I@NodeTree"
Type "I@Node" is not assignable to type "I@NodeTree"
Type "I@Node" is not assignable to type "N@NodeTree"