Skip to content

Commit

Permalink
[IMP] base: "--shell-file" option override for $PYTHONSTARTUP
Browse files Browse the repository at this point in the history
The $PYTHONSTARTUP env variable can contain a python script that is used
by Python shell at the start of any interactive session.

- fix this feature from being broken in python and ptpython
- include a new --shell-file= shell parameter to override the env
  variable $PYTHONSTARTUP
- group the two shell parameters in a new options group
- remove custom python shells' banners from the start of the session
- shell options can now be saved in the configuration file.

Python docs: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONSTARTUP

Documentation PR: odoo/documentation#11334
task-4306704
  • Loading branch information
lordkrandel committed Nov 6, 2024
1 parent 47a33b5 commit fa86eb8
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 19 deletions.
2 changes: 2 additions & 0 deletions odoo/addons/base/tests/config/save_posix.conf
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ reportgz = False
screencasts =
screenshots = /tmp/odoo_tests
server_wide_modules = base,rpc,web
shell_file =
shell_interface = None
smtp_password = False
smtp_port = 25
smtp_server = localhost
Expand Down
9 changes: 7 additions & 2 deletions odoo/addons/base/tests/test_configmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,12 @@ def test_01_default_config(self):
# security
'list_db': True,

# shell
'shell_interface': None,
'shell_file': '',

# advanced
'dev_mode': [],
'shell_interface': None,
'stop_after_init': False,
'osv_memory_count_limit': 0,
'transient_age_limit': 1.0,
Expand Down Expand Up @@ -236,7 +239,8 @@ def test_02_default_config_file(self):

# advanced
'dev_mode': [], # blacklist for save, ignored from the config file
'shell_interface': 'ipython', # blacklist for save, read from the config file
'shell_interface': 'ipython',
'shell_file': '',
'stop_after_init': True, # blacklist for save, read from the config file
'osv_memory_count_limit': 71,
'transient_age_limit': 4.0,
Expand Down Expand Up @@ -357,6 +361,7 @@ def test_04_odoo16_config_file(self):
'db_maxconn_gevent': False,
'db_replica_host': False,
'db_replica_port': False,
'shell_file': '',
'geoip_country_db': '/usr/share/GeoIP/GeoLite2-Country.mmdb',
'from_filter': False,
'gevent_port': 8072,
Expand Down
39 changes: 25 additions & 14 deletions odoo/cli/shell.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# ruff: noqa: PLC0415

import code
import logging
import os
Expand Down Expand Up @@ -39,15 +40,15 @@ def raise_keyboard_interrupt(*a):


class Console(code.InteractiveConsole):
def __init__(self, locals=None, filename="<console>"):
code.InteractiveConsole.__init__(self, locals, filename)
def __init__(self, local_vars=None, filename="<console>"):
code.InteractiveConsole.__init__(self, locals=local_vars, filename=filename)
try:
import readline
import rlcompleter
except ImportError:
print('readline or rlcompleter not available, autocomplete disabled.')
else:
readline.set_completer(rlcompleter.Completer(locals).complete)
readline.set_completer(rlcompleter.Completer(local_vars).complete)
readline.parse_and_bind("tab: complete")


Expand All @@ -72,6 +73,8 @@ def console(self, local_vars):
for i in sorted(local_vars):
print('%s: %s' % (i, local_vars[i]))

pythonstartup = config.options.get('shell_file') or os.environ.get('PYTHONSTARTUP')

preferred_interface = config.options.get('shell_interface')
if preferred_interface:
shells_to_try = [preferred_interface, 'python']
Expand All @@ -80,27 +83,35 @@ def console(self, local_vars):

for shell in shells_to_try:
try:
return getattr(self, shell)(local_vars)
return getattr(self, shell)(local_vars, pythonstartup)
except ImportError:
pass
except Exception:
_logger.warning("Could not start '%s' shell." % shell)
_logger.debug("Shell error:", exc_info=True)

def ipython(self, local_vars):
def ipython(self, local_vars, pythonstartup=None):
from IPython import start_ipython
start_ipython(argv=[], user_ns=local_vars)
argv = (
["--TerminalIPythonApp.display_banner=False"]
+ [f"--TerminalIPythonApp.exec_files={pythonstartup}"] if pythonstartup else []
)
start_ipython(argv=argv, user_ns=local_vars)

def ptpython(self, local_vars):
def ptpython(self, local_vars, pythonstartup=None):
from ptpython.repl import embed
embed({}, local_vars)
embed({}, local_vars, startup_paths=[pythonstartup] if pythonstartup else False)

def bpython(self, local_vars):
def bpython(self, local_vars, pythonstartup=None):
from bpython import embed
embed(local_vars)

def python(self, local_vars):
Console(locals=local_vars).interact()
embed(local_vars, args=['-q', '-i', pythonstartup] if pythonstartup else None)

def python(self, local_vars, pythonstartup=None):
console = Console(local_vars)
if pythonstartup:
with open(pythonstartup, encoding="utf-8") as f:
console.runsource(f.read(), filename=pythonstartup, symbol="exec")
console.interact(banner='')

def shell(self, dbname):
local_vars = {
Expand Down
16 changes: 13 additions & 3 deletions odoo/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,6 @@ def _build_cli(self):
group.add_option('--dev', dest='dev_mode', type="string", file_exportable=False,
help="Enable developer mode. Param: List of options separated by comma. "
"Options : all, reload, qweb, xml")
group.add_option('--shell-interface', dest='shell_interface', type="string", file_exportable=False,
help="Specify a preferred REPL to use in shell mode. Supported REPLs are: "
"[ipython|ptpython|bpython|python]")
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False, file_exportable=False,
help="stop the server after its initialization")
group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=0,
Expand All @@ -335,6 +332,19 @@ def _build_cli(self):
help="Absolute path to the GeoIP Country database file.")
parser.add_option_group(group)

group = optparse.OptionGroup(parser, "Shell options")
group.add_option(
'--shell-file', dest='shell_file', type="string", my_default='',
help="Specify a python script to be run after the start of the shell. "
"Overrides the env variable PYTHONSTARTUP."
)
group.add_option(
'--shell-interface', dest='shell_interface', type="string",
help="Specify a preferred REPL to use in shell mode. "
"Supported REPLs are: [ipython|ptpython|bpython|python]"
)
parser.add_option_group(group)

if os.name == 'posix':
group = optparse.OptionGroup(parser, "Multiprocessing options")
# TODO sensible default for the three following limits.
Expand Down

0 comments on commit fa86eb8

Please sign in to comment.