django-read-only


Namedjango-read-only JSON
Version 1.15.0 PyPI version JSON
download
home_pagehttps://github.com/adamchainz/django-read-only
SummaryDisable Django database writes.
upload_time2023-10-11 09:36:38
maintainer
docs_urlNone
authorAdam Johnson
requires_python>=3.8
licenseMIT
keywords django
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ================
django-read-only
================

.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-read-only/main.yml?branch=main&style=for-the-badge
   :target: https://github.com/adamchainz/django-read-only/actions?workflow=CI

.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
  :target: https://github.com/adamchainz/django-read-only/actions?workflow=CI

.. image:: https://img.shields.io/pypi/v/django-read-only.svg?style=for-the-badge
   :target: https://pypi.org/project/django-read-only/

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
   :target: https://github.com/psf/black

.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
   :target: https://github.com/pre-commit/pre-commit
   :alt: pre-commit

Disable Django database writes.

Requirements
------------

Python 3.8 to 3.12 supported.

Django 3.2 to 5.0 supported.

----

**Want to work smarter and faster?**
Check out my book `Boost Your Django DX <https://adamchainz.gumroad.com/l/byddx>`__ which covers django-read-only, IPython, and many other tools.

----

Installation
------------

Install with **pip**:

.. code-block:: sh

    python -m pip install django-read-only

Then add to your installed apps:

.. code-block:: python

    INSTALLED_APPS = [
        ...,
        "django_read_only",
        ...,
    ]

Usage
-----

In your settings file, set ``DJANGO_READ_ONLY`` to ``True`` and all data modification queries will cause an exception:

.. code-block:: console

    $ DJANGO_READ_ONLY=1 python manage.py shell
    ...
    >>> User.objects.create_user(username="hacker", password="hunter2")
    ...
    DjangoReadOnlyError(...)

For convenience, you can also control this with the ``DJANGO_READ_ONLY`` environment variable, which will count as ``True`` if set to anything but the empty string.
The setting takes precedence over the environment variable.

During a session with ``DJANGO_READ_ONLY`` set on, you can re-enable writes by calling ``enable_writes()``:

.. code-block:: pycon

    >>> import django_read_only
    >>> django_read_only.enable_writes()

Writes can be disabled with ``disable_writes()``:

.. code-block:: pycon

    >>> django_read_only.disable_writes()

To temporarily allow writes, use the ``temp_writes()`` context manager / decorator:

.. code-block:: pycon

    >>> with django_read_only.temp_writes():
    ...     User.objects.create_user(...)
    ...

Note that writes being enabled/disabled is global state, affecting all threads and asynchronous coroutines.

Recommended Setup
-----------------

Set read-only mode on in your production environment, and maybe staging, during interactive sessions.
This can be done by setting the ``DJANGO_READ_ONLY`` environment variable in the shell profile file (``bashrc``, ``zshrc``, etc.) of the system’s user account.
This way developers performing exploratory queries can’t accidentally make changes, but writes will remain enabled for non-shell processes like your WSGI server.

With this setup, developers can also run management commands with writes enabled by setting the environment variable before the command:

.. code-block:: console

    $ DJANGO_READ_ONLY= python manage.py clearsessions

Some deployment platforms don’t allow you to customize your shell profile files.
In this case, you will need to find a way to detect shell mode from within your settings file.

For example, on Heroku there’s the ``DYNO`` environment variable (`docs <https://devcenter.heroku.com/articles/dynos#local-environment-variables>`__) to identify the current virtual machine.
It starts with “run.” for interactive sessions.
You can use this to enable read-only mode in your settings file like so:

.. code-block:: python

    if os.environ.get("DYNO", "").startswith("run."):
        DJANGO_READ_ONLY = bool(os.environ.get("DJANGO_READ_ONLY", "1"))
    else:
        DJANGO_READ_ONLY = False

IPython Extension
-----------------

django-read-only also works as an IPython extension for quick access to enable/disable read-only mode.
Load it with:

.. code-block:: ipython

    In [1]: %load_ext django_read_only

You can have the extension always load by setting it up to your `IPython configuration file <https://ipython.readthedocs.io/en/stable/config/intro.html>`__:

.. code-block:: python

    c.InteractiveShellApp.extensions.append("django_read_only")

When loaded, use the ``%read_only`` line magic to disable or enable read-only mode:

.. code-block:: ipython

    In [2]: %read_only off
    Write queries enabled.

    In [3]: %read_only on
    Write queries disabled.

This reduces the amount of typing needed to disable read-only mode.

How it Works
------------

