Skip to content

Commit

Permalink
Allow specifying requirements in examples
Browse files Browse the repository at this point in the history
  • Loading branch information
Bobronium committed Aug 9, 2022
1 parent b255fe5 commit db7b88e
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 13 deletions.
34 changes: 24 additions & 10 deletions docs/build/exec_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def gen_ansi_output() -> None:

dont_execute_re = re.compile(r'^# dont-execute\n', flags=re.M | re.I)
dont_upgrade_re = re.compile(r'^# dont-upgrade\n', flags=re.M | re.I)
requires_re = re.compile(r'^# requires: *(.+)\n', flags=re.M | re.I)
required_py_re = re.compile(r'^# *requires *python *(\d+).(\d+)', flags=re.M)


Expand Down Expand Up @@ -166,6 +167,13 @@ def should_upgrade(file_text: str) -> tuple[str, bool]:
return file_text, True


def get_requirements(file_text: str) -> tuple[str, str | None]:
m = requires_re.search(file_text)
if m:
return requires_re.sub('', file_text), m.groups()[0]
return file_text, None


def exec_file(file: Path, file_text: str, error: Error) -> tuple[list[str], str | None]:
no_print_intercept_re = re.compile(r'^# no-print-intercept\n', flags=re.M)
print_intercept = not bool(no_print_intercept_re.search(file_text))
Expand Down Expand Up @@ -205,27 +213,26 @@ def exec_file(file: Path, file_text: str, error: Error) -> tuple[list[str], str


def filter_lines(lines: list[str], error: Any) -> tuple[list[str], bool]:
changed = False
ignored_above = False
try:
ignore_above = lines.index('# ignore-above')
except ValueError:
pass
else:
changed = True
ignored_above = True
lines = lines[ignore_above + 1 :]

try:
ignore_below = lines.index('# ignore-below')
except ValueError:
pass
else:
changed = True
lines = lines[:ignore_below]

lines = '\n'.join(lines).split('\n')
if any(len(l) > MAX_LINE_LENGTH for l in lines):
error(f'lines longer than {MAX_LINE_LENGTH} characters')
return lines, changed
return lines, ignored_above


def upgrade_code(content: str, min_version: Version = HIGHEST_VERSION) -> str:
Expand Down Expand Up @@ -316,10 +323,10 @@ def exec_examples() -> int:

def error(*desc: str) -> None:
errors.append((file, desc))
previous_frame = sys._getframe(-3)
previous_frame = sys._getframe(1)
filename = Path(previous_frame.f_globals["__file__"]).relative_to(Path.cwd())
location = f'{filename}:{previous_frame.f_lineno}'
sys.stderr.write(f'{location}: error in {file.name}: {" ".join(desc)}\n')
sys.stderr.write(f'{location}: error in {file.relative_to(Path.cwd())}:\n{" ".join(desc)}\n')

if not file.is_file():
# __pycache__, maybe others
Expand All @@ -336,6 +343,7 @@ def error(*desc: str) -> None:

file_text, execute, lowest_version = should_execute(file.name, file_text)
file_text, upgrade = should_upgrade(file_text)
file_text, requirements = get_requirements(file_text)

if upgrade and upgrade_code(file_text, min_version=lowest_version) != file_text:
error("pyupgrade would upgrade file. If it's not desired, add '# dont-upgrade' line at the top of the file")
Expand All @@ -345,7 +353,7 @@ def error(*desc: str) -> None:
versions.extend(populate_upgraded_versions(file, file_text, lowest_version))

json_outputs: set[str | None] = set()
should_run_as_is = True
should_run_as_is = not requirements
final_content: list[str] = []
for file, file_text, lowest_version in versions:
if execute and sys.version_info >= lowest_version:
Expand All @@ -354,8 +362,8 @@ def error(*desc: str) -> None:
else:
lines = file_text.split('\n')

lines, changed = filter_lines(lines, error)
should_run_as_is = not changed
lines, ignored_lines_before_script = filter_lines(lines, error)
should_run_as_is = should_run_as_is and not ignored_lines_before_script

final_content.append(
PYTHON_CODE_MD_TMPL.format(
Expand All @@ -366,7 +374,13 @@ def error(*desc: str) -> None:

if should_run_as_is:
final_content.append('_(This script is complete, it should run "as is")_')

elif requirements:
final_content.append(f'_(This script requires {requirements})_')
else:
error(
"script may not run as is, but requirements were not specified.",
"specify `# requires: ` in the end of the script",
)
if len(json_outputs) > 1:
error('json output should not differ between versions')

Expand Down
1 change: 1 addition & 0 deletions docs/examples/index_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
User(signup_ts='broken', friends=[1, 2, 'not number'])
except ValidationError as e:
print(e.json())
# requires: User from previous example
1 change: 1 addition & 0 deletions docs/examples/settings_disable_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ def customise_sources(


print(Settings(my_api_key='this is ignored'))
# requires: `MY_API_KEY` env variable to be set, e.g. `export MY_API_KEY=xxx`
1 change: 1 addition & 0 deletions docs/examples/validation_decorator_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ async def main():


asyncio.run(main())
# requires: `conn.execute()` that will return `'testing@example.com'`
2 changes: 0 additions & 2 deletions docs/usage/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,6 @@ Each error object contains:
As a demonstration:

{!.tmp_examples/models_errors1.md!}
_(This script is complete, it should run "as is". `json()` has `indent=2` set by default, but I've tweaked the
JSON here and below to make it slightly more concise.)_

### Custom Errors

Expand Down
1 change: 0 additions & 1 deletion docs/usage/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,3 @@ need to add your own custom sources, `customise_sources` makes this very easy:
You might also want to disable a source:

{!.tmp_examples/settings_disable_source.md!}
_(This script is complete, it should run "as is", here you might need to set the `my_api_key` environment variable)_

0 comments on commit db7b88e

Please sign in to comment.