mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
e7227db41a
* starting insert prints * working exec_script * remove prints, fix exec_examples.py * more cleanup of examples, better model printing * upgrade netlify runtime * extra docs deps * few more small tweaks
165 lines
5.1 KiB
Python
Executable File
165 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import importlib
|
|
import inspect
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import textwrap
|
|
import traceback
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
|
|
from devtools import PrettyFormat
|
|
from pydantic import BaseModel
|
|
|
|
THIS_DIR = Path(__file__).parent
|
|
DOCS_DIR = (THIS_DIR / '..').resolve()
|
|
EXAMPLES_ROOT = DOCS_DIR / 'examples'
|
|
TMP_EXAMPLES_ROOT = DOCS_DIR / '.tmp_examples'
|
|
MAX_LINE_LENGTH = int(re.search(r'max_line_length = (\d+)', (EXAMPLES_ROOT / '.editorconfig').read_text()).group(1))
|
|
LONG_LINE = 50
|
|
pformat = PrettyFormat(simple_cutoff=LONG_LINE)
|
|
PRINT_TO_JSON = {'example2.py', 'schema1.py', 'schema2.py', 'schema3.py', 'schema4.py'}
|
|
ENVIRON = {'my_auth_key': 'xxx', 'my_api_key': 'xxx'}
|
|
|
|
|
|
def to_string(value: Any) -> str:
|
|
# attempt to build a pretty version
|
|
if isinstance(value, BaseModel):
|
|
s = str(value)
|
|
if len(s) > LONG_LINE:
|
|
indent = ' ' * (len(value.__class__.__name__) + 1)
|
|
return value.__class__.__name__ + ' ' + f'\n{indent}'.join(f'{k}={v!r}' for k, v in value.__dict__.items())
|
|
else:
|
|
return s
|
|
if isinstance(value, (dict, list, tuple, set)):
|
|
return pformat(value)
|
|
elif isinstance(value, str) and any(re.fullmatch(r, value, flags=re.DOTALL) for r in ['{".+}', r'\[.+\]']):
|
|
try:
|
|
obj = json.loads(value)
|
|
except ValueError:
|
|
# not JSON, not a problem
|
|
pass
|
|
else:
|
|
s = json.dumps(obj)
|
|
if len(s) > LONG_LINE:
|
|
json.dumps(obj, indent=2)
|
|
else:
|
|
return s
|
|
|
|
return str(value)
|
|
|
|
|
|
class MockPrint:
|
|
def __init__(self, file: Path):
|
|
self.file = file
|
|
self.statements = []
|
|
|
|
def __call__(self, *args):
|
|
frame = inspect.currentframe().f_back.f_back.f_back
|
|
if not self.file.samefile(frame.f_code.co_filename):
|
|
# sys.stdout.write(' '.join(map(str, args)))
|
|
raise RuntimeError("what's wrong here?")
|
|
s = ' '.join(map(to_string, args))
|
|
|
|
lines = []
|
|
for line in s.split('\n'):
|
|
if len(line) > MAX_LINE_LENGTH - 3:
|
|
lines += textwrap.wrap(line, width=MAX_LINE_LENGTH - 3)
|
|
else:
|
|
lines.append(line)
|
|
self.statements.append((frame.f_lineno, lines))
|
|
|
|
|
|
def all_md_contents() -> str:
|
|
file_contents = []
|
|
for f in DOCS_DIR.glob('**/*.md'):
|
|
file_contents.append(f.read_text())
|
|
return '\n\n\n'.join(file_contents)
|
|
|
|
|
|
def exec_examples():
|
|
errors = []
|
|
all_md = all_md_contents()
|
|
new_files = {}
|
|
os.environ.clear()
|
|
os.environ.update(ENVIRON)
|
|
|
|
sys.path.append(str(EXAMPLES_ROOT))
|
|
for file in sorted(EXAMPLES_ROOT.iterdir()):
|
|
|
|
def error(desc: str):
|
|
errors.append((file, desc))
|
|
sys.stderr.write(f'{file.name} Error: {desc}\n')
|
|
|
|
if not file.is_file():
|
|
# __pycache__, maybe others
|
|
continue
|
|
|
|
if file.suffix != '.py':
|
|
# just copy
|
|
new_files[file.name] = file.read_text()
|
|
continue
|
|
|
|
if f'{{!.tmp_examples/{file.name}!}}' not in all_md:
|
|
error('file not used anywhere')
|
|
mp = MockPrint(file)
|
|
with patch('builtins.print') as mock_print:
|
|
mock_print.side_effect = mp
|
|
try:
|
|
importlib.import_module(file.stem)
|
|
except Exception:
|
|
tb = traceback.format_exception(*sys.exc_info())
|
|
error(''.join(e for e in tb if '/pydantic/docs/examples/' in e or not e.startswith(' File ')))
|
|
|
|
file_text = file.read_text()
|
|
if '\n\n\n' in file_text:
|
|
error('too many new lines')
|
|
if not file_text.endswith('\n'):
|
|
error('no trailing new line')
|
|
lines = file_text.split('\n')
|
|
|
|
if any(len(l) > MAX_LINE_LENGTH for l in lines):
|
|
error(f'lines longer than {MAX_LINE_LENGTH} characters')
|
|
|
|
if file.name in PRINT_TO_JSON:
|
|
if len(mp.statements) != 1:
|
|
error('should only have one print statement')
|
|
new_files[file.stem + '.json'] = '\n'.join(mp.statements[0][1]) + '\n'
|
|
|
|
else:
|
|
for line_no, print_lines in reversed(mp.statements):
|
|
if len(print_lines) > 2:
|
|
text = '"""\n{}\n"""'.format('\n'.join(print_lines))
|
|
else:
|
|
text = '\n'.join('#> ' + l for l in print_lines)
|
|
lines.insert(line_no, text)
|
|
|
|
try:
|
|
ignore_above = lines.index('# === ignore above')
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
lines = lines[ignore_above + 1 :]
|
|
|
|
new_files[file.name] = '\n'.join(lines)
|
|
|
|
if errors:
|
|
return 1
|
|
|
|
if TMP_EXAMPLES_ROOT.exists():
|
|
shutil.rmtree(TMP_EXAMPLES_ROOT)
|
|
|
|
print(f'writing {len(new_files)} example files to {TMP_EXAMPLES_ROOT}')
|
|
TMP_EXAMPLES_ROOT.mkdir()
|
|
for file_name, content in new_files.items():
|
|
(TMP_EXAMPLES_ROOT / file_name).write_text(content)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(exec_examples())
|