-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy path_utils.py
More file actions
88 lines (66 loc) · 3.29 KB
/
_utils.py
File metadata and controls
88 lines (66 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
from __future__ import annotations
import re
from pathlib import Path
from typing import TYPE_CHECKING
from griffe import Module, load
if TYPE_CHECKING:
from collections.abc import Generator
from griffe import Class, Function
SKIPPED_METHODS = {
'with_custom_http_client',
}
"""Methods where the async and sync docstrings are intentionally different."""
SRC_PATH = Path(__file__).resolve().parent.parent / 'src'
"""Path to the source code of the apify_client package."""
_SUBSTITUTIONS = [
(re.compile(r'Client'), 'ClientAsync'),
(re.compile(r'\bsynchronously\b'), 'asynchronously'),
(re.compile(r'\bSynchronously\b'), 'Asynchronously'),
(re.compile(r'\bsynchronous\b'), 'asynchronous'),
(re.compile(r'\bSynchronous\b'), 'Asynchronous'),
(re.compile(r'Retry a function'), 'Retry an async function'),
(re.compile(r'Function to retry'), 'Async function to retry'),
(re.compile(r'returned page also supports iteration: `for'), 'returned page also supports iteration: `async for'),
]
"""Patterns for converting sync docstrings to async docstrings."""
def load_package() -> Module:
"""Load the apify_client package using griffe."""
package = load('apify_client', search_paths=[str(SRC_PATH)])
if not isinstance(package, Module):
raise TypeError('Expected griffe to load a Module')
return package
def walk_modules(module: Module) -> Generator[Module]:
"""Recursively yield all modules in the package."""
yield module
for submodule in module.modules.values():
yield from walk_modules(submodule)
def iter_docstring_mismatches(package: Module) -> Generator[tuple[Class, Function, Class, Function, str, bool]]:
"""Yield docstring mismatches between sync and async client methods.
Yields (async_class, async_method, sync_class, sync_method, expected_docstring, has_existing).
"""
for module in walk_modules(package):
for async_class in module.classes.values():
if not async_class.name.endswith('ClientAsync'):
continue
sync_class = module.classes.get(async_class.name.replace('ClientAsync', 'Client'))
if not sync_class:
continue
for async_method in async_class.functions.values():
if any(str(d.value) == 'ignore_docs' for d in async_method.decorators):
continue
if async_method.name in SKIPPED_METHODS:
continue
sync_method = sync_class.functions.get(async_method.name)
if not sync_method or not sync_method.docstring:
continue
expected_docstring = _sync_to_async_docstring(sync_method.docstring.value)
if not async_method.docstring:
yield async_class, async_method, sync_class, sync_method, expected_docstring, False
elif async_method.docstring.value != expected_docstring:
yield async_class, async_method, sync_class, sync_method, expected_docstring, True
def _sync_to_async_docstring(docstring: str) -> str:
"""Convert a docstring from a sync component version into a docstring for its async analogue."""
result = docstring
for pattern, replacement in _SUBSTITUTIONS:
result = pattern.sub(replacement, result)
return result