-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify dev_tools.notebooks.utils.rewrite_notebook #6030
Changes from 6 commits
dad7560
85891a3
c695e98
dd1a130
ed7315e
9464f43
92477a7
0d5895f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,12 @@ | |
import pytest | ||
|
||
from dev_tools import shell_tools | ||
from dev_tools.notebooks import filter_notebooks, list_all_notebooks, rewrite_notebook | ||
from dev_tools.notebooks import ( | ||
filter_notebooks, | ||
list_all_notebooks, | ||
rewrite_notebook, | ||
remove_if_temporary_notebook, | ||
) | ||
|
||
SKIP_NOTEBOOKS = [ | ||
# skipping vendor notebooks as we don't have auth sorted out | ||
|
@@ -67,7 +72,7 @@ def test_notebooks_against_released_cirq(notebook_path): | |
notebook_file = os.path.basename(notebook_path) | ||
notebook_rel_dir = os.path.dirname(os.path.relpath(notebook_path, ".")) | ||
out_path = f"out/{notebook_rel_dir}/{notebook_file[:-6]}.out.ipynb" | ||
rewritten_notebook_descriptor, rewritten_notebook_path = rewrite_notebook(notebook_path) | ||
rewritten_notebook_path = rewrite_notebook(notebook_path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
papermill_flags = "--no-request-save-on-cell-execute --autosave-cell-every 0" | ||
cmd = f"""mkdir -p out/{notebook_rel_dir} | ||
papermill {rewritten_notebook_path} {out_path} {papermill_flags}""" | ||
|
@@ -83,6 +88,4 @@ def test_notebooks_against_released_cirq(notebook_path): | |
f"notebook (in Github Actions, you can download it from the workflow artifact" | ||
f" 'notebook-outputs')" | ||
) | ||
|
||
if rewritten_notebook_descriptor: | ||
os.close(rewritten_notebook_descriptor) | ||
remove_if_temporary_notebook(rewritten_notebook_path) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,7 +69,7 @@ def rewrite_notebook(notebook_path): | |
|
||
* Lines in this file without `->` are ignored. | ||
|
||
* Lines in this file with `->` are split into two (if there are mulitple `->` it is an | ||
* Lines in this file with `->` are split into two (if there are multiple `->` it is an | ||
error). The first of these is compiled into a pattern match, via `re.compile`, and | ||
the second is the replacement for that match. | ||
|
||
|
@@ -82,16 +82,17 @@ def rewrite_notebook(notebook_path): | |
It is the responsibility of the caller of this method to delete the new file. | ||
|
||
Returns: | ||
Tuple of a file descriptor and the file path for the rewritten file. If no `.tst` file | ||
was found, then the file descriptor is None and the path is `notebook_path`. | ||
The file path for the rewritten file. If no `.tst` file was found, | ||
return the input `notebook_path` as the notebook was not changed. | ||
Otherwise return a new path created in the temporary directory. | ||
|
||
Raises: | ||
AssertionError: If there are multiple `->` per line, or not all of the replacements | ||
are used. | ||
""" | ||
notebook_test_path = os.path.splitext(notebook_path)[0] + '.tst' | ||
if not os.path.exists(notebook_test_path): | ||
return None, notebook_path | ||
return notebook_path | ||
|
||
# Get the rewrite rules. | ||
patterns = [] | ||
|
@@ -104,20 +105,37 @@ def rewrite_notebook(notebook_path): | |
|
||
used_patterns = set() | ||
with open(notebook_path, 'r') as original_file: | ||
new_file_descriptor, new_file_path = tempfile.mkstemp(suffix='.ipynb') | ||
with open(new_file_path, 'w') as new_file: | ||
for line in original_file: | ||
new_line = line | ||
for pattern, replacement in patterns: | ||
new_line = pattern.sub(replacement, new_line) | ||
if new_line != line: | ||
used_patterns.add(pattern) | ||
break | ||
new_file.write(new_line) | ||
lines = original_file.readlines() | ||
for i, line in enumerate(lines): | ||
for pattern, replacement in patterns: | ||
new_line = pattern.sub(replacement, line) | ||
if new_line != line: | ||
lines[i] = new_line | ||
used_patterns.add(pattern) | ||
break | ||
|
||
assert len(patterns) == len(used_patterns), ( | ||
'Not all patterns where used. Patterns not used: ' | ||
f'{set(x for x, _ in patterns) - used_patterns}' | ||
) | ||
|
||
return new_file_descriptor, new_file_path | ||
with tempfile.NamedTemporaryFile(mode='w', suffix='-rewrite.ipynb', delete=False) as new_file: | ||
new_file.writelines(lines) | ||
|
||
return new_file.name | ||
|
||
|
||
def remove_if_temporary_notebook(notebook_path: str): | ||
"""Delete temporary notebook if written by `rewrite_notebook`. | ||
|
||
Do nothing if the specified notebook is not in the temporary directory | ||
or if its filename does not end in '-rewrite.ipynb' | ||
|
||
Use this to safely clean up notebooks created by `rewrite_notebook`. | ||
""" | ||
if ( | ||
notebook_path.endswith('-rewrite.ipynb') | ||
and os.path.isfile(notebook_path) | ||
and os.path.dirname(notebook_path) == tempfile.gettempdir() | ||
): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implements the pattern wherein we make a decision (here: whether or not we need to rewrite a notebook into a temporary file) and then later try to work out what decision was taken to correlate earlier and later actions. This coding pattern is brittle since it's easy for the code to evolve in a way that breaks perfect correlation between the two decision points. One way to solve this is to return a bool from If you really dislike two return values you could try to hide the bool in the type by e.g. returning an instance of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
os.remove(notebook_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we are (and have always been) leaking temporary files. Could you fix while you're at it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@viathor - thank you for the review. The temporary files cleanup was a bit more involved - can you please take a quick look again?