{ "info": { "author": "Saul Shanabrook", "author_email": "s.shanabrook@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries" ], "description": "django-dumper \n============================\n\n.. image:: https://img.shields.io/pypi/v/django-dumper.svg?style=flat-square :target: https://pypi.python.org/pypi/django-dumper .. image:: https://img.shields.io/travis/saulshanabrook/django-dumper.svg?style=flat-square :target: https://travis-ci.org/saulshanabrook/django-dumper/\n\n``django-dumper`` provides full site caching, similar to Django's,\nalong with path based invalidation based on model saves.\nIt will cache every page that is not in ``DUMPER_PATH_IGNORE_REGEX``\n**forever**. So for it to be effective, all of your pages must\nbe invalidated when neccesary, by specifying in your models\nwhich paths should be invalidated those models are saved.\n\n\nWhy...?\n-------\n``django-dumper`` was created to scratch a rather specific itch. I was having\ntrouble reducing load times on pages where there were lots of images. By\ndefault, if you want to render an image's url, width, and height, in a template then\nthat hits the storage backend three times per image. With a remote backend,\nlike S3, this creates long and unreliable page load times. If you are smarter\nand create `cached height and width fields`_, then you can reduce this to one\nhit. This is still not ideal for a page with 100+ images. So I thought, the only\ntime these images ever change, is when a model saves. And then I started\nthinking, actually the only time any of my pages changed was when a model\nsaved. Of course, I still wanted my page load times to be as low as possible\nbefore caching, but why would i re-render these pages on every request, if\nthey would be identical for every visitor, until someone changed a model?\n\nSo I set about to build an app that would allow me to do just that. It\nwould cache the full content of each response indefinitely. It would then\ninvalidate certain responses, based on their paths, whenever a model was saved.\nFor instance, if the ``/ice-cream/`` page displays links to each flavor and\n``/ice-cream//`` has detailed information on a flavor, then\nevery time a flavor is saved, it should not only invalidate its specific detail\npage, but also the general list page. This is definitely a brute force approach,\nbut it makes sense to me because it is *safe*. You might over invalidate, but,\nif setup correctly, you will never have stale caches.\n\nThis is by no means an all purpose caching app. Every page rendered by your site\nmust be determined only by models. Detail and list views are examples of pages\ndetermined by models. Also, if your site differentiates at based on request\nheaders (cookies, languages, etc...) then this will not work, because it will\nserve the same version to all visitors.\n\n.. _cached height and width fields: https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ImageField.height_field\n\n\n\nInstallation\n------------\nInstallation is as easy as::\n\n pip install django-dumper\n\n\nSetup\n-----\nConfiguration is similar to Django's `per site`_ cache.\n\nYou\u2019ll need to add ``'dumper.middleware.cache.UpdateCacheMiddleware'`` and\n``'dumper.middleware.cache.FetchFromCacheMiddleware'`` to your\n``MIDDLEWARE_CLASSES setting``, as in this example:\n\n.. code-block:: python\n\n MIDDLEWARE_CLASSES = (\n 'dumper.middleware.UpdateCacheMiddleware',\n 'django.middleware.common.CommonMiddleware',\n 'dumper.middleware.FetchFromCacheMiddleware',\n )\n\nThen, add the following optional settings to your Django settings file:\n\n1. ``DUMPER_CACHE_ALIAS`` \u2013 The cache alias to use for storage. Defaults\n ``'default'``\n2. ``DUMPER_KEY_PREFIX`` \u2013 If the cache is shared across multiple sites\n using the same Django installation, set this to the name of the site,\n or some other string that is unique to this Django instance, to\n prevent key collisions. Defaults to ``'dumper.cached_path.'``.\n3. ``DUMPER_PATH_IGNORE_REGEX`` \u2013 If matched on the path, then\n these pages will not be cached. By default it won't cached the admin\n ``^/admin/``\n\n.. hint:: If you use Django Grappelli, then you shouldn't be caching\n any paths under `/grappelli/` and if you are serving media or static from\n your app, you should ignore those as well.\n\n .. code-block:: python\n\n DUMPER_PATH_IGNORE_REGEX = r'^/(?:(?:admin)|(?:grappelli)|(?:media))/'\n\n.. _per site: https://docs.djangoproject.com/en/dev/topics/cache/#the-per-site-cache\n\nUsage\n-----\nTo invalidate certain paths on a model save and delete, register that model\nusing ``dumper.register``. It will invalidate every path returned by the\n``dependent_paths`` method.\n\n.. code-block:: python\n\n from django.db import models\n\n import dumper\n\n\n class IceCream(models.Model):\n slug = models.CharField(max_length=200)\n\n def get_absolute_url(self):\n return '/' + self.slug\n\n def dependent_paths(self):\n '''Returns a list of paths to invalidate when this model is updated'''\n return [self.get_absolute_url()]\n\n dumper.register(IceCream)\n\n``dependent_paths`` can also returns the paths of related objects to invalidate\nthem as well. For instance if each ``IceCream`` had some related ``Sizes``\nthen if one of those sizes is modified, that should invalidate the ``IceCream``\nas well.\n\n\n.. code-block:: python\n\n from django.db import models\n\n import dumper\n\n\n class IceCream(models.Model):\n slug = models.CharField(max_length=200)\n sizes = models.ManyToManyField(Size, related_name='ice_creams')\n\n def get_absolute_url(self):\n return '/' + self.slug\n\n def dependent_paths(self):\n '''Returns a list of paths to invalidate when this model is updated'''\n return [self.get_absolute_url()]\n\n\n class Size(models.Model):\n slug = models.CharField(max_length=200)\n\n def get_absolute_url(self):\n return '/' + self.slug\n\n def dependent_paths(self):\n for ice_cream in self.ice_creams:\n yield ice_cream.get_absolute_url()\n yield self.get_absolute_url()\n\n dumper.register(IceCream)\n dumper.register(Size)\n\n\nDebugging\n---------\nThe `dumper` package has `DEBUG` logging in place for the midleware\nand for the invalidation. To enable this, just make sure that\nany logs coming from `dumper` with the level `DEBUG` are shown.\n\nThe simplest way to do this would be to this in your `settings.py`\n\n.. code-block:: python\n\n LOGGING = {\n 'version': 1,\n 'disable_existing_loggers': False,\n 'formatters': {\n 'name': {\n 'format': '%(name)s: %(message)s'\n },\n },\n 'handlers': {\n 'console': {\n 'level': 'DEBUG',\n 'class': 'logging.StreamHandler',\n 'formatter': 'name'\n },\n },\n 'loggers': {\n 'dumper': {\n 'level': 'DEBUG',\n 'handlers': ['console', ]\n }\n }\n }\n\n\n\nAdvice\n------\nI would recommend enabling `ETags`_. That way the whole response\nwon't have to be sent to the user, only the header, if the ETAG is the same.\n\n.. _ETags: https://docs.djangoproject.com/en/dev/ref/settings/#use-etags\n\nThe Django document ion does not cohesively describe how your middleware\nshould be ordered, however `this stack overflow`_ discussion does a fine job.\n\n.. _this stack overflow: http://stackoverflow.com/questions/4632323/practical-rules-for-django-middleware-ordering#question\n\n\nInternals\n---------\n\nCache Middleware |dumper/middleware.py|_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nMy caching is based off of Django's `per site cache`_, but much simpler.\nOriginally I just used their cache, but this greatly complicated my code\nand made it harder to understand. This is because their cache\n`creates different cached versions`_. for the same URL based on the ``Vary`` HTML header.\nIt is much more complicated to implement path based invalidation, if other things\nbesides the path are being use to generate the cache key. For instance, when I was\nsupporting the Django middleware I had to figure out a way to delete every cached\nversion of the path.\n\nIf your pages do vary based on anything besides the path and HTTP method,\nthen you should not cache them with ``django-dumper``. Either ignore them\nwith the ``DUMPER_PATH_IGNORE_REGEX`` setting or don't use the project at all\nif all of your pages fall under this category.\n\n.. |dumper/middleware.py| replace:: ``dumper/middleware.py``\n.. _dumper/middleware.py: https://github.com/saulshanabrook/django-dumper/blob/master/dumper/middleware.py\n.. _per site cache: https://docs.djangoproject.com/en/dev/topics/cache/#the-per-site-cache\n.. _creates different cached versions: https://github.com/django/django/blob/master/django/middleware/cache.py#L38-L39\n\n\nInvalidate Paths |dumper/invalidation.py|_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nIn order to invalidate a model when it saves, we get the path's that should\nbe invalidated from the model, and then remove the cache keys that correspond\nto those paths. Each cache key is made up of a path plus a HTTP method.\n\n.. |dumper/invalidation.py| replace:: ``dumper/invalidation.py``\n.. _dumper/invalidation.py: https://github.com/saulshanabrook/django-dumper/blob/master/dumper/invalidation.py\n\n\nInvalidating on Model Saves: |dumper/site.py|_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nWhen you register a model a invalidation function to three signals.\nThat function gets the paths from the model and then uses |dumper/invalidation.py|_\nto delete them. The three signals it registers with are ``post_save``, ``pre_delete``,\nand ``m2m_changed``. The last signal is called whenever any member that relationship\nis added, deleted, or changed. It most likely calls the\ninvalidation function more than once if a many to many relationship is changed,\nbut is harmless, besides the slight performance hit from hitting the cache backend.\n\n.. |dumper/site.py| replace:: ``dumper/site.py``\n.. _dumper/site.py: https://github.com/saulshanabrook/django-dumper/blob/master/dumper/site.py\n\n\nContributing\n------------\n\nIf you find issues or would like to see a feature suppored, head over to\nthe `issues section` and report it. Don't be agraid, go ahead, do it!\n\n.. _issues section: https://github.com/saulshanabrook/django-dumper/issues\n\nTo contribute code in any form, fork the repository and clone it locally.\nCreate a new branch for your feature::\n\n git commit -b feature/whatever-you-like\n\nThen make sure all the tests past (and write new ones for any new features)\n\nWith Docker Compose and Docker::\n\n docker-compose run tests\n # run `docker-compose build` if you change the required packages before testing again\n\nNormally::\n\n pip install -e .\n pip install -r requirements-dev.txt\n django-admin.py test --settings=test.settings\n\nCheck if the README.rst looks right::\n\n restview --long-description\n\nThen push the finished feature to github and open a pull request form the branch.\n\nNew Release\n^^^^^^^^^^^\nTo create a new release:\n\n1. Add changes to ``CHANGES.txt``\n2. Change version in ``setup.py``\n3. ``python setup.py register``\n4. ``python setup.py sdist upload``\n\n\n.. image:: https://d2weczhvl823v0.cloudfront.net/saulshanabrook/django-dumper/trend.png\n :alt: Bitdeli badge\n :target: https://bitdeli.com/free", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://www.github.com/saulshanabrook/django-dumper", "keywords": null, "license": "LICENSE.txt", "maintainer": null, "maintainer_email": null, "name": "django-dumper", "package_url": "https://pypi.org/project/django-dumper/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/django-dumper/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://www.github.com/saulshanabrook/django-dumper" }, "release_url": "https://pypi.org/project/django-dumper/0.2.7/", "requires_dist": null, "requires_python": null, "summary": "Django URL cache invalidation from model saves", "version": "0.2.7" }, "last_serial": 1865260, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "60a28168ff6b60b8942335d9e939351e", "sha256": "b3ece5a6787941e8fb4ec7e4d45c34cca9be802e4afa39e5a5c8fb653cef477a" }, "downloads": -1, "filename": "django-dumper-0.1.0.tar.gz", "has_sig": false, "md5_digest": "60a28168ff6b60b8942335d9e939351e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10525, "upload_time": "2013-07-09T18:46:34", "url": "https://files.pythonhosted.org/packages/21/e4/38cce11f8b3c4a6d6968aad246d18f78427059c691ee2540b3e49ecadd76/django-dumper-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "655acb13c8c80ec234c44aba4e65224f", "sha256": "42a2fc1a9b6f08e31082363bf5700e92f26bdcc971a89c44d7e57bc08af4baf3" }, "downloads": -1, "filename": "django-dumper-0.1.1.tar.gz", "has_sig": false, "md5_digest": "655acb13c8c80ec234c44aba4e65224f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10500, "upload_time": "2013-07-20T15:14:54", "url": "https://files.pythonhosted.org/packages/f0/92/a154aaf0a1d16c5456f7c82c8b33b8a35e09187e2f3d4d2887100877f3d6/django-dumper-0.1.1.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "049019269863c3b4582eebd3e42cbd08", "sha256": "2a19c7d3a7f37971a8f1bf43764220c8a8e14f9932e84723db3bb537daf6b48a" }, "downloads": -1, "filename": "django-dumper-0.2.0.tar.gz", "has_sig": false, "md5_digest": "049019269863c3b4582eebd3e42cbd08", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10207, "upload_time": "2013-09-02T23:39:35", "url": "https://files.pythonhosted.org/packages/ca/2a/bfabd17c3f938e836a9d52697553571f964392dfc59ddb1a1d346d423844/django-dumper-0.2.0.tar.gz" } ], "0.2.1": [], "0.2.2": [ { "comment_text": "", "digests": { "md5": "ffb6ea7bca215812284addb28134b0b2", "sha256": "899a8bbdea6667eadc1e507bb8a8cca3ecbfa1d3e9900a9188af7ff11e9fbeee" }, "downloads": -1, "filename": "django-dumper-0.2.2.tar.gz", "has_sig": false, "md5_digest": "ffb6ea7bca215812284addb28134b0b2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15679, "upload_time": "2013-10-26T18:43:28", "url": "https://files.pythonhosted.org/packages/e8/d1/3b74c6785ed15172f556bd7cf8404ff2a1b8a893725b8f6905f4a74882e3/django-dumper-0.2.2.tar.gz" } ], "0.2.3": [ { "comment_text": "", "digests": { "md5": "d109dca48e58408a0fff3856822e494f", "sha256": "af79d7639c19b8132399d19de67d200878d997c906d777b6535ead0ebde9eeaf" }, "downloads": -1, "filename": "django-dumper-0.2.3.tar.gz", "has_sig": false, "md5_digest": "d109dca48e58408a0fff3856822e494f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15929, "upload_time": "2013-11-11T18:12:27", "url": "https://files.pythonhosted.org/packages/23/fb/a557b97bae07865013acdc9a272b3975e3f08e4c5690ccd21d750abb0316/django-dumper-0.2.3.tar.gz" } ], "0.2.4": [ { "comment_text": "", "digests": { "md5": "61281d594dd7fee415c37b5c191037a2", "sha256": "c756b5dff6b10d01b7b36e88636483c1b73ae59965ebc88ff458a14a7de9d6ab" }, "downloads": -1, "filename": "django-dumper-0.2.4.tar.gz", "has_sig": false, "md5_digest": "61281d594dd7fee415c37b5c191037a2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16128, "upload_time": "2014-03-12T22:42:29", "url": "https://files.pythonhosted.org/packages/94/39/0905e5d0b145c2d4900d4f3b0022047e03f07536f152093192e0dafb260b/django-dumper-0.2.4.tar.gz" } ], "0.2.5": [ { "comment_text": "", "digests": { "md5": "766723d814391decce3ef84edd81395a", "sha256": "e0b1bd69859539237f3aa5c09ceed1847c27dec1f306ea207cd2ce36102fec90" }, "downloads": -1, "filename": "django-dumper-0.2.5.tar.gz", "has_sig": false, "md5_digest": "766723d814391decce3ef84edd81395a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16674, "upload_time": "2014-06-03T18:31:34", "url": "https://files.pythonhosted.org/packages/0d/95/f3c6d4fda5aa34e3c1d0bd35971c530dbac866c6f95cc4fce8a0e59231e1/django-dumper-0.2.5.tar.gz" } ], "0.2.6": [ { "comment_text": "", "digests": { "md5": "6d3b44785ca33a29a3628eb33c44f8c8", "sha256": "65f0fc85bf937ec971c775f2af0359d9d1a57e7d95e89b91fe48efb3f949df66" }, "downloads": -1, "filename": "django-dumper-0.2.6.tar.gz", "has_sig": false, "md5_digest": "6d3b44785ca33a29a3628eb33c44f8c8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16933, "upload_time": "2015-11-02T16:18:59", "url": "https://files.pythonhosted.org/packages/08/3e/7f2bbc72a212ca2177d2777ae1e0e757fd5d3282a3c55cef14e057125190/django-dumper-0.2.6.tar.gz" } ], "0.2.7": [ { "comment_text": "", "digests": { "md5": "a7862c7795f92dce9e41ef7bd97a167c", "sha256": "7ccec76d1165e52efd7552af7eb0517d46d31b68cb8ea2b3581f3a849bb1ad72" }, "downloads": -1, "filename": "django-dumper-0.2.7.tar.gz", "has_sig": false, "md5_digest": "a7862c7795f92dce9e41ef7bd97a167c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17170, "upload_time": "2015-12-16T15:49:03", "url": "https://files.pythonhosted.org/packages/70/54/d4b29242ef445fcb277cc64d745de4e6c63c4c0dfd1bf2478510f1ec6248/django-dumper-0.2.7.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a7862c7795f92dce9e41ef7bd97a167c", "sha256": "7ccec76d1165e52efd7552af7eb0517d46d31b68cb8ea2b3581f3a849bb1ad72" }, "downloads": -1, "filename": "django-dumper-0.2.7.tar.gz", "has_sig": false, "md5_digest": "a7862c7795f92dce9e41ef7bd97a167c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17170, "upload_time": "2015-12-16T15:49:03", "url": "https://files.pythonhosted.org/packages/70/54/d4b29242ef445fcb277cc64d745de4e6c63c4c0dfd1bf2478510f1ec6248/django-dumper-0.2.7.tar.gz" } ] }