diff --git a/.config/bpython/config b/.config/bpython/config new file mode 100644 index 0000000..cc6f7ab --- /dev/null +++ b/.config/bpython/config @@ -0,0 +1,8 @@ +[general] + +# Make `bpython` and `python` share their history +# (Note: `$XDG_STATE_HOME` is not expanded) +hist_file = ~/.local/state/python/history + +# No limit +hist_length = 999999 diff --git a/.config/git/ignore b/.config/git/ignore index a0ed808..5e96e2b 100644 --- a/.config/git/ignore +++ b/.config/git/ignore @@ -4,3 +4,11 @@ *.orig *.temp *.tmp + +# Python +__pycache__/ +*.py[cod] +.python-version +.venv/ +venv/ +.env diff --git a/.config/pypoetry/config.toml b/.config/pypoetry/config.toml new file mode 100644 index 0000000..a815e69 --- /dev/null +++ b/.config/pypoetry/config.toml @@ -0,0 +1,9 @@ +[virtualenvs] + +create = true +in-project = true # .venv/ inside the project, not a central cache + + +[installer] + +parallel = true diff --git a/.config/python/startup.py b/.config/python/startup.py new file mode 100644 index 0000000..03bbe77 --- /dev/null +++ b/.config/python/startup.py @@ -0,0 +1,54 @@ +"""Move Python's history file to "$XDG_STATE_HOME/python/history". + +Python 3.13+ honors the `$PYTHON_HISTORY` environment variable, +so this file is only needed for older interpreters (e.g., via `pyenv`). + +`$PYTHONSTARTUP` is executed in the REPL's own namespace +=> Everything is wrapped in a function that is deleted afterwards + to avoid polluting the prompt with temporary names +""" + +import sys + + +def _setup_history(): + if sys.version_info >= (3, 13): + return # `$PYTHON_HISTORY` handles it + + try: + import readline # because it is not in every build + except ImportError: + return + + import atexit + import os + + state_home = os.environ.get("XDG_STATE_HOME") or os.path.join( + os.path.expanduser("~"), ".local", "state" + ) + history_file = os.path.join(state_home, "python", "history") + + os.makedirs(os.path.dirname(history_file), exist_ok=True) + if os.path.isdir(history_file): + raise OSError(history_file + " must not be a directory") + + try: + readline.read_history_file(history_file) + except OSError: # `$PYTHON_HISTORY` is non-existent or unreadable + pass + + readline.set_auto_history(True) + readline.set_history_length(999999) + + def write_history(): + try: + readline.write_history_file(history_file) + except OSError: + pass + + atexit.register(write_history) # Keep a reference to `write_history` + + +_setup_history() + +del _setup_history diff --git a/.config/shell/aliases b/.config/shell/aliases index f9ccda0..4d662da 100644 --- a/.config/shell/aliases +++ b/.config/shell/aliases @@ -136,3 +136,31 @@ fi alias grep='grep --exclude-dir=.git' + + + +# Make working with Python more convenient + +alias py='python3' + +ipy() { + if command -v ipython >/dev/null 2>&1; then + ipython "$@" + elif command -v ipython3 >/dev/null 2>&1; then + ipython3 "$@" + else + echo "ipython not installed" >&2 + return 1 + fi +} + +if _command_exists poetry; then + alias pr='poetry run' +fi + +if _command_exists pyenv; then + alias pyvenvs='pyenv virtualenvs --bare --skip-aliases' + alias pyver='pyenv version' + alias pyvers='pyenv versions --skip-aliases' + alias pywhich='pyenv which' +fi diff --git a/.config/shell/env b/.config/shell/env index b871a11..4b4ab2f 100644 --- a/.config/shell/env +++ b/.config/shell/env @@ -49,3 +49,7 @@ unset _bat export BAT_CONFIG_PATH="$XDG_CONFIG_HOME/bat/config" export LESSHISTFILE="$XDG_STATE_HOME/less/history" export PSQLRC="$XDG_CONFIG_HOME/psql/psqlrc" +export PYENV_ROOT="$XDG_DATA_HOME/pyenv" +export PYTHONPYCACHEPREFIX="$XDG_CACHE_HOME/python" +export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/startup.py" +export PYTHON_HISTORY="$XDG_STATE_HOME/python/history" diff --git a/.local/state/python/.gitkeep b/.local/state/python/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.profile b/.profile index d8b1e50..495284c 100644 --- a/.profile +++ b/.profile @@ -45,6 +45,12 @@ _prepend_to_path "$HOME/.local/bin" [ -f "$HOME/.config/shell/env" ] && . "$HOME/.config/shell/env" +# Must be between sourcing ~/.config/shell/env and ~/.config/shell/aliases +# because the first defines the `pyenv` related environment variables +# while the second creates `pyenv` related aliases +_prepend_to_path "$PYENV_ROOT/bin" + + [ -f "$HOME/.config/shell/aliases" ] && . "$HOME/.config/shell/aliases" @@ -62,5 +68,20 @@ fi # which then also ensures that this file is sourced +if [ -z "${PYENV_SHELL:-}" ] && _command_exists pyenv; then + + eval "$(pyenv init -)" || echo "WARN: pyenv init failed" >&2 + + case $- in + *i*) + if [ -d "$PYENV_ROOT/plugins/pyenv-virtualenv" ]; then + eval "$(pyenv virtualenv-init -)" || true + fi + ;; + esac + +fi + + # When everything is loaded, show a little welcome message case $- in *i*) [ -f "$HOME/.config/shell/welcome" ] && . "$HOME/.config/shell/welcome";; esac