{ "info": { "author": "Jacek Tomaszewski", "author_email": "jacek.tomek@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 1.10", "Framework :: Django :: 1.11", "Framework :: Django :: 1.7", "Framework :: Django :: 1.8", "Framework :: Django :: 1.9", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python" ], "description": "==============\ndjango-synchro\n==============\n\n\nAim & purpose\n=============\n\nThis app is for synchronization of django objects between databases.\n\nIt logs information about objects' manipulations (additions, changes, deletions).\nWhen synchronization is launched, all objects logged from the last checkpoint are synced to another database.\n\n**Important note**: This app doesn't log detailed information about changes (e.g. which fields were updated),\njust that such manipulation occured. When the synchronization is performed, the objects are synced with their newest, actual values.\n(however, you can specify some fields to be `skipped` during synchronization, see below__).\n\n__ `Skipping fields`_\n\nExample 1\n---------\n\nConsider scenario:\n\n- there is one production project deployed on the web\n- and the same project is deployed on some office computer in case of main server failure\n\nAssuming that the local database is regularly synced (eg. once a day the main database is exported and imported into the local system),\nin case of a long main server downtime the staff may use the local project (inserting objects etc.).\n\nAfter the server is up again, the local changes (from the point of the last checkpoint) can be painlessly synchronized to the remote server.\n\nExample 2\n---------\n\nYou can also synchronize databases both ways, not only in the slave-master model like in the previous example.\n\nHowever, it is probably better (if possible) to have a common database rather than to have\none for every project deployment and to perform synchronization between them.\n\n\nRequirements\n============\n\nThe app is tested to work with Django 1.7 - 1.11. If you want to use app in older versions of Django,\nuse the 0.6 release.\n\nThe app needs ``django-dbsettings`` to store the time of last synchronization.\n\nInstallation\n============\n\n1. Install app (**note**: ``django-dbsettings`` is required and please view its install notes,\n such as `cache backend` important remarks)::\n\n $ pip install django-synchro\n\n or download it manually along with dependencies and put in python path.\n\n#. Configure ``DATABASES``.\n\n#. Add ``synchro`` and ``dbsettings`` to ``INSTALLED_APPS``.\n\n#. Specify in your ``settings.py`` what is `remote database` name and which models should be watched and synchronized::\n\n SYNCHRO_REMOTE = 'remote'\n SYNCHRO_MODELS = (\n 'my_first_app', # all models from my_first_app\n ('my_second_app', 'model1', 'model2'), # only listed models (letter case doesn't matter)\n 'my_third_app', # all models again\n 'django.contrib.sites', # you may specify fully qualified name...\n 'auth', # or just app label\n )\n\nLater, `REMOTE` will mean `remote database`.\n\n\nUsage\n=====\n\nSynchronization\n---------------\n\nJust invoke ``synchronize`` management command::\n\n $ ./manage.py synchronize\n\nAdmin synchro view\n------------------\n\nIn order to allow performing synchronization without shell access, you can use special admin view.\n\nInclude in your urls::\n\n url(r'^synchro/', include('synchro.urls', 'synchro', 'synchro')),\n\nThen the view will be available at reversed url: ``synchro:synchro``.\n\nThe view provides two buttons: one to perform synchronization, and the other to\n`reset checkpoint`__. If you would like to disable the reset button, set\n``SYNCHRO_ALLOW_RESET = False`` in your ``settings.py``.\n\nDebugging\n---------\n\nIn order to track a cause of exception during synchronization, set ``SYNCHRO_DEBUG = True``\n(and ``DEBUG = True`` as well) in your ``settings.py`` and try to perform synchronization by admin view.\n\n__ Checkpoints_\n\n``SYNCHRO_REMOTE`` setting\n--------------------------\n\nGenerally, ``SYNCHRO_REMOTE`` setting can behave in 3 different ways:\n\n1. The most naturally: it holds name of `REMOTE` database. When ``synchronize`` is called, ``sychro`` will\n sync objects from `LOCAL` database to `REMOTE` one.\n#. When ``SYNCHRO_REMOTE`` is ``None``: it means that no `REMOTE` is needed as ``synchro`` will only store\n logs (see below__). It's useful on `REMOTE` itself.\n#. When ``SYNCHRO_REMOTE`` is not specified at all, it behaves just like above (as if it was ``None``), but\n will show a RuntimeWarning.\n\n__ synchro_on_remote_\n\n\nRemarks and features\n====================\n\nQuerySet ``update`` issue\n-------------------------\n\nDjango-synchro logs information about objects modifications and later use it when asked for synchronization.\n\nThe logging take place using the ``post_save`` and ``post_delete`` signal handlers.\n\nThat means that actions which don't emmit those signals (like ``objects.update`` method) would result\nin no log stored, hence no synchronization of actions' objects.\n\n**So, please remind**: objects modified via ``objects.update`` won't be synchronized unless some special code is prepared\n(eg. calling ``save`` on all updated objects or manually invoking ``post_save`` signal).\n\nNatural keys\n------------\n\nFor efficient objects finding, it is **highly suggested** to provide ``natural_key`` object method\nand ``get_by_natural_key`` manager method.\nThis will allow easy finding whether the synchronized object exists in `REMOTE` and to prevent duplicating.\n\nAlthough adding ``natural_key`` to model definition is relatively quick, extending a manager may\nrequire extra work in cases when the default manager is used::\n\n class MyManager(models.Manager):\n def get_by_natural_key(self, code, day):\n return self.get(code=code, day=day)\n\n class MyModel(models.Model):\n ...\n objects = MyManager()\n def natural_key(self):\n return self.code, self.day\n\nTo minimalize the effort of implementing a custom manager, a shortcut is provided::\n\n from synchro.core import NaturalManager\n\n class MyModel(models.Model):\n ...\n objects = NaturalManager('code', 'day')\n def natural_key(self):\n return self.code, self.day\n\nOr even easier (effect is exactly the same)::\n\n from synchro.core import NaturalKeyModel\n\n class MyModel(NaturalKeyModel):\n ...\n _natural_key = ('code', 'day')\n\n``NaturalManager`` extends the built-in Manager by default; you can change its superclass using ``manager`` keyword::\n\n from synchro.core import NaturalManager\n\n class MyVeryCustomManager(models.Manager):\n ... # some mumbo-jumbo magic\n\n class MyModel(models.Model):\n ...\n objects = NaturalManager('code', 'day', manager=MyVeryCustomManager)\n def natural_key(self):\n return self.code, self.day\n\nWhen using ``NaturalKeyModel``, ``NaturalManager`` will extend the defined (``objects``) manager::\n\n from synchro.core import NaturalKeyModel\n\n class MyVeryCustomManager(models.Manager):\n ... # some mumbo-jumbo magic\n\n class MyModel(NaturalKeyModel):\n ...\n _natural_key = ('code', 'day')\n objects = MyVeryCustomManager()\n\nSide note: in fact invoking ``NaturalManager`` creates a new class being ``NaturalManager``'s subclass.\n\nThe purpose of a natural key is to *uniquely* distinguish among model instances;\nhowever, there are situations where it is impossible. You can choose such fields that will cause\n``get_by_natural_key`` to find more than one object. In such a situation, it will raise\n``MultipleObjectsReturned`` exception and the synchronization will fail.\n\nBut you can tell ``NaturalManager`` that you are aware of such a situation and that it\nshould just take the first object found::\n\n class Person(models.Model):\n ...\n # combination of person name and city is not unique\n objects = NaturalManager('first_name', 'last_name', 'city', allow_many=True)\n def natural_key(self):\n return self.first_name, self.last_name, self.city\n\nOr with ``NaturalKeyModel``::\n\n class Person(NaturalKeyModel):\n ...\n # combination of person name and city is not unique\n _natural_key = ('first_name', 'last_name', 'city')\n _natural_manager_kwargs = {'allow_many': True} # I know, it looks quite ugly\n\nDon't use ``allow_many`` unless you are completely sure what you are doing and what\nyou want to achieve.\n\nSide note: if ``natural_key`` consist of only one field, be sure to return a tuple anyway::\n\n class MyModel(models.Model):\n ...\n objects = NaturalManager('code')\n def natural_key(self):\n return self.code, # comma makes it tuple\n\nOr to assign tuple in ``NaturalKeyModel``::\n\n _natural_key = ('code',)\n\nPreviously, there were ``natural_manager`` function that was used instead of ``NaturalManager``\n- however, it's deprecated.\n\nSkipping fields\n---------------\n\nIf your model has some fields that should not be synchronized, like computed fields\n(eg. field with payment balances, which is updated on every order save - in ``order.post_save`` signal),\nyou can exclude them from synchronization::\n\n class MyModel(models.Model):\n ...\n SYNCHRO_SKIP = ('balance',)\n\nWhen a new object is synchronized, all its skipped fields will be reset to default values on `REMOTE`.\nOf course, the `LOCAL` object will stay untouched.\n\nTemporary logging disabling\n---------------------------\n\nIf you don't want to log some actions::\n\n from synchro.core import DisableSynchroLog\n\n with DisableSynchroLog():\n mymodel.name = foo\n mymodel.save()\n\nOr, in a less robust way, with a decorator::\n\n from synchro.core import disable_synchro_log\n\n @disable_synchro_log\n def foo(mymodel):\n mymodel.name = foo\n mymodel.save()\n\nSignals\n-------\n\nThat's a harder part.\n\nIf your signal handlers modify other objects, such an action will be probably reproduced twice:\n\n- first, when the model will be updated on `REMOTE`, then normal `REMOTE` signal handler will launch\n- second time, because the original signal handler's action was logged, the whole modified object will be synchronized;\n this is probably undesirable.\n\nConsider a bad scenario:\n\n1. Initially databases are synced. There is an object ``A`` in each of the databases. ``A.foo`` and ``A.bar`` values are both 1.\n#. On `REMOTE`, we change ``A.foo`` to 42 and save.\n#. On `LOCAL`, we save object ``X``. In some ``X`` signal handler, ``A.bar`` is incremented.\n#. We perform synchronization:\n\n a. ``X`` is synced.\n #. ``X`` signal handler is invoked on `REMOTE`, resulting in `REMOTE`'s ``A.bar`` incrementation.\n So far so good. `REMOTE`'s ``A.bar == 2`` and ``A.foo == 42``, just like it should.\n #. Because ``A`` change (during step 3) was logged, ``A`` is synced. *Not good* -\n `REMOTE` value of ``A.foo`` will be overwritten with 1\n (because `LOCAL` version is considered newer, as it was saved later).\n\nIt happened because the signal handler actions were logged.\n\nTo prevent this from happening, wrap handler with ``DisableSynchroLog``::\n\n @receiver(models.signals.post_delete, sender=Parcel)\n def update_agent_balance_delete(sender, instance, *args, **kwargs):\n with DisableSynchroLog():\n instance.agent.balance -= float(instance.payment_left))\n instance.agent.save()\n\nOr with the decorator::\n\n @receiver(models.signals.post_delete, sender=Parcel)\n @disable_synchro_log\n def update_agent_balance_delete(sender, instance, *args, **kwargs):\n instance.agent.balance -= float(instance.payment_left))\n instance.agent.save()\n\nIf using the decorator, be sure to place it after connecting to the signal, not before - otherwise it won't work.\n\n``Update`` issue again\n......................\n\nOne can benefit from the fact that ``objects.update`` is not logged and use it in signal handlers instead of ``DisableSynchroLog``.\n\nSignal handlers for multi-db\n............................\n\nJust a reminder note.\n\nWhen a synchronization is performed, signal handlers are invoked for created/updated/deleted `REMOTE` objects.\nAnd those signals are of course handled on the `LOCAL` machine.\n\nThat means: signal handlers (and probably other part of project code) must be ready to handle both `LOCAL`\nand `REMOTE` objects. It must use ``using(...)`` clause or ``db_manager(...)`` to ensure that the proper database\nis used::\n\n def reset_specials(sender, instance, *args, **kwargs):\n Offer.objects.db_manager(instance._state.db).filter(date__lt=instance.date).update(special=False)\n\nPlain ``objects``, without ``db_manager`` or ``using``, always use the ``default`` database (which means `LOCAL`).\n\nBut that is normal in multi-db projects.\n\n.. _synchro_on_remote:\n\nSynchro on `REMOTE` and time comparing\n--------------------------------------\n\nIf you wish only to synchronize one-way (always from `LOCAL` to `REMOTE`), you may be tempted not to include\n``synchro`` in `REMOTE` ``INSTALLED_APPS``.\n\nYes, you can do that and you will save some resources - logs won't be stored.\n\nBut keeping ``synchro`` active on `REMOTE` is a better idea. It will pay at synchonization: the synchro will look\nat logs and determine which object is newer. If the `LOCAL` one is older, it won't be synced.\n\nYou probably should set ``SYNCHRO_REMOTE = None`` on `REMOTE` if no synchronizations will be\nperformed there (alternatively, you can add some dummy sqlite database to ``DATABASES``).\n\nCheckpoints\n-----------\n\nIf you wish to reset sychronization status (that is - delete logs and set checkpoint)::\n\n from synchro.core import reset_synchro\n\n reset_synchro()\n\nOr raw way of manually changing synchro checkpoint::\n\n from synchro.models import options\n\n options.last_check = datetime.datetime.now() # or any time you wish\n\n----------\n\nChangelog\n=========\n\n**0.7** (12/11/2017)\n - Support Django 1.8 - 1.11\n - Dropped support for Django 1.6 and older\n - Backward incompatibility:\n you need to refactor all `from synchro import ...`\n into `from synchro.core import ...`\n\n**0.6** (27/12/2014)\n - Support Django 1.7\n - Fixed deprecation warnings\n\n**0.5.2** (29/07/2014)\n - Fixed dangerous typo\n - Added 'reset' button to synchro view and SYNCHRO_ALLOW_RESET setting\n - Prepared all texts for translation\n - Added PL, DE, FR, ES translations\n - Added ``SYNCHRO_DEBUG`` setting\n\n**0.5.1** (28/02/2013)\n Fixed a few issues with 0.5 release\n\n**0.5** (27/02/2013)\n - Refactored code to be compatible with Django 1.5\n - Required Django version increased from 1.3 to 1.4 (the code was already using some\n 1.4-specific functions)\n - Removed deprecated natural_manager function\n\n**0.4.2** (18/10/2012)\n - Fixed issue with app loading (thanks to Alexander Todorov for reporting)\n - Added 1 test regarding the issue above\n\n**0.4.1** (23/09/2012)\n - Fixed symmetrical m2m synchronization\n - Added 1 test regarding the issue above\n\n**0.4** (16/09/2012)\n - **Deprecation**: natural_manager function is deprecated. Use NaturalManager instead\n - Refactored NaturalManager class so that it plays well with models involved in m2m relations\n - Refactored NaturalManager class so that natural_manager function is unnecessary\n - Added NaturalKeyModel base class\n - Fixed bug with m2m user-defined intermediary table synchronization\n - Fixed bugs with m2m changes synchronization\n - Added 3 tests regarding m2m aspects\n\n**0.3.1** (12/09/2012)\n - ``SYNCHRO_REMOTE`` setting is not required anymore.\n Its lack will only block ``synchronize`` command\n - Added 2 tests regarding the change above\n - Updated README\n\n**0.3** (04/09/2012)\n - **Backward incompatible**: Changed ``Reference`` fields type from ``Integer`` to ``Char`` in\n order to store non-numeric keys\n - Included 24 tests\n - Refactored NaturalManager class so that it is accessible and importable\n - Exception is raised if class passed to natural_manager is not Manager subclass\n - Switched to dbsettings-bundled DateTimeValue\n - Updated README\n\n**0.2** (10/06/2012)\n Initial PyPI release\n\n**0.1**\n Local development\n\n----------\n\n:Author: Jacek Tomaszewski\n:Thanks: to my wife for text correction\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/zlorf/django-synchro", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "django-synchro", "package_url": "https://pypi.org/project/django-synchro/", "platform": "", "project_url": "https://pypi.org/project/django-synchro/", "project_urls": { "Homepage": "https://github.com/zlorf/django-synchro" }, "release_url": "https://pypi.org/project/django-synchro/0.7/", "requires_dist": null, "requires_python": "", "summary": "Django app for database data synchronization.", "version": "0.7" }, "last_serial": 3327071, "releases": { "0.2": [ { "comment_text": "", "digests": { "md5": "dac91b1448fa04f35aeeca57cf74c343", "sha256": "201e3746a3b04a03a449797e7e6c39bd0ddfa7c8ca9e8d3128890aa3fba617aa" }, "downloads": -1, "filename": "django-synchro-0.2.tar.gz", "has_sig": false, "md5_digest": "dac91b1448fa04f35aeeca57cf74c343", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14517, "upload_time": "2012-06-10T20:48:07", "url": "https://files.pythonhosted.org/packages/28/46/8c5765de8d96ef9a8c7be227e068607041ef83ff25c27a9e9a58425977f9/django-synchro-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "71fa4dfd02a4ad87dd3b595faa010c86", "sha256": "1d9d07eaa500f728dac1f7c6b8111d18e456e097ec9ec1c7df9dbf98c2a197a7" }, "downloads": -1, "filename": "django-synchro-0.3.tar.gz", "has_sig": false, "md5_digest": "71fa4dfd02a4ad87dd3b595faa010c86", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19975, "upload_time": "2012-09-04T12:11:36", "url": "https://files.pythonhosted.org/packages/36/ce/f27bab8c70f4cd7343dc4aa0dc4bd1491f31f8628cbd21c0d164ebf5cd28/django-synchro-0.3.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "fdf921c62fea3bc885e3ef047b234ce4", "sha256": "3103d8718148f4996142b5606a44ae0aa391fa4dd7813be8c6503f2d4d3e7827" }, "downloads": -1, "filename": "django-synchro-0.3.1.tar.gz", "has_sig": false, "md5_digest": "fdf921c62fea3bc885e3ef047b234ce4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21332, "upload_time": "2012-09-12T14:46:01", "url": "https://files.pythonhosted.org/packages/07/ca/11af5a348f098c625ab8a86d65b959ecc860614f240527cd6a0585ec99ac/django-synchro-0.3.1.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "29810b8dbe201cbef9e8ca1e7d3f5126", "sha256": "920a0d5a2fd2224029087255a164500554298df55c60ec8793c749bc1fcee35f" }, "downloads": -1, "filename": "django-synchro-0.4.tar.gz", "has_sig": false, "md5_digest": "29810b8dbe201cbef9e8ca1e7d3f5126", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24586, "upload_time": "2012-09-16T17:33:41", "url": "https://files.pythonhosted.org/packages/07/f3/cfb46be1ad4527faa031ea192bb090b2e5a3c48220dff854d9d4ec0bbd04/django-synchro-0.4.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "60d1843fa57c0bcf3720b4310fd122f6", "sha256": "e31da1ee3d34cbfb3206c66c7b2b357ce0468019ee09011032e85baf7ca0937a" }, "downloads": -1, "filename": "django-synchro-0.4.1.tar.gz", "has_sig": false, "md5_digest": "60d1843fa57c0bcf3720b4310fd122f6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24765, "upload_time": "2012-09-23T15:22:10", "url": "https://files.pythonhosted.org/packages/7c/93/d4f412ba159fb0565864948a720f9ec1e9fa3bb0895d534475fae2849419/django-synchro-0.4.1.tar.gz" } ], "0.4.2": [ { "comment_text": "", "digests": { "md5": "85dbfc1a666b1a640b1fe584ff6a5344", "sha256": "6bb164d4766e5d4de2165cb6ae6f88dd1eb37ccef888a6de5c785ff741fa4f00" }, "downloads": -1, "filename": "django-synchro-0.4.2.tar.gz", "has_sig": false, "md5_digest": "85dbfc1a666b1a640b1fe584ff6a5344", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25185, "upload_time": "2012-10-18T09:03:27", "url": "https://files.pythonhosted.org/packages/bf/19/d2cb68d7f89c9baf6cd98002d5328928ed93d0f5b2ce83014aa955330149/django-synchro-0.4.2.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "67c40fefc899a3205da3cb59885602ec", "sha256": "10505d6464a010947af7d721de1891650ac0fd667834da06a42eefbe2ef8f186" }, "downloads": -1, "filename": "django-synchro-0.5.tar.gz", "has_sig": false, "md5_digest": "67c40fefc899a3205da3cb59885602ec", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25465, "upload_time": "2013-02-27T17:09:31", "url": "https://files.pythonhosted.org/packages/04/0d/501bd569bb6232682a2a62b1637655a9ebbd698408bc73a625e8f945e7f3/django-synchro-0.5.tar.gz" } ], "0.5.1": [ { "comment_text": "", "digests": { "md5": "773461552a3a6f1b540aa3cca370404d", "sha256": "dc411cdf553e8c2dabc5d8c43eb609e4d32f51c83710f805ca7a64de2ceef593" }, "downloads": -1, "filename": "django-synchro-0.5.1.tar.gz", "has_sig": false, "md5_digest": "773461552a3a6f1b540aa3cca370404d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25772, "upload_time": "2013-02-28T09:29:22", "url": "https://files.pythonhosted.org/packages/3b/44/96f4bbeb33178e2af02a98a20c775980a80314d7212ca33f10acd5e57218/django-synchro-0.5.1.tar.gz" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "8375dbd76a3ebdaba7cc05f055d144a6", "sha256": "c8019f682924b696690d118ecf8502e1f96f6b75f77f9a22ad764e8f3b20625b" }, "downloads": -1, "filename": "django-synchro-0.5.2.tar.gz", "has_sig": false, "md5_digest": "8375dbd76a3ebdaba7cc05f055d144a6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26070, "upload_time": "2014-07-29T18:41:42", "url": "https://files.pythonhosted.org/packages/68/d8/9ac66c2565291976188baf031d6de6160e91509d916c8e9136ebf5e8625b/django-synchro-0.5.2.tar.gz" } ], "0.6": [ { "comment_text": "", "digests": { "md5": "5ff242ded7f8e90072ad59a520f5310b", "sha256": "002a9d403e23719e33c0c54f26233bdcd91c66d4df250fb023dad18647f0f390" }, "downloads": -1, "filename": "django-synchro-0.6.tar.gz", "has_sig": false, "md5_digest": "5ff242ded7f8e90072ad59a520f5310b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30766, "upload_time": "2014-12-27T10:20:43", "url": "https://files.pythonhosted.org/packages/17/41/7f5db3764489886fd8346a10fd65fc8c5383da566c7277fec4823f3c57ee/django-synchro-0.6.tar.gz" } ], "0.7": [ { "comment_text": "", "digests": { "md5": "7ea5e8826fe4d5e011a61f7a7db70194", "sha256": "00f74af4631f1086b9f94ec7b5f4f34a145f41c110aee08170befddfcdf75087" }, "downloads": -1, "filename": "django-synchro-0.7.tar.gz", "has_sig": false, "md5_digest": "7ea5e8826fe4d5e011a61f7a7db70194", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31104, "upload_time": "2017-11-12T22:30:01", "url": "https://files.pythonhosted.org/packages/40/df/40f43a17ad5332f86badec73200fa063e37a6e936cf72539766046f0aaf0/django-synchro-0.7.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7ea5e8826fe4d5e011a61f7a7db70194", "sha256": "00f74af4631f1086b9f94ec7b5f4f34a145f41c110aee08170befddfcdf75087" }, "downloads": -1, "filename": "django-synchro-0.7.tar.gz", "has_sig": false, "md5_digest": "7ea5e8826fe4d5e011a61f7a7db70194", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31104, "upload_time": "2017-11-12T22:30:01", "url": "https://files.pythonhosted.org/packages/40/df/40f43a17ad5332f86badec73200fa063e37a6e936cf72539766046f0aaf0/django-synchro-0.7.tar.gz" } ] }