Skip to content

Commit 0c793da

Browse files
committed
Change implementation of NamedList to avoid issues with mypy, and updated docs.
1 parent e7b1634 commit 0c793da

File tree

3 files changed

+140
-18
lines changed

3 files changed

+140
-18
lines changed

doc-source/api/bases.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,51 @@
33
*********************************
44

55
.. automodule:: domdf_python_tools.bases
6+
:no-members:
7+
8+
Dictable
9+
---------
10+
11+
.. autoclass:: domdf_python_tools.bases.Dictable
12+
:inherited-members:
13+
:special-members:
14+
15+
16+
NamedList
17+
----------
18+
19+
Both :class:`~.NamedList` and :func:`~.namedlist` can be used to create a named list.
20+
21+
:func:`~.namedlist` can be used as follows:
22+
23+
.. code-block:: python
24+
25+
>>> ShoppingList = namedlist("ShoppingList")
26+
>>> shopping_list = ShoppingList(["egg and bacon", "egg sausage and bacon", "egg and spam", "egg bacon and spam"])
27+
>>>
28+
29+
If you wish to create a subclass with additional features it is recommended to subclass
30+
from :class:`NamedList` rather than from :func:`~.namedlist`. For example, do this:
31+
32+
33+
.. code-block:: python
34+
35+
>>> class ShoppingList(NamedList):
36+
... pass
37+
>>>
38+
39+
and not this:
40+
41+
.. code-block:: python
42+
43+
>>> class ShoppingList(namedlist())
44+
... pass
45+
>>>
46+
47+
This avoids any potential issues with `mypy <http://mypy-lang.org/>`_
48+
49+
.. autoclass:: domdf_python_tools.bases.NamedList
650
:inherited-members:
751
:special-members:
52+
53+
.. autofunction:: domdf_python_tools.bases.namedlist

domdf_python_tools/bases.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from abc import abstractmethod
2828
from collections import UserList
2929
from pprint import pformat
30-
from typing import Any, Callable, Dict, Iterable, Tuple
30+
from typing import Any, Dict, Iterable, Tuple, Type
3131

3232
# 3rd party
3333
import pydash # type: ignore
@@ -72,26 +72,30 @@ def __eq__(self, other) -> bool:
7272
return NotImplemented
7373

7474

75-
def namedlist(name: str = "NamedList") -> Callable:
75+
class NamedList(UserList):
7676
"""
77-
A factory function to return a custom list subclass with a name.
78-
79-
:param name: The name of the list.
77+
A list with a name.
8078
81-
:return:
79+
The name of the list is taken from the name of the subclass.
8280
"""
8381

84-
class NamedList(UserList):
85-
"""
86-
A list with a name.
87-
"""
82+
def __repr__(self) -> str:
83+
return f"{super().__repr__()}"
84+
85+
def __str__(self) -> str:
86+
return f"{self.__class__.__name__}{pformat(list(self))}"
87+
8888

89-
def __repr__(self) -> str:
90-
return f"{super().__repr__()}"
89+
def namedlist(name: str = "NamedList") -> Type[NamedList]:
90+
"""
91+
A factory function to return a custom list subclass with a name.
92+
93+
:param name: The name of the list.
94+
"""
9195

92-
def __str__(self) -> str:
93-
return f"{self.__class__.__name__}{pformat(list(self))}"
96+
class cls(NamedList):
97+
pass
9498

95-
NamedList.__name__ = name
99+
cls.__name__ = name
96100

97-
return NamedList
101+
return cls

tests/test_bases.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import pytest
1616

1717
# this package
18-
from domdf_python_tools.bases import Dictable, namedlist
18+
from domdf_python_tools.bases import Dictable, NamedList, namedlist
1919
from domdf_python_tools.utils import printr, printt
2020

2121

@@ -111,8 +111,80 @@ def test_namedlist(capsys):
111111

112112
captured = capsys.readouterr()
113113
stdout = captured.out.split("\n")
114-
assert stdout[0] == "<class 'domdf_python_tools.bases.namedlist.<locals>.NamedList'>"
114+
assert stdout[0] == "<class 'domdf_python_tools.bases.namedlist.<locals>.cls'>"
115115
assert stdout[1] == "['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
116116
assert stdout[2] == (
117117
"ShoppingList['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
118118
)
119+
120+
assert str(type(shopping_list)) == "<class 'domdf_python_tools.bases.namedlist.<locals>.cls'>"
121+
assert repr(
122+
shopping_list
123+
) == "['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
124+
assert str(shopping_list) == (
125+
"ShoppingList['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
126+
)
127+
128+
129+
def test_namedlist_subclass_function(capsys):
130+
131+
class ShoppingList(namedlist()): # type: ignore
132+
pass
133+
134+
shopping_list = ShoppingList(["egg and bacon", "egg sausage and bacon", "egg and spam", "egg bacon and spam"])
135+
assert isinstance(shopping_list, UserList)
136+
assert shopping_list[0] == "egg and bacon"
137+
138+
printt(shopping_list)
139+
printr(shopping_list)
140+
print(shopping_list)
141+
142+
captured = capsys.readouterr()
143+
stdout = captured.out.split("\n")
144+
assert stdout[0] == "<class 'tests.test_bases.test_namedlist_subclass_function.<locals>.ShoppingList'>"
145+
assert stdout[1] == "['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
146+
assert stdout[2] == (
147+
"ShoppingList['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
148+
)
149+
150+
assert str(
151+
type(shopping_list)
152+
) == "<class 'tests.test_bases.test_namedlist_subclass_function.<locals>.ShoppingList'>"
153+
assert repr(
154+
shopping_list
155+
) == "['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
156+
assert str(shopping_list) == (
157+
"ShoppingList['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
158+
)
159+
160+
161+
def test_namedlist_subclass_class(capsys):
162+
163+
class ShoppingList(NamedList):
164+
pass
165+
166+
shopping_list = ShoppingList(["egg and bacon", "egg sausage and bacon", "egg and spam", "egg bacon and spam"])
167+
assert isinstance(shopping_list, UserList)
168+
assert shopping_list[0] == "egg and bacon"
169+
170+
printt(shopping_list)
171+
printr(shopping_list)
172+
print(shopping_list)
173+
174+
captured = capsys.readouterr()
175+
stdout = captured.out.split("\n")
176+
assert stdout[0] == "<class 'tests.test_bases.test_namedlist_subclass_class.<locals>.ShoppingList'>"
177+
assert stdout[1] == "['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
178+
assert stdout[2] == (
179+
"ShoppingList['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
180+
)
181+
182+
assert str(
183+
type(shopping_list)
184+
) == "<class 'tests.test_bases.test_namedlist_subclass_class.<locals>.ShoppingList'>"
185+
assert repr(
186+
shopping_list
187+
) == "['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
188+
assert str(shopping_list) == (
189+
"ShoppingList['egg and bacon', 'egg sausage and bacon', 'egg and spam', 'egg bacon and spam']"
190+
)

0 commit comments

Comments
 (0)