refactor: modularize registration runtime safely
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TARGETS = [
|
||||
ROOT / "grok_register_ttk.py",
|
||||
ROOT / "registration_flow.py",
|
||||
ROOT / "cpa_xai" / "browser_confirm.py",
|
||||
ROOT / "cpa_export.py",
|
||||
ROOT / "cf_mail_debug.py",
|
||||
ROOT / "cpa_xai" / "oauth_device.py",
|
||||
ROOT / "cpa_xai" / "proxyutil.py",
|
||||
]
|
||||
|
||||
|
||||
def free_names(node):
|
||||
assigned = set()
|
||||
loaded = set()
|
||||
for child in ast.walk(node):
|
||||
if isinstance(child, ast.Name):
|
||||
if isinstance(child.ctx, (ast.Store, ast.Param)):
|
||||
assigned.add(child.id)
|
||||
elif isinstance(child.ctx, ast.Load):
|
||||
loaded.add(child.id)
|
||||
elif isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) and child is not node:
|
||||
assigned.add(child.name)
|
||||
return sorted(loaded - assigned)
|
||||
|
||||
out = {}
|
||||
for path in TARGETS:
|
||||
if not path.exists():
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8")
|
||||
tree = ast.parse(text, filename=str(path))
|
||||
entries = []
|
||||
for node in tree.body:
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||
entries.append({
|
||||
"kind": type(node).__name__,
|
||||
"name": node.name,
|
||||
"start": node.lineno,
|
||||
"end": getattr(node, "end_lineno", node.lineno),
|
||||
"free_names": free_names(node),
|
||||
})
|
||||
out[str(path.relative_to(ROOT))] = {
|
||||
"lines": len(text.splitlines()),
|
||||
"definitions": entries,
|
||||
}
|
||||
|
||||
(ROOT / "refactor_inventory.json").write_text(
|
||||
json.dumps(out, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
print("inventory written")
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__).resolve().parent / "apply_full_safe_modularization.py"
|
||||
text = path.read_text(encoding="utf-8")
|
||||
old = '''def node_span(text: str, node: ast.AST) -> tuple[int, int]:
|
||||
lines = text.splitlines(keepends=True)
|
||||
offsets = [0]
|
||||
for line in lines:
|
||||
offsets.append(offsets[-1] + len(line))
|
||||
start = offsets[node.lineno - 1] + getattr(node, "col_offset", 0)
|
||||
end_line = getattr(node, "end_lineno", node.lineno)
|
||||
end_col = getattr(node, "end_col_offset", len(lines[end_line - 1]))
|
||||
end = offsets[end_line - 1] + end_col
|
||||
while end < len(text) and text[end] in "\\r\\n":
|
||||
end += 1
|
||||
return start, end
|
||||
'''
|
||||
new = '''def node_span(text: str, node: ast.AST) -> tuple[int, int]:
|
||||
# AST column offsets are UTF-8 byte offsets, not Python character offsets.
|
||||
# Every node moved by this script is top-level, so complete source lines are
|
||||
# the safest exact boundary and preserve non-ASCII strings without truncation.
|
||||
lines = text.splitlines(keepends=True)
|
||||
offsets = [0]
|
||||
for line in lines:
|
||||
offsets.append(offsets[-1] + len(line))
|
||||
start = offsets[node.lineno - 1]
|
||||
end_line = getattr(node, "end_lineno", node.lineno)
|
||||
end = offsets[end_line]
|
||||
return start, end
|
||||
'''
|
||||
if text.count(old) != 1:
|
||||
raise RuntimeError(f"node_span patch expected one match, got {text.count(old)}")
|
||||
path.write_text(text.replace(old, new, 1), encoding="utf-8")
|
||||
print("fixed AST source spans")
|
||||
@@ -1,130 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__).resolve().with_name("apply_full_safe_modularization.py")
|
||||
text = path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def replace_once(source, old, new, label):
|
||||
count = source.count(old)
|
||||
if count != 1:
|
||||
raise RuntimeError(f"{label}: expected one match, got {count}")
|
||||
return source.replace(old, new, 1)
|
||||
|
||||
|
||||
# Keep monkeypatch compatibility for generate_username after moving mail code.
|
||||
text = replace_once(
|
||||
text,
|
||||
'''def _bind_mail_service():
|
||||
_mail_service.bind_runtime(globals())
|
||||
''',
|
||||
'''def _bind_mail_service():
|
||||
_mail_service.bind_runtime(globals())
|
||||
_current = globals().get("generate_username")
|
||||
_standard = _MAIL_COMPAT_PROXIES.get("generate_username")
|
||||
if _current is not None and _current is not _standard:
|
||||
_mail_service.generate_username = _current
|
||||
elif _standard is not None:
|
||||
_mail_service.generate_username = _MAIL_ORIGINALS["generate_username"]
|
||||
''',
|
||||
"mail binder",
|
||||
)
|
||||
|
||||
text = replace_once(
|
||||
text,
|
||||
'''for _name in {MAIL_NAMES!r}:
|
||||
globals()[_name] = _make_compat_proxy(_mail_service, _name, _bind_mail_service)
|
||||
for _name in {REGISTRATION_NAMES!r}:
|
||||
''',
|
||||
'''_MAIL_ORIGINALS = dict((name, getattr(_mail_service, name)) for name in {MAIL_NAMES!r})
|
||||
_MAIL_COMPAT_PROXIES = dict()
|
||||
for _name in {MAIL_NAMES!r}:
|
||||
_proxy = _make_compat_proxy(_mail_service, _name, _bind_mail_service)
|
||||
_MAIL_COMPAT_PROXIES[_name] = _proxy
|
||||
globals()[_name] = _proxy
|
||||
for _name in {REGISTRATION_NAMES!r}:
|
||||
''',
|
||||
"mail proxy registry",
|
||||
)
|
||||
|
||||
# Preserve cf_mail_debug.requests and create_address for tests/external callers.
|
||||
text = replace_once(
|
||||
text,
|
||||
'''import argparse
|
||||
import time
|
||||
|
||||
from mail_service import CloudflareMailClient, extract_verification_code
|
||||
|
||||
|
||||
def main():
|
||||
''',
|
||||
'''import argparse
|
||||
import time
|
||||
|
||||
from curl_cffi import requests
|
||||
from mail_service import CloudflareMailClient, extract_verification_code
|
||||
|
||||
|
||||
def create_address(api_base, auth_mode="none", api_key="", create_path="/api/new_address",
|
||||
domain="", name="", timeout=20):
|
||||
import mail_service as _mail_service
|
||||
client = CloudflareMailClient(
|
||||
api_base, auth_mode=auth_mode, api_key=api_key,
|
||||
create_path=create_path, timeout=timeout,
|
||||
)
|
||||
original_requests = _mail_service.requests
|
||||
_mail_service.requests = requests
|
||||
try:
|
||||
return client.create_address(domain=domain, name=name)
|
||||
finally:
|
||||
_mail_service.requests = original_requests
|
||||
|
||||
|
||||
def main():
|
||||
''',
|
||||
"debug public API",
|
||||
)
|
||||
|
||||
text = replace_once(
|
||||
text,
|
||||
''' address, credential = client.create_address(domain=args.domain, name=args.name)
|
||||
''',
|
||||
''' address, credential = create_address(
|
||||
args.api_base,
|
||||
auth_mode=args.auth_mode,
|
||||
api_key=args.api_key,
|
||||
create_path=args.create_path,
|
||||
domain=args.domain,
|
||||
name=args.name,
|
||||
)
|
||||
''',
|
||||
"debug create call",
|
||||
)
|
||||
|
||||
# External cpa_xai packages may use relative imports; register before execution.
|
||||
text = replace_once(
|
||||
text,
|
||||
"import shutil\nimport time\nfrom pathlib import Path\n",
|
||||
"import shutil\nimport sys\nimport time\nfrom pathlib import Path\n",
|
||||
"cpa sys import",
|
||||
)
|
||||
text = replace_once(
|
||||
text,
|
||||
''' module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module.mint_and_export
|
||||
''',
|
||||
''' module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
except Exception:
|
||||
sys.modules.pop(module_name, None)
|
||||
raise
|
||||
return module.mint_and_export
|
||||
''',
|
||||
"cpa external package load",
|
||||
)
|
||||
|
||||
path.write_text(text, encoding="utf-8")
|
||||
print("public compatibility patch applied")
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__).resolve().with_name("apply_full_safe_modularization.py")
|
||||
text = path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def replace_once(source, old, new, label):
|
||||
count = source.count(old)
|
||||
if count != 1:
|
||||
raise RuntimeError(f"{label}: expected one match, got {count}")
|
||||
return source.replace(old, new, 1)
|
||||
|
||||
|
||||
# Patch the generated main-module import block using a small stable anchor.
|
||||
text = replace_once(
|
||||
text,
|
||||
"import_block = '''\\nimport functools\\n",
|
||||
"import_block = '''\\nimport functools\\nimport types\\n",
|
||||
"main import block",
|
||||
)
|
||||
|
||||
old_getattr = '''def __getattr__(name):
|
||||
if name in {{"browser", "page", "browser_proxy_bridge", "browser_started_with_proxy", "cf_clearance"}}:
|
||||
return getattr(_registration_browser, name)
|
||||
raise AttributeError(name)
|
||||
'''
|
||||
new_getattr = '''def __getattr__(name):
|
||||
if name in {{"browser", "page", "browser_proxy_bridge", "browser_started_with_proxy", "cf_clearance"}}:
|
||||
return getattr(_registration_browser, name)
|
||||
if name in {{"_cf_domain_index", "_cloudmail_domain_index"}}:
|
||||
return getattr(_mail_service, name)
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
class _CompatibilityModule(types.ModuleType):
|
||||
def __setattr__(self, name, value):
|
||||
if name == "config":
|
||||
if value is not _app_config.config:
|
||||
if not isinstance(value, dict):
|
||||
raise TypeError("config must be a dict")
|
||||
_app_config.config.clear()
|
||||
_app_config.config.update(value)
|
||||
value = _app_config.config
|
||||
elif name in {{"_cf_domain_index", "_cloudmail_domain_index"}}:
|
||||
setattr(_mail_service, name, int(value))
|
||||
self.__dict__.pop(name, None)
|
||||
return
|
||||
super().__setattr__(name, value)
|
||||
|
||||
|
||||
sys.modules[__name__].__class__ = _CompatibilityModule
|
||||
'''
|
||||
text = replace_once(text, old_getattr, new_getattr, "compatibility getattr")
|
||||
|
||||
path.write_text(text, encoding="utf-8")
|
||||
print("shared state compatibility patch applied")
|
||||
Reference in New Issue
Block a user