2023-09-02 22:09:40 +03:00
|
|
|
"""
|
|
|
|
Scan the `client/` directory, take all function definitions with `self: Client`
|
|
|
|
as the first parameter, and generate the corresponding `Client` methods to call
|
|
|
|
them, with matching signatures.
|
|
|
|
|
|
|
|
The documentation previously existing in the `Client` definitions is preserved.
|
|
|
|
|
|
|
|
Imports of new definitions and formatting must be added with other tools.
|
|
|
|
|
|
|
|
Properties and private methods can use a different parameter name than `self`
|
|
|
|
to avoid being included.
|
|
|
|
"""
|
2024-03-16 21:05:58 +03:00
|
|
|
|
2023-09-02 22:09:40 +03:00
|
|
|
import ast
|
2023-09-30 18:13:24 +03:00
|
|
|
import subprocess
|
2023-09-02 22:09:40 +03:00
|
|
|
import sys
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
class FunctionMethodsVisitor(ast.NodeVisitor):
|
|
|
|
def __init__(self) -> None:
|
2024-03-17 15:06:03 +03:00
|
|
|
self.methods: list[ast.FunctionDef | ast.AsyncFunctionDef] = []
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
|
|
self._try_add_def(node)
|
|
|
|
|
2023-09-03 12:03:07 +03:00
|
|
|
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
2023-09-02 22:09:40 +03:00
|
|
|
self._try_add_def(node)
|
|
|
|
|
2024-03-17 15:06:03 +03:00
|
|
|
def _try_add_def(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
2024-03-18 20:55:23 +03:00
|
|
|
match node.args.posonlyargs + node.args.args:
|
2023-09-02 22:09:40 +03:00
|
|
|
case [ast.arg(arg="self", annotation=ast.Name(id="Client")), *_]:
|
|
|
|
self.methods.append(node)
|
2024-03-16 21:05:58 +03:00
|
|
|
case _:
|
|
|
|
pass
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
class MethodVisitor(ast.NodeVisitor):
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self._in_client = False
|
2024-03-17 15:06:03 +03:00
|
|
|
self.method_docs: dict[str, str] = {}
|
2023-09-02 22:09:40 +03:00
|
|
|
|
2023-09-03 12:03:07 +03:00
|
|
|
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
2023-09-02 22:09:40 +03:00
|
|
|
if node.name == "Client":
|
|
|
|
assert not self._in_client
|
|
|
|
self._in_client = True
|
|
|
|
for subnode in node.body:
|
|
|
|
self.visit(subnode)
|
|
|
|
self._in_client = False
|
|
|
|
|
|
|
|
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
|
|
self._try_add_doc(node)
|
|
|
|
|
2023-09-03 12:03:07 +03:00
|
|
|
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
2023-09-02 22:09:40 +03:00
|
|
|
self._try_add_doc(node)
|
|
|
|
|
2024-03-17 15:06:03 +03:00
|
|
|
def _try_add_doc(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
2023-09-02 22:09:40 +03:00
|
|
|
if not self._in_client:
|
|
|
|
return
|
|
|
|
|
|
|
|
match node.body:
|
|
|
|
case [ast.Expr(value=ast.Constant(value=str(doc))), *_]:
|
|
|
|
self.method_docs[node.name] = doc
|
2024-03-16 21:05:58 +03:00
|
|
|
case _:
|
|
|
|
pass
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
2023-09-03 12:03:07 +03:00
|
|
|
client_root = Path.cwd() / "client/src/telethon/_impl/client/client"
|
2023-09-30 18:13:24 +03:00
|
|
|
client_py = client_root / "client.py"
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
fm_visitor = FunctionMethodsVisitor()
|
|
|
|
m_visitor = MethodVisitor()
|
|
|
|
|
|
|
|
for file in client_root.glob("*.py"):
|
|
|
|
if file.stem in ("__init__", "client"):
|
|
|
|
pass
|
|
|
|
with file.open(encoding="utf-8") as fd:
|
|
|
|
contents = fd.read()
|
|
|
|
|
|
|
|
fm_visitor.visit(ast.parse(contents))
|
|
|
|
|
2023-09-30 18:13:24 +03:00
|
|
|
with client_py.open(encoding="utf-8") as fd:
|
2023-09-02 22:09:40 +03:00
|
|
|
contents = fd.read()
|
|
|
|
|
|
|
|
m_visitor.visit(ast.parse(contents))
|
|
|
|
|
2024-03-17 15:06:03 +03:00
|
|
|
class_body: list[ast.stmt] = []
|
2023-09-13 20:00:10 +03:00
|
|
|
|
2023-09-02 22:09:40 +03:00
|
|
|
for function in sorted(fm_visitor.methods, key=lambda f: f.name):
|
2024-03-17 20:26:32 +03:00
|
|
|
function.body.clear()
|
2023-09-02 22:09:40 +03:00
|
|
|
if doc := m_visitor.method_docs.get(function.name):
|
|
|
|
function.body.append(ast.Expr(value=ast.Constant(value=doc)))
|
|
|
|
|
|
|
|
call: ast.AST = ast.Call(
|
|
|
|
func=ast.Name(id=function.name, ctx=ast.Load()),
|
2024-03-18 20:55:23 +03:00
|
|
|
args=[
|
|
|
|
ast.Name(id=a.arg, ctx=ast.Load())
|
|
|
|
for a in function.args.posonlyargs + function.args.args
|
|
|
|
],
|
2023-09-02 22:09:40 +03:00
|
|
|
keywords=[
|
|
|
|
ast.keyword(arg=a.arg, value=ast.Name(id=a.arg, ctx=ast.Load()))
|
|
|
|
for a in function.args.kwonlyargs
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
2024-03-18 20:55:23 +03:00
|
|
|
if function.args.posonlyargs:
|
|
|
|
function.args.posonlyargs[0].annotation = None
|
|
|
|
else:
|
|
|
|
function.args.args[0].annotation = None
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
if isinstance(function, ast.AsyncFunctionDef):
|
2024-10-06 21:05:11 +03:00
|
|
|
call = ast.Await(value=call) # type: ignore [arg-type]
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
match function.returns:
|
|
|
|
case ast.Constant(value=None):
|
2024-10-06 21:05:11 +03:00
|
|
|
call = ast.Expr(value=call) # type: ignore [arg-type]
|
2023-09-02 22:09:40 +03:00
|
|
|
case _:
|
2024-10-06 21:05:11 +03:00
|
|
|
call = ast.Return(value=call) # type: ignore [arg-type]
|
2023-09-02 22:09:40 +03:00
|
|
|
|
2024-03-17 20:26:32 +03:00
|
|
|
function.body.append(call)
|
2023-09-13 20:00:10 +03:00
|
|
|
class_body.append(function)
|
2023-09-02 22:09:40 +03:00
|
|
|
|
2023-09-30 18:13:24 +03:00
|
|
|
generated = ast.unparse(
|
|
|
|
ast.ClassDef(
|
|
|
|
name="Client", bases=[], keywords=[], body=class_body, decorator_list=[]
|
2023-09-13 20:00:10 +03:00
|
|
|
)
|
2023-09-30 18:13:24 +03:00
|
|
|
)[len("class Client:") :].strip()
|
|
|
|
|
|
|
|
start_idx = contents.index("\n", contents.index("# Begin partially @generated"))
|
|
|
|
end_idx = contents.index("# End partially @generated")
|
|
|
|
|
|
|
|
with client_py.open("w", encoding="utf-8") as fd:
|
|
|
|
fd.write(
|
|
|
|
f"{contents[:start_idx]}\n\n {generated}\n\n {contents[end_idx:]}"
|
|
|
|
)
|
|
|
|
|
|
|
|
print("written @generated")
|
|
|
|
exit(subprocess.run((sys.executable, "-m", "black", str(client_py))).returncode)
|
2023-09-02 22:09:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|