-
Notifications
You must be signed in to change notification settings - Fork 30
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
Override default readline via python -m override_readline
#72
Conversation
Over the years we have tried and debated many ways to override the default readline module in Python so that tab completion can be fixed in the standard interactive interpreter (see e.g. #26 and #27). The original solution was `easy_install readline`: ugly but effective. In the pip era this option has fallen away. The current installation route of `pip install gnureadline` leaves it up to the user to override `sys.modules['readline']` or to `import gnureadline as readline`. The problem is that those tricks have to happen before the interpreter even prints its startup banner. Python 3 roughly starts up like this: - run site.py: - set up `sys.__interactivehook__` [but DON'T import readline yet] - run sitecustomize.py - run usercustomize.py - import rlcompleter [imports readline!] - output: Python 3.11.3 (main, May 9 2023, 18:38:16) [...] on darwin Type "help", "copyright", "credits" or "license" for more information. - run PYTHONSTARTUP - run sys.__interactivehook__() [also imports readline fwiw] - output: >>> PYTHONSTARTUP is too late. It can modify the interactive hook but not readline itself. That leaves sitecustomize and usercustomize. It feels a bit iffy when an installed package modifies those but, in mitigation, the user has to push the button and the override tries to explain what it does in some detail. When you run `python -m override_readline`, the script first targets sitecustomize.py because it is better for virtual environments and system-wide overrides. If you don't have permission, it falls back to usercustomize.py. It does `sys.modules['readline'] = gnureadline` and also lets `sys.__interactivehook__` print a reminder that readline is overridden (and where the override is). This is better than a direct print() which also pops up in non-interactive Python invocations like pip, etc. The override is reasonably foolproof and won't crash even if gnureadline is uninstalled and the override inadvertently remains behind. And best of all, it all works in Python 2.7 too because site customization has been around forever :-P The nice thing about running a module as a script is that it inherently uses the appropriate python interpreter, which avoids confusion. This script could even form the core of a revamped `readline` package that switches between, say, pyreadline and gnureadline based on the platform.
The local package version of `readline.py` is less important now that there is an override mechanism. I kept it though because it still caters for cases like ActiveState Python which has no readline module at all. In those cases it is sufficient to install gnureadline and be done with it, with no need for the override. Add a note to explain this in case I forget again :-) And remind folks not to go down the dark `sys.path` with this module.
This is mostly to support Homebrew Python, which already has a sitecustomize.py in the version-specific Cellar directory. This directory, however, is not returned by `site.getsitepackages()` so the override goes nowhere. Furthermore, the sitecustomize.py is replaced with every patch release of Homebrew Python (e.g. going from 3.11.3 to 3.11.4), requiring a new override invocation even though the `gnureadline` package has not been reinstalled. This is mildly irritating. Make two changes: - Prefer usercustomize to sitecustomize. This override is valid for a minor release of any Python, making it more convenient. - If you do pick sitecustomize, import the module and use its actual `__file__` location to avoid getting the run-around. It is easy to target sitecustomize instead by simply adding the -s flag; i.e. `python -s -m override_readline`. No need for fancy argparsing :-)
Print the full path of the customisation module (it was printed when the file had to be created from scratch but not when it was appended to). Suggest that the file can be deleted if unwanted *only if* we created it in the first place, to prevent possible tears :-) Make the text print banners private to the module.
Explain the use case of fixing tab completion in more detail, based on the new override_readline script. Thanks Steven aka @onlynone for your excellent .pythonrc example script :-) Also, don't mention the defunct easy_install and pyreadline (last commit in 2015) but rather point out that prompt_toolkit works on Windows.
After removing the pyreadline link I discovered its continuation, pyreadline3, and added that instead. It has more downloads than gnureadline :-)
This is in preparation of more improvements to the tests.
Drop simplistic import test. Consolidate all `import readline` checks in a single test (`test_identity_of_readline_module`). The test module gains two script options that will explicitly check whether readline is or is not the same as gnureadline. Use this to check whether `override_readline` works, by first verifying that the modules differ, applying the override and verifying that the modules are the same afterwards. This is all run from within the tests directory to avoid picking up the alternative readline.py module in the project directory.
Python 3.6 is stuck with the previous-generation tox 3, which calls the option `changedir` instead of `change_dir`. Mercifully the tox 4 rewrite supports both versions. Once we drop 3.6 we can return to the modern name.
This combination seems to be unpopular, since the (binary) Python distribution is not on the runner machine and has to be downloaded: Version 3.7 was not found in the local cache Version 3.7 is available for downloading Download from "https://github.com/actions/python-versions/releases/download/3.7.17-5356448435/python-3.7.17-darwin-x64.tar.gz" However, the standard readline module (readline.cpython-37m-darwin.so) cannot be imported because the ncurses library could not be found (it's looking for /usr/local/opt/ncurses/lib/libncursesw.6.dylib instead of the more usual /usr/lib/libncurses.5.4.dylib). I opened an issue on GitHub Actions (actions/setup-python#859), so this option might still come back.
python -m override_readline
scriptpython -m override_readline
I've now verified that @onlynone's pythonrc works as expected. I checked it by replacing the line readline.parse_and_bind('tab: complete') with silly completion code customised for each library: if "libedit" in readline.__doc__:
readline.parse_and_bind("bind e rl_complete")
else:
readline.parse_and_bind("r: complete") So if the This approach only requires the Thanks Steven for the very useful example! |
Over the years we have tried and debated many ways to override the default readline module in Python so that tab completion can be fixed in the standard interactive interpreter (see e.g. #26 and #27).
The original solution was
easy_install readline
: ugly but effective. In the pip era this option has fallen away. The current installation route ofpip install gnureadline
leaves it up to the user to overridesys.modules['readline']
or toimport gnureadline as readline
.The problem is that those tricks have to happen before the interpreter even prints its startup banner. Python 3 roughly starts up like this:
run
site.py
:sys.__interactivehook__
[but DON'T import readline yet]sitecustomize.py
usercustomize.py
import rlcompleter
[imports readline!]output:
PYTHONSTARTUP
sys.__interactivehook__()
[also imports readline fwiw]PYTHONSTARTUP
is a bit late to the party and will need to redo the interactive hook as well (see for example this useful comment). This makes sitecustomize and usercustomize more convenient, as they only need to override readline itself.Here is a rough comparison of the various customization routes:
It feels a bit iffy when an installed package modifies those customization modules but, in mitigation, the user has to push the button and the override tries to explain what it does in some detail.
When you run
python -m override_readline
, the script first targetsusercustomize.py
because then you don't have to update it after every patch release (third number in version triplet), just like thegnureadline
package itself. If you don't have permission or you are in a virtualenv, it falls back tositecustomize.py
. If you want to go straight tositecustomize.py
, simply add the standard-s
flag and runpython -s -m override_readline
.It does
sys.modules['readline'] = gnureadline
and also letssys.__interactivehook__
print a reminder that readline is overridden (and where the override is). This is better than a directprint()
which also pops up in non-interactive Python invocations like pip, etc. Here is an example:The override is reasonably foolproof and won't crash even if
gnureadline
is uninstalled and the override inadvertently remains behind. And best of all, it all works in Python 2.7 too because site customization has been around forever 😛 The nice thing about running a module as a script is that it inherently uses the appropriate python interpreter, which avoids confusion.I've also updated the tests to confirm that
override_readline
works. The original test involving the alternativereadline.py
module is downplayed. I don't want to give anyone bad ideas 😅I also refreshed the main README to mention things currently in use, instead of
easy_install
and the oldpyreadline
, and incorporated @keeely's very useful suggestion.