Skip to main content

django-pyrepl-hacks 🐍

PyPI CI Coverage

A Django shell that uses the new Python REPL, with pyrepl-hacks key bindings.

This package teaches manage.py shell about the new Python REPL (3.13+), adds some additional key bindings to the new REPL, and adds utilities for customizing the REPL.

Supported Python versions 📌

This builds on pyrepl-hacks, which uses Python's internal _pyrepl module. That module is private, so this package supports only the Python versions it has been tested against, and requires-python says which those are. A newer Python will refuse to install it until a release here widens that.

If the new REPL cannot run, the pyrepl interface steps aside and Django moves on to IPython, bpython, or code.interact, exactly as it would if this package were not installed.

Installing 💾

This needs Python 3.13, 3.14, or 3.15 and Django 5.2 or later.

Install with uv:

uv add django-pyrepl-hacks

Or with pip:

python -m pip install django-pyrepl-hacks

Then add it to INSTALLED_APPS:

INSTALLED_APPS = [
    "django_pyrepl_hacks",
    # ...
]

Now manage.py shell will use the new REPL:

$ ./manage.py shell
14 objects imported automatically (use -v 2 for details).

>>>

Default key bindings ⌨️

Key Command What it does
Home home Move to the first character in the input
End end Move to the last character in the input
Alt+M move-to-indentation Move to the first non-space in the line
Shift+Tab dedent Dedent the whole input
Alt+Down move-line-down Swap the current line with the next one
Alt+Up move-line-up Swap the current line with the previous one
Ctrl+Up previous-history Move to the previous history entry
Ctrl+Down next-history Move to the next history entry
Alt+{ previous-paragraph Move to the previous blank line
Alt+} next-paragraph Move to the next blank line

To see what is actually bound in your project, including your own additions:

./manage.py shell --show-bindings

Settings ⚙️

Every setting is optional.

PYREPL_BINDINGS

A dictionary mapping a key to the thing that key should do, layered over the defaults above.

The keys should be human-readable key bindings: "Ctrl+K", "Alt+Up", "Shift+Tab", "F4", "Home", "PageUp", or a sequence like "Ctrl+X Ctrl+R".

The values must be either:

  1. A string representing the name of an installed _pyrepl command
  2. A function, representing a new _pyrepl command
  3. django_pyrepl_hacks.insert("some text") to insert text

Using an existing _pyrepl command

There are about 50 built-in key commands to _pyrepl: show-history, paste-mode, transpose-characters, yank, yank-pop, unix-word-rubout, backward-word, forward-word, and history-search-backward are some of the more useful ones.

pyrepl-hacks adds dedent, move-line-up, move-line-down, move-to-indentation, previous-paragraph, and next-paragraph.

