-
Notifications
You must be signed in to change notification settings - Fork 210
[MRG] ENH: Capture 'repr's of last expression #541
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #541 +/- ##
==========================================
+ Coverage 97.45% 97.49% +0.04%
==========================================
Files 29 29
Lines 2825 2875 +50
==========================================
+ Hits 2753 2803 +50
Misses 72 72
Continue to review full report at Codecov.
|
|
Worked out the memory profiling - you need to Everything works - but the code is not elegant. Reviews welcome. |
|
I should be able to look tomorrow. Glad you were able to figure out the eval/exec business in the meantime! |
|
(I think this is a AppVeyor issue as all I changed were some variable names) |
|
Looks good! This should probably also be used at least once in the official docs. We use the gallery of |
|
I would also remove the |
|
Thanks!
I can simply alter Local module so the code is: N=100
NIt feels trivial but it shows the main point of the ENH. I would also like to add a note about the While looking into this I have realised that unwanted text is now output while making matplotlib plots -
I have tested these and they all work here. I think the best/easiest solution is just to document this, either in the first gallery with a plot: Colourmaps or the Sin function. WDYT? (On this note I would like to tidy up the galleries (
I wanted to make this into 2 issues and 2 PRs but I can understand that it makes sense to discuss in 1 issue - in which case I could have just added to the end of the old issue..! Alas, I will remove 'closes'. |
Sure, and once we support
Sure
This is going to be a potentially nasty backward compat break, as most libraries I work with do many such statements, and having the Does what will print the In MNE for example we will probably add |
Yes that is a good idea. I think it would be useful to make a new With regards to the Matplotlib problem - this bothered me all weekend but I could not think of a better solution. I have added the configuration. A few points:
Thus it does not make sense to me why we would make the config equal to
I am not sure how this would work. I understand fine grain control to work like For an existing Sphinx-Gallery project, I feel like it would be annoying to add this configuration (in new example |
I would not branch the code this way. I would always execute the second way, then the config option just determines which of the results we actually display in the output. Seems simpler to me.
Your code just needs to try to run the
Hopefully it makes sense based on what I said above
No we will add a To our examples. |
Let's worry about this when we actually need it. For now let's work on sorting out how the main config option should work, then when someone hits a use case let's figure out the file/block/line-based fine-grained control. |
To lay out more clearly, this is what I would do: |
Yes that makes much more sense. And trying the 'repr' methods in order of preference given by the config tuple sounds like an elegant solution. I will amend this. With regard to the fine grain control, I was mostly asking how the config would be changed by the |
doc/configuration.rst
Outdated
| The ``print_eval_repr`` configuration allows the user to control what output of | ||
| the last expression in a code block, is captured and rendered in the built | ||
| documentation. By default ``print_eval_repr`` is an empty tuple. With this | ||
| setting, the last statement in a code block is executed with ``exec()`` and |
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.
How this is accomplished (eval vs exec) is an implementation detail SG users don't need to know/care about. I would just describe that:
x = pandas.DataFrame(...)
is never rendered, but:
x
will print depending on the value of print_eval_repr.
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.
How this is accomplished (eval vs exec) is an implementation detail SG users don't need to know/care about.
You're right and I was thinking this when I wrote it.
will print depending on the value of print_eval_repr.
This is a better explanation. The problem is that I have not implemented capturing _repr_html_ yet and I was trying to make this PR and thus the documentation in it, applying only to the 'exec/eval'.
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.
If you want, you can make the docs for now just about () and ('__repr__',), and add __html_repr__ support separately
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.
@lucyleeow don't forget to address this
doc/getting_started.rst
Outdated
| ----------------------- | ||
|
|
||
| In your Sphinx source directory, (e.g., ``myproject/doc``) execute: | ||
| In your Sphinx source directory, (e.g. ``myproject/doc``) execute: |
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.
Most style guides suggest the use of a comma after both e.g. and i.e.
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.
still unaddressed
sphinx_gallery/tests/test_gen_rst.py
Outdated
| ('a=2\nb=3', ''), | ||
| ('a=2\na', '2'), | ||
| ('a=2\nprint(a)', '2'), | ||
| ('print("hello")\na=2\na', 'hello\n\n2')]) |
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.
You could also parametrize over whether print_eval_repr is ('__repr__',) or () and triage the output_test_string check below based on (in the empty case) whether the last line contains print() or not
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.
Sorry, I wasn't quite sure if you meant to parametrize twice, e.g.:
@pytest.mark.parametrize('config', [(),('__repr__',)])
@pytest.mark.parametrize('code', ['code1','code2'])I tried to do this but couldn't figure out a good way because the output for all options (2x2 in the case above) was different - the only solution I thought of was to write out all combinations. Also I wasn't sure what you meant by triage...
(I will explain the tests I made below)
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.
You can do just pairs rather than 2x2 with:
pytest.mark.parametrize('config, code', [((), 'code1'), (('__repr__',), 'code2')])
|
I was initially thinking to separate the 2 steps (as per #540) into 2 PRs (to keep it modular..?) but I can see also that it might make sense to do it all in 1 PR. (though this might depend on how I implement it) WDYT? The problem is that I am having trouble working out how to capture anything else apart from In the #420 POC PR, it is done using Suggestions welcome, otherwise I will continue banging my head against |
|
No idea if this makes sense without digging into |
|
Errant close, sorry |
|
I can definitely check if it is an assignment. An expression is anything that can be on the RHS of a variable assignment (i.e., The problem is that I am evaluating the compiled |
This is basically what I was thinking. Use |
|
I think (hope) this solution is an acceptable level of 'hacky'. I worked out how to assign the last statement, if it is an expression to a variable called a = 2
aturns into: a = 2
___ = aTechnically, since only statements that can be on the RHS of an assign are expressions, I think I am safe in assigning any expression to a variable. Doing it with
I can then use
html headerThe html header I used was just the very simple: I think it is best for users to include in their raw html anything extra (e.g., if they want their html in an iframe). Open to suggestions. TestsI appreciate that I may have gone overboard with the tests. I tried to make it clean (thank you @massich for your help) but please suggest better solutions. Here are the cases I wanted to capture:
CI is only failing for Python_version 'nightly' - and it is failing for many tests...I have no idea why... |
doc/getting_started.rst
Outdated
| ----------------------- | ||
|
|
||
| In your Sphinx source directory, (e.g., ``myproject/doc``) execute: | ||
| In your Sphinx source directory, (e.g. ``myproject/doc``) execute: |
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.
still unaddressed
examples/local_module.py
Outdated
| """ | ||
|
|
||
| N = 100 | ||
| N |
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.
this example is actually not executed by SG (it does not match the build pattern) so this shouldn't do anything. Can you modify an example that runs, e.g., https://1042-25860190-gh.circle-artifacts.com/0/rtd_html/auto_examples/plot_function_identifier.html you could write font_size at the end and we should see it (if you also update our doc/conf.py to match, which would be worthwhile)
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.
Yes, I can do that. Just to confirm I can change our doc/conf.py print_eval_repr to ('__repr__',)?
Also, as you suggested it would be nice to give an example of this in action like a pandas dataframe - but would I have to import this if I were to use it? I could instead define my own class like in the test and use this to produce some html - maybe in a separate gallery as I am not sure it fits into any of the current galleries. I would also have to change the config for our documentation to ('_repr_html_', '__repr__') (which still might work fine for the other galleries, I will check).
I will also update the documentation to talk about capturing the other repr's
Finally, I just realised that we don't eval() anything now and maybe we should change the name of this config. Maybe 'capture_repr' or something similar...?
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.
Sure capture_repr seems good
Yes you can change doc/conf.py. I think it's also fine to add a pandas dependency for the doc build for the purposes of showing the __html_repr__ (eventually) and/or __repr__ (in this PR). Basically we want to show a use case that will be common, and maybe that will be the most common one.
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.
But this PR will now be able to show _repr_html_?
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.
In that case I'd change our doc/conf.py to have __repr_html__ and __repr__ in it, demo both in some example, and also update the docs in this PR to mention __repr_html__ since it's not in there currently
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.
Excellent.
Sorry, can you add pandas as dependency for just the doc build or do you have to add it for the whole package?
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.
Just for the doc build is fine. Add it here:
And in intersphinx here:
as 'pandas': ('https://pandas.pydata.org/pandas-docs/stable', None),
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.
... and looking at our existing examples, making a dedicated example for this probably makes sense. You can show that __html_repr__ and __repr__ both work, depending on what the object actually exposes. And that assigments do not print.
larsoner
left a comment
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.
Other than a minor error that's breaking CircleCI and the fact that Travis and AppVeyor are unhappy, LGTM. Example looks good:
doc/getting_started.rst
Outdated
| :ref:`build_pattern`. | ||
| * The output that is captured while executing the ``.py`` files and | ||
| subsequently incorporated into the built documentation, can be finely | ||
| tuned. See :ref:`print_eval_repr`. |
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.
Wrong link now
|
The example does demo very well the functionality, and shows that you
have solved the problem. Congratulations!
One suggestion: frame the example as a demo of capturing the output, and
discuss the configuration options at the end. This will be more useful
for the end user.
|
|
(You were too quick for me! But here is my summary anyway, for clarity)
Unfortunately, the pandas dataframe used in the example does not look nice because the raw html in The test failing is because I added html line breaks. But I've also noticed an indenting problem. Might ponder overnight and do it tomorrow. @GaelVaroquaux nice to hear from you! :p |
|
The styling for pandas appears code-wise somewhere here: https://github.com/pandas-dev/pandas/blob/master/pandas/io/formats/html.py But really it looks like their output already includes it. Looking at the HTML I see: HTML<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>col1</th>
<th>col2</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>4</td>
</tr>
</tbody>
</table>How did you think it should look? If it looks better in Jupyter, it might be worth playing around a little bit with the code and/or pinging the pandas folks (listserv?) to get a hint on how to get (As we do more "it almost works but if only we could ..." fixes, we presumably get closer to replicating the functionality of |
|
The dataframe looks slightly better in jupyter notebook: I thought that If we do decide to just use nbconvert I might be slightly upset - (┛ಠ_ಠ)┛彡┻━┻ 😄 But a good proof of concept is right here: #422 I think CSS addition should be another PR...? But I am happy to ask about pandas. |
|
🙃 |
|
Don't wait for another review from me. This is great!! |
|
Thanks @lucyleeow ! |
|
Woot! Congratulations, Lucy, this is a major feature!!
I think that we are going to have to think about a release soon...
|
|
A release coming anytime soon so we can use this? support for |
|
@banesullivan yes it's a good point, documentation is ahead of the current release - I've been meaning to create an issue about this. If you do wish to use this feature you can install as developer. Sorry for the confusion. |
|
I think it's fine to release now. @lucyleeow do you want to try following our instructions for how to make a release? |
|
I should be able to get onto it this week but from reading the instructions, I may have some questions. What's the best way to ask? Make a new issue? Should I do the items that don't require push access to SG or? |
|
@lucyleeow send me your PyPi address and I'll add you there. Working on giving you write permissions here but I don't have admin access yet (I think?). Going to bother @agramfort about it |
|
😮 that is a lot of trust. I just made a PyPi account: https://pypi.org/user/lucyleeow/ |
|
Done. But let me get #559 taken care of before release |

Closes #540
First attempt. Works but is not pretty.
First, I could not use logic in Jupyter-Sphinx because the value of the last node in body is made into an expression (
last = ast.Expression(block.body.pop().value)) and evaluated regardless of whether it is an expression or not. This means that if the last node is a statement (e.g.b=2) the value (2) will be made into an expression and2will be returned (which it shouldn't be) andb=2is never run (and the variable not available later). Thepop()removes this node from the body, which is later executed (let me know if I missed something).Notes:
print()expression should not be run twice, as both outputs would be captured.I could not work out how to memory profile the last expression. I think ideally memory profiler should be run on both the 'body' and the last expression and the max mem of the 2 used. I tried using
_memory_usage()with_exec_once()(withexec()changed toeval()within the function) but variables assigned in the body were not available. Is it because ofglobals_being set within_exec_once()(?):Suggestions? @GaelVaroquaux ?