The most accurate way to prevent writes is to connect as a separate database user with only read permission.
However, this has limitations - Django doesn’t support modifying the ``DATABASES`` setting live, so sessions would not be able to temporarily allow writes.

Instead, django-read-only uses `always installed database instrumentation <https://adamj.eu/tech/2020/07/23/how-to-make-always-installed-django-database-instrumentation/>`__ to inspect executed queries and only allow those which look like reads.
It uses a “fail closed” philosophy, so anything unknown will fail, which should be fairly reasonable.

Because django-read-only uses Django database instrumentation, it cannot block queries running through the underlying database connection (accesses through ``django.db.connection.connection``), and it cannot filter operations within stored procedures (which use ``connection.callproc()``).
These are very rare in practice though, so django-read-only’s method works well for most projects.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/adamchainz/django-read-only",
    "name": "django-read-only",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "Django",
    "author": "Adam Johnson",
    "author_email": "me@adamj.eu",
    "download_url": "https://files.pythonhosted.org/packages/03/1f/d5569c1b6eda6c610ac2b90a57d6e86410651c2b79ad6af5f94cefbaf799/django_read_only-1.15.0.tar.gz",
    "platform": null,
    "description": "================\ndjango-read-only\n================\n\n.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-read-only/main.yml?branch=main&style=for-the-badge\n   :target: https://github.com/adamchainz/django-read-only/actions?workflow=CI\n\n.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge\n  :target: https://github.com/adamchainz/django-read-only/actions?workflow=CI\n\n.. image:: https://img.shields.io/pypi/v/django-read-only.svg?style=for-the-badge\n   :target: https://pypi.org/project/django-read-only/\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge\n   :target: https://github.com/psf/black\n\n.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge\n   :target: https://github.com/pre-commit/pre-commit\n   :alt: pre-commit\n\nDisable Django database writes.\n\nRequirements\n------------\n\nPython 3.8 to 3.12 supported.\n\nDjango 3.2 to 5.0 supported.\n\n----\n\n**Want to work smarter and faster?**\nCheck out my book `Boost Your Django DX <https://adamchainz.gumroad.com/l/byddx>`__ which covers django-read-only, IPython, and many other tools.\n\n----\n\nInstallation\n------------\n\nInstall with **pip**:\n\n.. code-block:: sh\n\n    python -m pip install django-read-only\n\nThen add to your installed apps:\n\n.. code-block:: python\n\n    INSTALLED_APPS = [\n        ...,\n        \"django_read_only\",\n        ...,\n    ]\n\nUsage\n-----\n\nIn your settings file, set ``DJANGO_READ_ONLY`` to ``True`` and all data modification queries will cause an exception:\n\n.. code-block:: console\n\n    $ DJANGO_READ_ONLY=1 python manage.py shell\n    ...\n    >>> User.objects.create_user(username=\"hacker\", password=\"hunter2\")\n    ...\n    DjangoReadOnlyError(...)\n\nFor convenience, you can also control this with the ``DJANGO_READ_ONLY`` environment variable, which will count as ``True`` if set to anything but the empty string.\nThe setting takes precedence over the environment variable.\n\nDuring a session with ``DJANGO_READ_ONLY`` set on, you can re-enable writes by calling ``enable_writes()``:\n\n.. code-block:: pycon\n\n    >>> import django_read_only\n    >>> django_read_only.enable_writes()\n\nWrites can be disabled with ``disable_writes()``:\n\n.. code-block:: pycon\n\n    >>> django_read_only.disable_writes()\n\nTo temporarily allow writes, use the ``temp_writes()`` context manager / decorator:\n\n.. code-block:: pycon\n\n    >>> with django_read_only.temp_writes():\n    ...     User.objects.create_user(...)\n    ...\n\nNote that writes being enabled/disabled is global state, affecting all threads and asynchronous coroutines.\n\nRecommended Setup\n-----------------\n\nSet read-only mode on in your production environment, and maybe staging, during interactive sessions.\nThis can be done by setting the ``DJANGO_READ_ONLY`` environment variable in the shell profile file (``bashrc``, ``zshrc``, etc.) of the system\u2019s user account.\nThis way developers performing exploratory queries can\u2019t accidentally make changes, but writes will remain enabled for non-shell processes like your WSGI server.\n\nWith this setup, developers can also run management commands with writes enabled by setting the environment variable before the command:\n\n.. code-block:: console\n\n    $ DJANGO_READ_ONLY= python manage.py clearsessions\n\nSome deployment platforms don\u2019t allow you to customize your shell profile files.\nIn this case, you will need to find a way to detect shell mode from within your settings file.\n\nFor example, on Heroku there\u2019s the ``DYNO`` environment variable (`docs <https://devcenter.heroku.com/articles/dynos#local-environment-variables>`__) to identify the current virtual machine.\nIt starts with \u201crun.\u201d for interactive sessions.\nYou can use this to enable read-only mode in your settings file like so:\n\n.. code-block:: python\n\n    if os.environ.get(\"DYNO\", \"\").startswith(\"run.\"):\n        DJANGO_READ_ONLY = bool(os.environ.get(\"DJANGO_READ_ONLY\", \"1\"))\n    else:\n        DJANGO_READ_ONLY = False\n\nIPython Extension\n-----------------\n\ndjango-read-only also works as an IPython extension for quick access to enable/disable read-only mode.\nLoad it with:\n\n.. code-block:: ipython\n\n    In [1]: %load_ext django_read_only\n\nYou can have the extension always load by setting it up to your `IPython configuration file <https://ipython.readthedocs.io/en/stable/config/intro.html>`__:\n\n.. code-block:: python\n\n    c.InteractiveShellApp.extensions.append(\"django_read_only\")\n\nWhen loaded, use the ``%read_only`` line magic to disable or enable read-only mode:\n\n.. code-block:: ipython\n\n    In [2]: %read_only off\n    Write queries enabled.\n\n    In [3]: %read_only on\n    Write queries disabled.\n\nThis reduces the amount of typing needed to disable read-only mode.\n\nHow it Works\n------------\n\nThe most accurate way to prevent writes is to connect as a separate database user with only read permission.\nHowever, this has limitations - Django doesn\u2019t support modifying the ``DATABASES`` setting live, so sessions would not be able to temporarily allow writes.\n\nInstead, django-read-only uses `always installed database instrumentation <https://adamj.eu/tech/2020/07/23/how-to-make-always-installed-django-database-instrumentation/>`__ to inspect executed queries and only allow those which look like reads.\nIt uses a \u201cfail closed\u201d philosophy, so anything unknown will fail, which should be fairly reasonable.\n\nBecause django-read-only uses Django database instrumentation, it cannot block queries running through the underlying database connection (accesses through ``django.db.connection.connection``), and it cannot filter operations within stored procedures (which use ``connection.callproc()``).\nThese are very rare in practice though, so django-read-only\u2019s method works well for most projects.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Disable Django database writes.",
    "version": "1.15.0",
    "project_urls": {
        "Changelog": "https://github.com/adamchainz/django-read-only/blob/main/CHANGELOG.rst",
        "Homepage": "https://github.com/adamchainz/django-read-only",
        "Mastodon": "https://fosstodon.org/@adamchainz",
        "Twitter": "https://twitter.com/adamchainz"
    },
    "split_keywords": [
        "django"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e9bb74403cb11a8df2cdff5065ed738619eb0ed779cc2cb58ea53fdfdd04fcfc",
                "md5": "1812ac78cf85740a78cf37cfddb326cf",
                "sha256": "42b9fedfcebc2d44d7cf1f5e8a525580f21e4e539d821f4eca87629258316d0e"
            },
            "downloads": -1,
            "filename": "django_read_only-1.15.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "1812ac78cf85740a78cf37cfddb326cf",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 6561,
            "upload_time": "2023-10-11T09:36:37",
            "upload_time_iso_8601": "2023-10-11T09:36:37.190198Z",
            "url": "https://files.pythonhosted.org/packages/e9/bb/74403cb11a8df2cdff5065ed738619eb0ed779cc2cb58ea53fdfdd04fcfc/django_read_only-1.15.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "031fd5569c1b6eda6c610ac2b90a57d6e86410651c2b79ad6af5f94cefbaf799",
                "md5": "62eac6f19d344bf208aa0ec11327c0cc",
                "sha256": "1628f65e13ca7f692e0bfc0df8cefda96a8179b8ae27a03175be318d7dac2c90"
            },
            "downloads": -1,
            "filename": "django_read_only-1.15.0.tar.gz",
            "has_sig": false,
            "md5_digest": "62eac6f19d344bf208aa0ec11327c0cc",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 7736,
            "upload_time": "2023-10-11T09:36:38",
            "upload_time_iso_8601": "2023-10-11T09:36:38.434950Z",
            "url": "https://files.pythonhosted.org/packages/03/1f/d5569c1b6eda6c610ac2b90a57d6e86410651c2b79ad6af5f94cefbaf799/django_read_only-1.15.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-10-11 09:36:38",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "adamchainz",
    "github_project": "django-read-only",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "django-read-only"
}
        
Elapsed time: 0.13946s