These are the default key bindings (you don't need to set these):

PYREPL_BINDINGS = {
    "Home": "home",
    "End": "end",
    "Alt+M": "move-to-indentation",
    "Shift+Tab": "dedent",
    "Alt+Down": "move-line-down",
    "Alt+Up": "move-line-up",
    "Ctrl+Up": "previous-history",
    "Ctrl+Down": "next-history",
    "Alt+{": "previous-paragraph",
    "Alt+}": "next-paragraph",
}

Note that by default, the home and end keys will move to the first character and the last character in the current code block, which is different from their default behavior in the REPL.

Defining a new command

Setting a PYREPL_BINDINGS value to a function object will register that function as a _pyrepl new command. The new command will be named after the function, with underscores replaced by hyphens, so the sql_of_last_query function below will register as a command named sql-of-last-query.

Functions are called with the pyrepl's reader object, which holds the text you are editing:

# myproject/repl_extensions.py
def sql_of_last_query(reader):
    """Type out the SQL of the most recent query."""
    from django.db import connection

    if connection.queries:
        reader.insert(connection.queries[-1]["sql"])
from myproject.repl_extensions import sql_of_last_query

PYREPL_BINDINGS = {"F9": sql_of_last_query}

A function taking three arguments is called with the event too (just as pyrepl-hacks commands with with_event=True are). This is needed to move the cursor, since movement commands require the event object:

import pyrepl_hacks as repl


def filter_call(reader, event_name, event):
    """Type `User.objects.filter()` and park the cursor inside the parens."""
    reader.insert("User.objects.filter()")
    repl.commands.left(reader, event_name, event)

Lambda functions are not allowed.

Inserting text

Here are two example bindings that insert text:

from django_pyrepl_hacks import insert

PYREPL_BINDINGS = {
    "Ctrl+F": insert("User.objects.filter("),
    "F9": insert("connection.queries[-1]['sql']"),
}

Hitting Ctrl+F will insert User.objects.filter( and hitting F9 will insert connection.queries[-1]['sql'].

Disabling a key binding

Using None as the value to a PYREPL_BINDINGS item will reset that binding back to its _pyrepl default.

If you would prefer the Home and End keys had their default behaviors (moving to the beginning/end of a line instead of the whole block) you could do this:

PYREPL_BINDINGS = {
    "Home": None,
    "End": None,
}

PYREPL_USE_DEFAULT_BINDINGS

Set to False to turn off the default key bindings listed above. The pyrepl-hacks commands they point at are still registered, so you can bind your own keys to them.

PYREPL_THEME

Use this to customize the syntax highlighting colors for the code you type at the REPL.

The keys are the eleven token types that _pyrepl knows about, and the values are colors:

PYREPL_THEME = {
    "string": "green",
    "number": "intense blue",
    "comment": "grey",
    "keyword": "bold magenta",
    "prompt": "bold green",
}

The token types:

Token What it colors
prompt The >>> and ... prompts
keyword def, if, for, import, return
keyword_constant None, True, False
soft_keyword match, case, type
builtin len, print, sorted
comment # like this
string "like this"
number 42, 3.14
op +, -, =, (, ,
definition The name in def name or class Name
reset Everything else, and the default style

The colors are:

Color bold intense background intense background
black yes yes yes yes
blue yes yes yes yes
cyan yes yes yes yes
green yes yes yes yes
grey no no no no
magenta yes yes yes yes
red yes yes yes yes
white yes yes yes yes
yellow yes yes yes yes

Every color works on its own. grey is the exception to the prefixes below: there is no bold grey, and asking for one is an error.

The prefixes are:

Specification Example
plain "red"
bold "bold red"
intense "intense red"
background "background red"
intense background "intense background red"
combined with a comma "background black, bold white"
nothing at all "reset"

Here's a theme that works well with the Solarized Light theme I use in my Terminal:

PYREPL_THEME = {
    "keyword": "green",
    "builtin": "blue",
    "comment": "intense blue",
    "string": "cyan",
    "number": "cyan",
    "definition": "blue",
    "soft_keyword": "bold green",
    "op": "intense green",
    "reset": "reset, intense green",
}

Any value you leave out keeps the REPL's default color.

Named themes (PYREPL_THEME = "solarized-light") are not (yet) implemented.

This setting needs Python 3.14 or later. On an older Python it is ignored and the REPL keeps its own colors, so a project running on both versions can set it once without breaking the shell for anyone. manage.py check warns that it is doing nothing there.

PYREPL_SETUP

This is an escape hatch: a place to put code that should run when the REPL starts and nowhere else.

This is the import path of a function, called once the REPL is configured and just before it starts:

PYREPL_SETUP = "myproject.repl.setup"

An import path rather than the function itself, because settings.py is read by every process you run and this is code only the REPL needs. To do more than one thing, call them from that one function.

Below are some example uses for that function that PYREPL_SETUP points to.

Indicating the environment in your prompt

Here the management shell prompt would become prod>>> and prod... when settings.DEBUG isn't True:

def setup():
    import sys
    from django.conf import settings

    if not settings.DEBUG:
        sys.ps1, sys.ps2 = "prod>>> ", "prod... "

A custom banner

To print something each time the REPL launches:

def setup():
    import django
    from django.db import connections

    database = connections["default"].settings_dict["NAME"]
    print(f"Django {django.get_version()} | {database}")

Checks ✅

manage.py check validates the PYREPL_* settings it can, so a mistake turns up before you are staring at a broken prompt.

Everything it reports is a warning rather than an error.

There are four mistakes it cannot catch:

  1. a command name that does not exist
  2. a key combination that cannot be spelled
  3. a PYREPL_SETUP path that does not import, or does not point at a callable
  4. a color name that does not exist

The first two need a REPL reader, and building one needs a terminal that manage.py check may not have. The third would mean importing your hook into every migrate, which is what naming it as a path exists to avoid.

All four are caught when the shell starts, and reported against the setting they came from:

$ ./manage.py shell
CommandError: Could not set up the REPL: PYREPL_BINDINGS['Ctrl+G'] is 'hom', which is not a command. Did you mean 'home'?

manage.py shell --show-bindings is the easiest way to check your configuration, since it resolves the same settings without starting a REPL.

Contributing 🤝

See CONTRIBUTING.md.

Metadata

Release files for django-pyrepl-hacks 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-pyrepl-hacks 0.2.0
File Size Uploaded Provenance Actions
django_pyrepl_hacks-0.2.0.tar.gz 20.6 kB Without provenance Details for django_pyrepl_hacks-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-pyrepl-hacks 0.2.0
File Interpreter ABI Platform Actions
django_pyrepl_hacks-0.2.0-py3-none-any.whl Python 3 none any Details for django_pyrepl_hacks-0.2.0-py3-none-any.whl

Total release size: 38.5 kB

Release files / django_pyrepl_hacks-0.2.0.tar.gz

Download URL django_pyrepl_hacks-0.2.0.tar.gz
Size 20.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1b93e2bf9a8f9666346a5a818a756c1d2e5ec2331b293a9c0314e47f8b1f2a94
BLAKE2b-256 checksum
How to use checksums
50177f83dcc0a308d234d4d898f5c0b286b73d8b05afbd0bf67a49c823f44dff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / django_pyrepl_hacks-0.2.0-py3-none-any.whl

Download URL django_pyrepl_hacks-0.2.0-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1fa0f1a3d252b257d2cfae967372979012f4c12600247e14f6dd73d675fd373b
BLAKE2b-256 checksum
How to use checksums
de6e22977adc4e08a6b102a50263076f56ce26d4bbcef69cd86e4dbf092ebab8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for django-pyrepl-hacks 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page