{ "info": { "author": "Mikhail Shvein", "author_email": "work_shvein_mihail@mail.ru", "bugtrack_url": null, "classifiers": [], "description": "# django-pg-bulk-update\nDjango extension to update multiple table records with similar (but not equal) conditions in efficient way on PostgreSQL\n\n## Requirements\n* Python 2.7 or Python 3.4+\n* django >= 1.7 \n Previous versions may also work, but haven't been tested. \n django.postgres.contrib fields are also supported (available since django 1.8)\n django.postgres.contrib.JSONField is supported since django 1.9 \n* pytz\n* six\n* typing\n* psycopg2\n* PostgreSQL 9.3+ \n Previous versions may also work, but haven't been tested. \n JSONB operations are available for PostgreSQL 9.4+.\n INSERT .. ON CONFLICT is used for PostgreSQL 9.5+.\n\n## Installation\nInstall via pip: \n`pip install django-pg-bulk-update` \nor via setup.py: \n`python setup.py install`\n\n## Usage\nYou can make queries in 2 ways:\n* Declaring a custom manager for your model\n* Calling query functions directly\n\n### Query functions\nThere are 3 query helpers in this library. There parameters are unified and described in the section below. \n\n* `bulk_update(model, values, key_fields='id', using=None, set_functions=None, key_fields_ops=(), where=None, returning=None, batch_size=None, batch_delay=0)` \n This function updates multiple records of given model in single database query. \n Functions forms raw sql query for PostgreSQL. It's work is not guaranteed on other databases. \n Function returns number of updated records.\n\n* `bulk_update_or_create(model, values, key_fields='id', using=None, set_functions=None, update=True, key_is_unique=True, returning=None, batch_size=None, batch_delay=0)` \n This function finds records by key_fields. It creates not existing records with data, given in values. \n If `update` flag is set, it updates existing records with data, given in values. \n\n There are two ways, this function may work:\n 1) Use INSERT ... ON CONFLICT statement. It is safe, but requires PostgreSQL 9.5+ and unique index on key fields.\n This behavior is used by default.\n 2) 3-query transaction: \n + Search for existing records \n + Create not existing records (if values have any) \n + Update existing records (if values have any and `update` flag is set) \n This behavior is used by default on PostgreSQL before 9.5 and if key_is_unique parameter is set to False.\n Note that transactional update has a known [race condition issue](https://github.com/M1hacka/django-pg-bulk-update/issues/14) that can't be fixed.\n\n Function returns number of records inserted or updated by query.\n\n* `pdnf_clause(key_fields, field_values, key_fields_ops=())` \n Pure django implementation of principal disjunctive normal form. It is base on combining Q() objects. \n Condition will look like:\n ```sql\n SELECT ... WHERE (a = x AND b = y AND ...) OR (a = x1 AND b = y1 AND ...) OR ...\n ```\n Function returns a [django.db.models.Q](https://docs.djangoproject.com/en/2.0/topics/db/queries/#complex-lookups-with-q-objects) instance \n\n\n### Function parameters\n* `model: Type[Model]`\n A subclass of django.db.models.Model to update\n\n* `values: Union[Union[TUpdateValuesValid, Dict[Any, Dict[str, Any]]], Iterable[Dict[str, Any]]]` \n Data to update. All items must update same fields!!! \n Parameter can have one of 2 forms: \n + Iterable of dicts. Each dict contains both key and update data. Each dict must contain all key_fields as keys.\n You can't update key_fields with this format.\n + Dict of key_values: update_fields_dict \n You can use this format to update key_fields\n - key_values can be tuple or single object. If tuple, key_values length must be equal to key_fields length.\n If single object, key_fields is expected to have 1 element\n - update_fields_dict is a dictionary {field_name: update_value} to update\n\n* `key_fields: Union[str, Iterable[str]]`\n Optional. Field names, which are used as update conditions.\n Parameter can have one of 2 forms:\n + String for single key field. Primary key is used by default.\n + Iterable of strings for multiple key fields.\n\n* `using: Optional[str]` \n Optional. Database alias to query. If not set, 'default' database is used.\n\n* `set_functions: Optional[Dict[str, Union[str, AbstractSetFunction]]]` \n Optional. Functions which will be used to set values. \n If given, it should be a dictionary:\n + Key is a field name, function is applied to\n + Value is a function alias name or AbstractSetFunction instance. \n Available function aliases:\n - 'eq', '=' \n Simple assign operator. It used by default for fields that are not mentioned in the dict. \n - 'incr', '+' \n Adds field value to previous one. It can be used for all numeric database types. \n - 'concat', '||' \n Concatenates field value to previous one. It can be used for string types, JSONField, HStoreField, ArrayField.\n - 'eq_not_null' \n This function can be used, if you want to update value only if it is not None.\n - 'union' \n This function combines ArrayField value with previous one, removing duplicates.\n - You can define your own set function. See section below.\n\n Increment, union and concatenate functions concern NULL as default value.\n You can see default values in sections below.\n\n* `key_field_ops: Union[Dict[str, Union[str, AbstractClauseOperator]], Iterable[Union[str, AbstractClauseOperator]]]`\n Optional. Operators, which are used to fined records for update. Operators are applied to `key_fields`. \n If some fields are not given, equality operator is used.\n `bulk_update_or_create` function always uses equality operator\n Parameter can have one of 2 forms: \n - Iterable of operator alias names or AbstractClauseOperator instances.\n Order of iterable must be the same as key_fields.\n - Dictionary:\n + Key is a field name, function is applied to\n + Value is a function alias name of set function or AbstractSetFunction instance. \n Available name aliases:\n - 'eq', '=', '=='\n Simple equality condition. It is used by default.\n - '!eq', '!=', '<>'\n Not equal operator\n - 'in'\n Searches for records, which have field from values list. Value should be an iterable of correct field values.\n - '!in'\n Searches for records, which have field not from values list. Value should be an iterable of correct field values.\n - 'lt', '<'\n - 'lte', '<='\n - 'gt', '>'\n - 'gte', '>='\n - 'between'\n Searches for records, which have field between a and b. Value should be iterable with 2 items.\n - 'is_null', 'isnull'\n Checks field value for been NULL. Value should be boolean (true for IS NULL, false for IS NOT NULL)\n - You can define your own clause operator. See section below.\n\n* `where`: Optional[WhereNode] \n This parameter is used to filter data before doing bulk update, using QuerySet filter and exclude methods.\n Generated condition should not contain annotations and other table references. \n *NOTE*: parameter is not supported in `bulk_update_or_create`\n\n* `returning: Optional[Union[str, Iterable[str]]]` \n If this parameter is set, it can be: \n 1. A field name string \n 2. An iterable of field names \n 3. '*' string to return all model fields \n\n Query returns django_pg_returning.ReturningQuerySet instead of rows count. \n Using this feature requires [django-pg-returning](https://github.com/M1hacka/django-pg-returning/tree/v1.0.2) \n library installed (it is not in requirements, though).\n\n* `batch_size: Optional[int]` \n If this parameter is set, values are split into batches of given size. Each batch is processed separately.\n Note that batch_size != number of records processed if you use key_field_ops other than 'eq'\n\n* `batch_delay: float` \n If batch_size is set, this parameter sets time to sleep in seconds between batches execution\n\n* `update: bool` \n If flag is not set, bulk_update_or_create function will not update existing records, only creating not existing. \n\n* `key_is_unique: bool`\n Defaults to True. Settings this flag to False forces library to use 3-query transactional update_or_create.\n\n* `field_values: Iterable[Union[Iterable[Any], dict]]` \n Field values to use in `pdnf_clause` function. They have simpler format than update functions.\n It can come in 2 formats: \n + An iterable of tuples in key_fields order `( (x, y), (x1, y1), ...)`\n + An iterable of dicts with field name as key `({'a': x, 'b': y}, ...)`\n\n\n### Examples\n```python\nfrom django.db import models\nfrom djngo_pg_bulk_udpate import bulk_update, bulk_update_or_create, pdnf_clause\n\n# Test model\nclass TestModel(models.Model):\n name = models.CharField(max_length=50)\n int_field = models.IntegerField()\n\n# Create test data\nTestModel.objects.bulk_create([TestModel(pk=i, name=\"item%d\" % i, int_field=1) for i in range(1, 4)])\n\n# Update by id field\nupdated = bulk_update(TestModel, [{\n \"id\": 1,\n \"name\": \"updated1\",\n}, {\n \"id\": 2,\n \"name\": \"updated2\"\n}])\n\nprint(updated)\n# Outputs: 2\n\n# Update returning\nres = bulk_update(TestModel, [{\n \"id\": 1,\n \"name\": \"updated1\",\n}, {\n \"id\": 2,\n \"name\": \"updated2\"\n}], returning=('id', 'name', 'int_field'))\n\nprint(type(res), list(res.values_list('id', 'name', 'int_field')))\n# Outputs: \n# \n# [\n# (1, \"updated1\", 1),\n# (2, \"updated2\", 1)\n# ]\n\n# Call update by name field\nupdated = bulk_update(TestModel, {\n \"updated1\": {\n \"int_field\": 2\n },\n \"updated2\": {\n \"int_field\": 3\n }\n}, key_fields=\"name\")\n\nprint(updated)\n# Outputs: 2\n\nprint(list(TestModel.objects.all().order_by(\"id\").values(\"id\", \"name\", \"int_field\")))\n# Outputs: [\n# {\"id\": 1, \"name\": \"updated1\", \"int_field\": 2},\n# {\"id\": 2, \"name\": \"updated2\", \"int_field\": 3},\n# {\"id\": 3, \"name\": \"item3\", \"int_field\": 1}\n# ]\n\n# Increment int_field by 3 and set name to 'incr' for records where id >= 2 and int_field < 3\nupdated = bulk_update(TestModel, {\n (2, 3): {\n \"int_field\": 3,\n \"name\": \"incr\"\n }\n}, key_fields=['id', 'int_field'], key_fields_ops={'int_field': '<', 'id': 'gte'}, set_functions={'int_field': '+'})\n\nprint(updated)\n# Outputs: 1\n\nprint(list(TestModel.objects.all().order_by(\"id\").values(\"id\", \"name\", \"int_field\")))\n# Outputs: [\n# {\"id\": 1, \"name\": \"updated1\", \"int_field\": 2},\n# {\"id\": 2, \"name\": \"updated2\", \"int_field\": 3},\n# {\"id\": 3, \"name\": \"incr\", \"int_field\": 4}\n# ]\n\n\nres = bulk_update_or_create(TestModel, [{\n \"id\": 3,\n \"name\": \"_concat1\",\n \"int_field\": 4\n}, {\n \"id\": 4,\n \"name\": \"concat2\",\n \"int_field\": 5\n}], set_functions={'name': '||'})\n\nprint(res)\n# Outputs: 2\n\nprint(list(TestModel.objects.all().order_by(\"id\").values(\"id\", \"name\", \"int_field\")))\n# Outputs: [\n# {\"id\": 1, \"name\": \"updated1\", \"int_field\": 2},\n# {\"id\": 2, \"name\": \"updated2\", \"int_field\": 3},\n# {\"id\": 3, \"name\": \"incr_concat1\", \"int_field\": 4},\n# {\"id\": 4, \"name\": \"concat2\", \"int_field\": 5},\n# ]\n\n# Find records where \n# id IN [1, 2, 3] AND name = 'updated2' OR id IN [3, 4, 5] AND name = 'concat2' OR id IN [2, 3, 4] AND name = 'updated1'\n cond = pdnf_clause(['id', 'name'], [([1, 2, 3], 'updated2'),\n ([3, 4, 5], 'concat2'),\n ([2, 3, 4], 'updated1')], key_fields_ops={'id': 'in'})\ndata = TestModel.objects.filter(cond).order_by('int_field').values_list('int_field', flat=True)\nprint(list(data))\n# Outputs: [3, 5]\n```\n\n### Using custom manager and query set\nIn order to simplify using `bulk_update` and `bulk_update_or_create` functions, you can use a custom manager. \nIt automatically fills:\n * `model` parameter\n * `using` parameter (extracts queryset write database)\n * `where` parameter (applies queryset filters, if called as QuerySet method). Not supported in bulk_update_or_create. \nYou can change database to use with [Manager.db_manager()](https://docs.djangoproject.com/en/2.0/topics/db/multi-db/#using-managers-with-multiple-databases) \nor [QuerySet.using()](https://docs.djangoproject.com/en/2.0/topics/db/multi-db/#manually-selecting-a-database-for-a-queryset) methods. \nThe rest parameters are the same as above. \n\n**Note**: As [django 2.2](https://docs.djangoproject.com/en/2.2/releases/2.2/) \n introduced [bulk_update](https://docs.djangoproject.com/en/2.2/ref/models/querysets/#bulk-update) method,\n library methods were renamed to `pg_bulk_update` and `pg_bulk_update_or_create` respectively.\n\nExample:\n```python\nfrom django.db import models\nfrom django_pg_bulk_update.manager import BulkUpdateManager\n\n# Test model\nclass TestModel(models.Model):\n objects = BulkUpdateManager()\n\n name = models.CharField(max_length=50)\n int_field = models.IntegerField()\n\n# Now you can use functions like:\nTestModel.objects.pg_bulk_update([\n # Any data here\n], key_fields='id', set_functions=None, key_fields_ops=())\n\n# Update only records with id gtreater than 5 \nTestModel.objects.filter(id__gte=5).pg_bulk_update([\n # Any data here\n], key_fields='id', set_functions=None, key_fields_ops=())\n\nTestModel.objects.pg_bulk_update_or_create([\n # Any data here\n], key_fields='id', set_functions=None, update=True) \n```\n\nIf you already have a custom manager, you can replace QuerySet to BulkUpdateQuerySet:\n```python\nfrom django.db import models\nfrom django.db.models.manager import BaseManager\nfrom django_pg_bulk_update.manager import BulkUpdateQuerySet\n\n\nclass CustomManager(BaseManager.from_queryset(BulkUpdateQuerySet)):\n pass\n\n\n# Test model\nclass TestModel(models.Model):\n objects = CustomManager()\n\n name = models.CharField(max_length=50)\n int_field = models.IntegerField()\n```\n\nIf you already have a custom QuerySet, you can inherit it from BulkUpdateMixin:\n```python\nfrom django.db import models\nfrom django.db.models.manager import BaseManager\nfrom django_pg_bulk_update.manager import BulkUpdateMixin\n\n\nclass CustomQuerySet(BulkUpdateMixin, models.QuerySet):\n pass\n\n\nclass CustomManager(BaseManager.from_queryset(CustomQuerySet)):\n pass\n\n\n# Test model\nclass TestModel(models.Model):\n objects = CustomManager()\n\n name = models.CharField(max_length=50)\n int_field = models.IntegerField()\n```\n\n### Custom clause operator\nYou can define your own clause operator, creating `AbstractClauseOperator` subclass and implementing:\n* `names` attribute\n* `def get_django_filter(self, name)` method\n* One of `def get_sql_operator(self)` or `def get_sql(self, table_field, value)`\n When clause is formed, it calls `get_sql()` method.\n In order to simplify method usage of simple `field value` operators,\n by default `get_sql()` forms this condition, calling `get_sql_operator()` method, which returns .\n\nOptionally, you can change `def format_field_value(self, field, val, connection, cast_type=True, **kwargs)` method,\nwhich formats value according to field rules\n\nExample:\n```python\nfrom django_pg_bulk_update import bulk_update\nfrom django_pg_bulk_update.clause_operators import AbstractClauseOperator\n\nclass LTClauseOperator(AbstractClauseOperator):\n names = {'lt', '<'}\n\n def get_django_filter(self, name): # type: (str) -> str\n \"\"\"\n This method should return parameter name to use in django QuerySet.fillter() kwargs\n :param name: Name of parameter\n :return: String with filter\n \"\"\"\n return '%s__lt' % name\n\n def get_sql_operator(self): # type: () -> str\n \"\"\"\n If get_sql operator is simple binary operator like \"field val\", this functions returns operator\n :return: str\n \"\"\"\n return '<'\n\n\n# Usage examples\n# import you function here before calling an update\nbulk_update(TestModel, [], key_field_ops={'int_field': 'lt'})\nbulk_update(TestModel, [], key_field_ops={'int_field': LTClauseOperator()})\n```\n\nYou can use class instance directly in `key_field_ops` parameter or use its aliases from `names` attribute. \nWhen update function is called, it searches for all imported AbstractClauseOperator subclasses and takes first class\nwhich contains alias in `names` attribute.\n\n### Custom set function\nYou can define your own set function, creating `AbstractSetFunction` subclass and implementing:\n* `names` attribute\n* `supported_field_classes` attribute\n* One of: \n - `def get_sql_value(self, field, val, connection, val_as_param=True, with_table=False, for_update=True, **kwargs)` method\n This method defines new value to set for parameter. It is called from `get_sql(...)` method by default.\n - `def get_sql(self, field, val, connection, val_as_param=True, with_table=False, for_update=True, **kwargs)` method\n This method sets full sql and it params to use in set section of update query. \n By default it returns: `\"%s\" = self.get_sql_value(...)`, params\n\nOptionally, you can change:\n* `def format_field_value(self, field, val, connection, cast_type=False, **kwargs)` method, if input data needs special formatting. \n* `def modify_create_params(self, model, key, kwargs)` method, to change data before passing them to model constructor\nin `bulk_update_or_create()`. This method is used in 3-query transactional update only. INSERT ... ON CONFLICT\nuses for_update flag of `get_sql()` and `get_sql_value()` functions\n\nExample: \n\n```python\nfrom django_pg_bulk_update import bulk_update\nfrom django_pg_bulk_update.set_functions import AbstractSetFunction\n\nclass CustomSetFunction(AbstractSetFunction):\n # Set function alias names\n names = {'func_alias_name'}\n\n # Names of django field classes, this function supports. You can set None (default) to support any field.\n supported_field_classes = {'IntegerField', 'FloatField', 'AutoField', 'BigAutoField'}\n\n def get_sql_value(self, field, val, connection, val_as_param=True, with_table=False, for_update=True, **kwargs):\n \"\"\"\n Returns value sql to set into field and parameters for query execution\n This method is called from get_sql() by default.\n :param field: Django field to take format from\n :param val: Value to format\n :param connection: Connection used to update data\n :param val_as_param: If flag is not set, value should be converted to string and inserted into query directly.\n Otherwise a placeholder and query parameter will be used\n :param with_table: If flag is set, column name in sql is prefixed by table name\n :param for_update: If flag is set, returns update sql. Otherwise - insert SQL\n :param kwargs: Additional arguments, if needed\n :return: A tuple: sql, replacing value in update and a tuple of parameters to pass to cursor\n \"\"\"\n # If operation is incremental, it should be ready to get NULL in database\n null_default, null_default_params = self._parse_null_default(field, connection, **kwargs)\n\n # Your function/operator should be defined here\n tpl = 'COALESCE(\"%s\", %s) + %s'\n\n if val_as_param:\n sql, params = self.format_field_value(field, val, connection)\n return tpl % (field.column, null_default, sql), null_default_params + params\n else:\n return tpl % (field.column, null_default, str(val)), null_default_params\n\n\n# Usage examples\n# import you function here before calling an update\nbulk_update(TestModel, [], set_functions={'int_field': 'func_alias_name'})\nbulk_update(TestModel, [], set_functions={'int_field': CustomSetFunction()})\n```\n\nYou can use class instance directly in `set_functions` parameter or use its aliases from `names` attribute. \nWhen update function is called, it searches for all imported AbstractSetFunction subclasses and takes first class\nwhich contains alias in `names` attribute.\n\n\n## Compatibility\nLibrary supports django.contrib.postgres.fields: \n+ ArrayField \n+ JSONField \n+ HStoreField \n\nNote that ArrayField and HStoreField are available since django 1.8, JSONField - since django 1.9. \nPostgreSQL before 9.4 doesn't support jsonb, and so - JSONField. \nPostgreSQL 9.4 supports JSONB, but doesn't support concatenation operator (||).\nIn order to support this set function a special function for postgres 9.4 was written. Add a migration to create it:\n\n```python\nfrom django.db import migrations,\nfrom django_pg_bulk_update.compatibility import Postgres94MergeJSONBMigration\n\nclass Migration(migrations.Migration):\n dependencies = []\n\n operations = [\n Postgres94MergeJSONBMigration()\n ]\n```\n\nPostgreSQL before 9.5 doesn't support INSERT ... ON CONFLICT statement. So 3-query transactional update will be used.\n\n## Performance\nTest background:\n- Django 2.0.2\n- PostgreSQL 10.2\n- Python 3.6.3\n- 1000 pre-created records \nUpdating records one by one took 51,68 seconds. \nUpdating records with bulk_update took 0.13 seconds. \nYou can write your own tests, based on test.test_performance and running it.\n\n## [django 2.2 bulk_update](https://docs.djangoproject.com/en/2.2/ref/models/querysets/#bulk-update) difference \nPros: \n* bulk_update_or_create() method\n* Ability to use complex set functions\n* Ability to use complex conditions\n* Ability to update primary key\n* pdnf_clause helper\n* Django 1.7+ support\n* Ability to make delay between batches\n* Ability to return affected rows instead of rowcount (using Postgres RETURNING feature)\n\nCons: \n* PostgreSQL only\n* Django method supports Func objects ([in library's backlog](https://github.com/M1hacka/django-pg-bulk-update/issues/38))\n* Ability to update parents/children (using extra queries)\n\n## [django-bulk-update](https://github.com/aykut/django-bulk-update) difference\nPros:\n* bulk_update_or_create() method\n* Ability to use complex set functions\n* Ability to use complex conditions\n* pdnf_clause helper\n* Django 1.7 support\n* Ability to make delay between batches\n* Ability to return affected rows instead of rowcount (using Postgres RETURNING feature)\n\nCons:\n* PostgreSQL only\n\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/M1hacka/django-pg-bulk-update", "keywords": "", "license": "BSD 3-clause \"New\" or \"Revised\" License", "maintainer": "", "maintainer_email": "", "name": "django-pg-bulk-update", "package_url": "https://pypi.org/project/django-pg-bulk-update/", "platform": "", "project_url": "https://pypi.org/project/django-pg-bulk-update/", "project_urls": { "Homepage": "https://github.com/M1hacka/django-pg-bulk-update" }, "release_url": "https://pypi.org/project/django-pg-bulk-update/3.0.1/", "requires_dist": null, "requires_python": "", "summary": "Django extension, executing bulk update operations for PostgreSQL", "version": "3.0.1" }, "last_serial": 5971942, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "32c5b776c90dcfa281fcad563c7fdef9", "sha256": "38dd4d8503f6dd6b854757965fdae4ac340e1a7868db94ca162701b42655038e" }, "downloads": -1, "filename": "django_pg_bulk_update-1.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "32c5b776c90dcfa281fcad563c7fdef9", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 20497, "upload_time": "2018-03-10T08:05:42", "url": "https://files.pythonhosted.org/packages/ab/95/06a45410f948f676d0e0aa8ecdbd436c719ecf94eb915fc18343268e27ee/django_pg_bulk_update-1.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0d2852aa94498a6ad4fbc9856e3df771", "sha256": "dbe118b8186ccb81cc2a2213613d1a783e52c3bc68fe5708e6d4d9e1e832fa84" }, "downloads": -1, "filename": "django-pg-bulk-update-1.0.0.tar.gz", "has_sig": false, "md5_digest": "0d2852aa94498a6ad4fbc9856e3df771", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19174, "upload_time": "2018-03-10T08:05:45", "url": "https://files.pythonhosted.org/packages/03/54/73a27cff957d831106f2ded95f77a96948ac87c0ca1716a493dc5c09713e/django-pg-bulk-update-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "8f8b91fd895158778bd0e645013bfae3", "sha256": "966304dda2c531ddd28a93a98f88ff35f8b7c690137361e266f576c59c3b8bed" }, "downloads": -1, "filename": "django_pg_bulk_update-1.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "8f8b91fd895158778bd0e645013bfae3", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 20603, "upload_time": "2018-03-30T09:49:17", "url": "https://files.pythonhosted.org/packages/35/e4/1d62e0dd869c1738007162eae950c738b53eff110a20eb70a212ad93da80/django_pg_bulk_update-1.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c7163a80b343b01ecd8fec7ceff449d1", "sha256": "680e6634142d84299b2c82458f9724fbb648b5ef9e39e37c3abf18a40350eded" }, "downloads": -1, "filename": "django-pg-bulk-update-1.0.1.tar.gz", "has_sig": false, "md5_digest": "c7163a80b343b01ecd8fec7ceff449d1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19243, "upload_time": "2018-03-30T09:49:18", "url": "https://files.pythonhosted.org/packages/12/9f/e13d17ecf3dc1319f08ea69c7c7cfed5776ea77467749c30eeb3a9a26bf5/django-pg-bulk-update-1.0.1.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "e368e186d72ba14fbd937378f703d6f7", "sha256": "2117d21426771f0664ea9b96403ea2baf8649ed741e0d1f649571c652851055e" }, "downloads": -1, "filename": "django_pg_bulk_update-1.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "e368e186d72ba14fbd937378f703d6f7", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 21037, "upload_time": "2018-04-30T09:53:15", "url": "https://files.pythonhosted.org/packages/15/88/087026c163a3a53b1f606912df48a6e7945f4524ab85f387e9ba400a67e2/django_pg_bulk_update-1.1.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4662b6739d7a14f944aa4478d4f70a20", "sha256": "0a8043c4905afd4c1150a702c295011279141c18ec8e2f19deec5013cda75f0a" }, "downloads": -1, "filename": "django-pg-bulk-update-1.1.0.tar.gz", "has_sig": false, "md5_digest": "4662b6739d7a14f944aa4478d4f70a20", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20084, "upload_time": "2018-04-30T09:53:17", "url": "https://files.pythonhosted.org/packages/d6/c0/6c80396cfa61b30d844c3ce6a69ad5280c56d32e4666992a6e9046e41a6a/django-pg-bulk-update-1.1.0.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "7e46ab19e92d17607b7526635409d896", "sha256": "f2a2545ac1742914caf1c88a218ae792635d4a225d17178ab1882568845dcf3a" }, "downloads": -1, "filename": "django_pg_bulk_update-2.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7e46ab19e92d17607b7526635409d896", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 28390, "upload_time": "2018-09-08T10:26:49", "url": "https://files.pythonhosted.org/packages/0a/bb/d815d8aae2d434f83fecc22e0c4d752394211e01581c61cc4ca8711d72a4/django_pg_bulk_update-2.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a9f116aa4e0e99e2c3fa106ba2aa7b26", "sha256": "14b2690e47b409c80f67a23493136a04e377d24aae6850bb5ff0db4727dadad1" }, "downloads": -1, "filename": "django-pg-bulk-update-2.0.0.tar.gz", "has_sig": false, "md5_digest": "a9f116aa4e0e99e2c3fa106ba2aa7b26", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29918, "upload_time": "2018-09-08T10:26:51", "url": "https://files.pythonhosted.org/packages/b2/01/1205405811826ee487784828a448a0b247905a6c59127380d683a31d9b29/django-pg-bulk-update-2.0.0.tar.gz" } ], "2.0.1": [ { "comment_text": "", "digests": { "md5": "c7f0f457b376472e32ea965003b0c788", "sha256": "dd5231b253787762d3a5150f8ee9e45075173812696a94c7a65f2fd8238230df" }, "downloads": -1, "filename": "django_pg_bulk_update-2.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "c7f0f457b376472e32ea965003b0c788", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 28605, "upload_time": "2018-09-26T07:25:10", "url": "https://files.pythonhosted.org/packages/e1/ef/f2f402a85f68403a5674fc8c8926692d13a89a5f3579849e0178d0b84ef2/django_pg_bulk_update-2.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "adfbb7721dbb7a5fc590efec5101d377", "sha256": "8ed828874ecbf94390f62bc81b56921842b091e209434141ca4de66971fec4bc" }, "downloads": -1, "filename": "django_pg_bulk_update-2.0.1-py3.6.egg", "has_sig": false, "md5_digest": "adfbb7721dbb7a5fc590efec5101d377", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": null, "size": 56771, "upload_time": "2018-09-26T07:25:12", "url": "https://files.pythonhosted.org/packages/a2/e5/0a7cd9b24f589497ba1ef63f886b71bee3e7e088f6bea5fa1805f2de45c3/django_pg_bulk_update-2.0.1-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "c853558b11c745836dc60f229894bae2", "sha256": "270eca0396579dd4d09ef45ae71a9eb1b5b5d75768596025097856b534d7d2b7" }, "downloads": -1, "filename": "django-pg-bulk-update-2.0.1.tar.gz", "has_sig": false, "md5_digest": "c853558b11c745836dc60f229894bae2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30217, "upload_time": "2018-09-26T07:25:14", "url": "https://files.pythonhosted.org/packages/9e/31/82ff7395b7de92601c890c0b336914793b52ab84781a3de8a85e67e6b4c6/django-pg-bulk-update-2.0.1.tar.gz" } ], "2.0.2": [ { "comment_text": "", "digests": { "md5": "3cb65b969e5277a8c5313aacaf42bba5", "sha256": "32e8b156c7dc96a7d7b88ed1953fcc10c79de172fd9f210d47d0f1bd5c592162" }, "downloads": -1, "filename": "django_pg_bulk_update-2.0.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "3cb65b969e5277a8c5313aacaf42bba5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 28619, "upload_time": "2018-09-27T06:35:37", "url": "https://files.pythonhosted.org/packages/90/c5/72bfce6bfce8285a32771d12c309f5cd9a721d3c1e4a853c560166c5bb1b/django_pg_bulk_update-2.0.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "76d2b5bda76ccecd7ea5847e603b576d", "sha256": "729b1e3b7a4b6ec75dbe8930f024a90f287b902a9e3c140992afb7bcaa220007" }, "downloads": -1, "filename": "django-pg-bulk-update-2.0.2.tar.gz", "has_sig": false, "md5_digest": "76d2b5bda76ccecd7ea5847e603b576d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30226, "upload_time": "2018-09-27T06:35:38", "url": "https://files.pythonhosted.org/packages/24/65/cd2ffa31ff4ff06334039dd68683154d49aeaf269029e85499a8b4f80d4f/django-pg-bulk-update-2.0.2.tar.gz" } ], "2.0.3": [ { "comment_text": "", "digests": { "md5": "c0c6aab5e2408b72f5c1b5c5a82ebf01", "sha256": "a38047109656261c935b6c7842b96c9cb805fe88f36c3abc31a4570ac5b05115" }, "downloads": -1, "filename": "django_pg_bulk_update-2.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "c0c6aab5e2408b72f5c1b5c5a82ebf01", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 28712, "upload_time": "2018-10-03T09:35:21", "url": "https://files.pythonhosted.org/packages/60/2e/9b3cb8576f450fcf51269de47d25f37d81b910add1dfab5b6f8a9dedec7f/django_pg_bulk_update-2.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2a7663566d4d8dae9f4edc59c3a30a2f", "sha256": "8fad605adc5d8993b04e997480dba864515a6b1f7fa2b75a2839409cfebda03e" }, "downloads": -1, "filename": "django-pg-bulk-update-2.0.3.tar.gz", "has_sig": false, "md5_digest": "2a7663566d4d8dae9f4edc59c3a30a2f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30377, "upload_time": "2018-10-03T09:35:23", "url": "https://files.pythonhosted.org/packages/23/45/83d6e2aa3a86ff1b9f1d138c597ac3bdd97b3dae2fce61d44bb1b9550f32/django-pg-bulk-update-2.0.3.tar.gz" } ], "2.1.0": [ { "comment_text": "", "digests": { "md5": "53c1105f7ca71974db256b4aa6564fcf", "sha256": "de4b162a9754d8236ca2075a7d16d0042054f630e561fa8d43ba2cbc820e2533" }, "downloads": -1, "filename": "django_pg_bulk_update-2.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "53c1105f7ca71974db256b4aa6564fcf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 29939, "upload_time": "2018-10-31T07:27:44", "url": "https://files.pythonhosted.org/packages/04/2e/4802a4a1dae10dccee8e1bff47fe13e6c72bf0565ec783575de14741dd00/django_pg_bulk_update-2.1.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c5e00a488b2b5953e3b9f3f8469d94d0", "sha256": "3e82439aca33dc63438610aaa35532491df98b15d53b97d19d71d238bf12698f" }, "downloads": -1, "filename": "django-pg-bulk-update-2.1.0.tar.gz", "has_sig": false, "md5_digest": "c5e00a488b2b5953e3b9f3f8469d94d0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32032, "upload_time": "2018-10-31T07:27:46", "url": "https://files.pythonhosted.org/packages/23/7f/186db95813f461eb56a8b13726494dfb3cd0147dd8a0e5ececec1bafb650/django-pg-bulk-update-2.1.0.tar.gz" } ], "2.2.0": [ { "comment_text": "", "digests": { "md5": "58d33ce1cfdf8a85f7cc6ac670d395d5", "sha256": "e04f4c805c3356a1bdcc2bca08620430dd8f3d66c17f2484bf6ebd40d58212d6" }, "downloads": -1, "filename": "django_pg_bulk_update-2.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "58d33ce1cfdf8a85f7cc6ac670d395d5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 31083, "upload_time": "2019-03-24T04:25:16", "url": "https://files.pythonhosted.org/packages/77/96/03a408266f965ca67bee2ee87b209b5075f3e550e45b48c8f43d62a23fcd/django_pg_bulk_update-2.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d20a9d1141c7be0514d0d776fdc1a38d", "sha256": "61e08f3b94b32f958dd1379f2286c831c4b3376517f48f50740805b14ce50bfb" }, "downloads": -1, "filename": "django-pg-bulk-update-2.2.0.tar.gz", "has_sig": false, "md5_digest": "d20a9d1141c7be0514d0d776fdc1a38d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32180, "upload_time": "2019-03-24T04:25:19", "url": "https://files.pythonhosted.org/packages/50/06/5537a0a833869e723a400f1085258d0809c0a94448f8cd97eae6cf118593/django-pg-bulk-update-2.2.0.tar.gz" } ], "3.0.0": [ { "comment_text": "", "digests": { "md5": "33a9533f22725bc4ac5d6e8df1a811c8", "sha256": "2e44deb630285ba12b1e8108c38587ada1a422e34be02e5821f34667147d1b7b" }, "downloads": -1, "filename": "django_pg_bulk_update-3.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "33a9533f22725bc4ac5d6e8df1a811c8", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 32130, "upload_time": "2019-07-08T04:48:35", "url": "https://files.pythonhosted.org/packages/cc/ce/3b484b777e1c25c240c2ac2f0ec09841d06b1e0d34b6e0f4eec32c682c8e/django_pg_bulk_update-3.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "893ac07b98960ca2044a27f3a10befee", "sha256": "b0c2af346b2767784d21b5b97ee143852f280082dbe6572fef51186ed3fe387c" }, "downloads": -1, "filename": "django-pg-bulk-update-3.0.0.tar.gz", "has_sig": false, "md5_digest": "893ac07b98960ca2044a27f3a10befee", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33618, "upload_time": "2019-07-08T04:48:37", "url": "https://files.pythonhosted.org/packages/a8/1c/13d9696a057b54167553867ed26f5c3089e50c32c9428fdc4257d2e3778a/django-pg-bulk-update-3.0.0.tar.gz" } ], "3.0.1": [ { "comment_text": "", "digests": { "md5": "d331f64f7bb0fa73479db76852a80caf", "sha256": "7c4076055e24a5e9c687e0b0cca32a7c142882ff7ec5000d6b902d7f49f3d0ed" }, "downloads": -1, "filename": "django_pg_bulk_update-3.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "d331f64f7bb0fa73479db76852a80caf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 32609, "upload_time": "2019-10-14T14:51:06", "url": "https://files.pythonhosted.org/packages/77/69/22eee45e798a750a7e1fec209c4a88515998bd305b1bca46358c11ad62c4/django_pg_bulk_update-3.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1a5058cf3f84be5fcee888fec722c724", "sha256": "c74c836345341c129cceb9bace9b31f8a64b41e84f82ffadcd7b24cc0bb39cef" }, "downloads": -1, "filename": "django-pg-bulk-update-3.0.1.tar.gz", "has_sig": false, "md5_digest": "1a5058cf3f84be5fcee888fec722c724", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34378, "upload_time": "2019-10-14T14:51:08", "url": "https://files.pythonhosted.org/packages/3b/15/35890553e4cc6656c7149be93ff8967845c9d99240b5db937215f209335b/django-pg-bulk-update-3.0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "d331f64f7bb0fa73479db76852a80caf", "sha256": "7c4076055e24a5e9c687e0b0cca32a7c142882ff7ec5000d6b902d7f49f3d0ed" }, "downloads": -1, "filename": "django_pg_bulk_update-3.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "d331f64f7bb0fa73479db76852a80caf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 32609, "upload_time": "2019-10-14T14:51:06", "url": "https://files.pythonhosted.org/packages/77/69/22eee45e798a750a7e1fec209c4a88515998bd305b1bca46358c11ad62c4/django_pg_bulk_update-3.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1a5058cf3f84be5fcee888fec722c724", "sha256": "c74c836345341c129cceb9bace9b31f8a64b41e84f82ffadcd7b24cc0bb39cef" }, "downloads": -1, "filename": "django-pg-bulk-update-3.0.1.tar.gz", "has_sig": false, "md5_digest": "1a5058cf3f84be5fcee888fec722c724", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34378, "upload_time": "2019-10-14T14:51:08", "url": "https://files.pythonhosted.org/packages/3b/15/35890553e4cc6656c7149be93ff8967845c9d99240b5db937215f209335b/django-pg-bulk-update-3.0.1.tar.gz" } ] }