-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
[bugfix] solve crash when using inspect()
on the "pyparsing" package
#2294
Conversation
@@ -61,3 +61,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] | |||
[[tool.mypy.overrides]] | |||
module = ["pygments.*", "IPython.*", "commonmark.*", "ipywidgets.*"] | |||
ignore_missing_imports = true | |||
|
|||
[tool.pytest.ini_options] | |||
testpaths = ["tests"] |
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.
while working on this I noticed that creating a Python script with a name starting ending with test_
anywhere in the project's repo was collected by Pytest: I think we'd better tell Pytest to stick with the contents of our tests/
folder - which should also make the tests collection faster as a side effect
@@ -19,12 +19,6 @@ def _first_paragraph(doc: str) -> str: | |||
return paragraph | |||
|
|||
|
|||
def _reformat_doc(doc: str) -> str: | |||
"""Reformat docstring.""" | |||
doc = cleandoc(doc).strip() |
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.
I noticed that we were ending up calling cleandoc
twice on our extracted docstrings - to remedy this I inlined this operation in a single _get_formatted_doc
method, later on this same module
if _doc is not None: | ||
if not self.help: |
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.
The process of calling cleandoc()
on the doctring's content and getting only its first paragraph if self.help
is not True is now factorised in a single method, rather than done twice in this class
self.style = style | ||
self.justify: Optional["JustifyMethod"] = justify | ||
self.overflow: Optional["OverflowMethod"] = overflow | ||
self.no_wrap = no_wrap | ||
self.end = end | ||
self.tab_size = tab_size | ||
self._spans: List[Span] = spans or [] | ||
self._length: int = len(text) | ||
self._length: int = len(sanitized_text) |
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.
that's the bugfix itself! 🙂 i.e. we should use the length of the sanitised text, rather than the one of the text we received as an argument
old_length = self._length | ||
self._length = len(new_text) | ||
self._length = len(sanitized_text) |
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.
same bug here 🙂
offset = len(self) | ||
text_length = len(text) | ||
text_length = len(sanitized_text) |
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 here
expected_replacement | ||
) | ||
|
||
assert render(Something, methods=True) == expected |
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.
to make sure that we don't have regressions on this bugfix, before trying to solve it I started by writing a test that reproduce the issue : this test was crashing before the fix, and passes now 🎈
4b1893a
to
49e2b1d
Compare
2320989
to
74d7807
Compare
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.
Great. Just a few pendantic requests, and we should be good to merge.
rich/control.py
Outdated
|
||
_CONTROL_MAKE_READABLE_TRANSLATE: Final = { |
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.
Can we call this CONTROL_ESCAPE
rich/control.py
Outdated
@@ -182,6 +198,22 @@ def strip_control_codes( | |||
return text.translate(_translate_table) | |||
|
|||
|
|||
def make_control_codes_readable( |
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.
I'd prefer escape_control_code
. Would you mind changing the terminology from "make readable" elsewhere?
@@ -220,3 +210,12 @@ def safe_getattr(attr_name: str) -> Tuple[Any, Any]: | |||
f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] " | |||
f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." | |||
) | |||
|
|||
def _get_formatted_doc(self, object_: Any) -> Optional[str]: |
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.
Would you mind adding a docstring.
…eadable version in docstrings With this we allow the following: - Such characters will be displayed in the data returned by "rich.inspect" - We fix the crash that can happen in some circumstances in the `Text.divide()` method when some docstrings have such special characters
…eadable version With this we allow the following: - Such characters will be displayed in the data returned by "rich.inspect" when they are used in docstrings - We fix the crash that can happen in some circumstances in the `Text.divide()` method when some docstrings have such special characters
…t with their escaped version
ce0b2ff
to
25d0c0e
Compare
@willmcgugan "escape control codes" is a better terminology indeed! 🙂 |
Codecov Report
@@ Coverage Diff @@
## master #2294 +/- ##
==========================================
- Coverage 98.88% 98.76% -0.13%
==========================================
Files 73 73
Lines 7629 7684 +55
==========================================
+ Hits 7544 7589 +45
- Misses 85 95 +10
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
👍 |
Type of changes
But we can of course choose to keep the existing behaviour as-is instead 🙂
Checklist
(N/A)
Description
As reported there, running this command was making Rich crash:
It took me a while to understand what was going wrong, as the code from which the exception pops is in
Text.divide(offsets)
which is quite a complex part 🤯But in the end I identified the culprits 🥷 : special ASCII characters! (in docstrings)
We were removing them from the content of the textual content of the
Text
class, but when caching the length of this content we were using the length of the non-sanitised text - which causes issues in some cases, as the text we're splitting into several lines doesn't actually have the length we think it has.Should we make control characters contained in docstrings visible in the inspection?
I first solved the bug by just making sure the cached length of the text is the one of the sanitised version of it, but then I noticed that the result was looking a bit odd with docstrings that contains such special characters, such as the
WordStart
class of this pyparsing package.When dumped with
inspect
we were ending up with "To emulate the ```` behavior of regular expressions", with 4 backticks in a row without content:That's why I opted for another strategy: replacing these control codes with their "readable" equivalents, so
\b
is displayed as "\b":However, although it's rather unlikely I appreciate that doing such a change in Rich's behaviour could break people's code if they were using some parsing over the result of
rich.inspect
for example.It depends I guess on whether or not we consider the stripping of control codes in docstrings, when inspecting code, as a feature of a bug? 🤔
If it's feature we may rather choose to keep the existing behaviour for the moment, and potentially change it later on with a major version bump, or an opt-in flag somewhere in Rich? 🙂