|
| 1 | +import inspect |
| 2 | +import re |
| 3 | +import typing |
| 4 | +from contextlib import suppress |
1 | 5 | from unittest import TestCase
|
2 | 6 |
|
3 | 7 | import sqlalchemy as sa
|
4 | 8 |
|
5 | 9 | from examples.sales import tables
|
6 | 10 | from star_alchemy import _star_alchemy
|
7 |
| -from star_alchemy._star_alchemy import Join, StarSchema |
| 11 | +from star_alchemy._star_alchemy import Join, StarSchema, StarSchemaSelect |
8 | 12 | from tests import tables
|
9 | 13 | from tests.util import AssertQueryEqualMixin, DocTestMixin, query_test
|
10 | 14 |
|
@@ -196,5 +200,71 @@ def test_detach(self):
|
196 | 200 | return product.select([product.tables['category'].c.id])
|
197 | 201 |
|
198 | 202 |
|
199 |
| -class DocTestCase(TestCase, DocTestMixin(_star_alchemy)): |
200 |
| - pass |
| 203 | +class DocStringTestCase(TestCase, DocTestMixin(_star_alchemy)): |
| 204 | + """ |
| 205 | + Check the docstrings of star_alchemy module, couldn't find a library |
| 206 | + that checks docstrings in rst format so fudging my own. The main |
| 207 | + things I care about... |
| 208 | +
|
| 209 | + * Parameters and return types are correctly documented |
| 210 | + * Examples are correct. |
| 211 | +
|
| 212 | + Most docstring checking focus on the format, which is less important |
| 213 | + for me. |
| 214 | + """ |
| 215 | + |
| 216 | + @property |
| 217 | + def sub_tests(self): |
| 218 | + """ |
| 219 | + Iterate through all the functions which should have their |
| 220 | + docstring checked for completeness. |
| 221 | + """ |
| 222 | + def filter_member(o): |
| 223 | + return ( |
| 224 | + (inspect.isfunction(o) or isinstance(o, property)) |
| 225 | + and (o.__doc__ is None or not o.__doc__.startswith('Method generated by attrs')) |
| 226 | + ) |
| 227 | + |
| 228 | + # TODO: StarSchemaSelect, compile_star_schema_select |
| 229 | + for cls in StarSchema, Join: |
| 230 | + for name, member in inspect.getmembers(cls, predicate=filter_member): |
| 231 | + if name in ("__delattr__", "__setattr__"): |
| 232 | + continue |
| 233 | + |
| 234 | + if isinstance(member, property): |
| 235 | + file = inspect.getfile(member.fget) |
| 236 | + line = inspect.getsourcelines(member.fget)[1] |
| 237 | + else: |
| 238 | + file = inspect.getfile(member) |
| 239 | + line = inspect.getsourcelines(member)[1] |
| 240 | + yield f'{cls.__name__}.{name}', member, f'File "{file}", line {line}' |
| 241 | + |
| 242 | + def test_docstring_should_be_present(self): |
| 243 | + for name, member, location in self.sub_tests: |
| 244 | + with self.subTest(name): |
| 245 | + if member.__doc__ is None: |
| 246 | + self.fail(f'Missing docstring ({location})') |
| 247 | + |
| 248 | + def test_docstring_return_should_be_documented(self): |
| 249 | + for name, member, location in self.sub_tests: |
| 250 | + with self.subTest(name): |
| 251 | + if member.__doc__ is None: |
| 252 | + self.skipTest(f'Missing docstring ({location})') |
| 253 | + if ":return:" not in member.__doc__: |
| 254 | + self.fail(f'Missing :return: ({location})') |
| 255 | + if re.search(r':return:\s*\n', member.__doc__): |
| 256 | + self.fail(f'Empty :return: ({location})') |
| 257 | + |
| 258 | + def test_all_parameters_should_be_documented(self): |
| 259 | + for name, member, location in self.sub_tests: |
| 260 | + with self.subTest(name): |
| 261 | + if not isinstance(member, property): |
| 262 | + if member.__doc__ is None: |
| 263 | + self.skipTest(f'Missing docstring ({location})') |
| 264 | + for parameter in inspect.signature(member).parameters.values(): |
| 265 | + param = f':param {parameter.name}:' |
| 266 | + if parameter.name != 'self': |
| 267 | + if param not in member.__doc__: |
| 268 | + self.fail(f'Missing {param} ({location})') |
| 269 | + if re.search(rf'{param}\s*\n', member.__doc__): |
| 270 | + self.fail(f'Empty {param} ({location})') |
0 commit comments