Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pynetbox for openSUSE:Factory 
checked in at 2021-11-01 18:35:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pynetbox (Old)
 and      /work/SRC/openSUSE:Factory/.python-pynetbox.new.1890 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pynetbox"

Mon Nov  1 18:35:41 2021 rev:25 rq:928436 version:6.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pynetbox/python-pynetbox.changes  
2021-08-03 22:48:39.556502774 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pynetbox.new.1890/python-pynetbox.changes    
    2021-11-01 18:35:53.761342083 +0100
@@ -1,0 +2,6 @@
+Sun Oct 31 10:10:38 UTC 2021 - Martin Hauke <[email protected]>
+
+- Update to version 6.2.0
+  * Fixes bulk update/delete on both Endpoint and RecordSet.
+
+-------------------------------------------------------------------

Old:
----
  pynetbox-6.1.3.tar.gz

New:
----
  pynetbox-6.2.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pynetbox.spec ++++++
--- /var/tmp/diff_new_pack.sIT8ON/_old  2021-11-01 18:35:55.073342833 +0100
+++ /var/tmp/diff_new_pack.sIT8ON/_new  2021-11-01 18:35:55.073342833 +0100
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define         skip_python2 1
 Name:           python-pynetbox
-Version:        6.1.3
+Version:        6.2.0
 Release:        0
 Summary:        NetBox API client library
 License:        Apache-2.0

++++++ pynetbox-6.1.3.tar.gz -> pynetbox-6.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/PKG-INFO new/pynetbox-6.2.0/PKG-INFO
--- old/pynetbox-6.1.3/PKG-INFO 2021-07-31 01:24:57.024464100 +0200
+++ new/pynetbox-6.2.0/PKG-INFO 2021-10-29 23:53:37.552109200 +0200
@@ -1,12 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: pynetbox
-Version: 6.1.3
+Version: 6.2.0
 Summary: NetBox API client library
 Home-page: https://github.com/digitalocean/pynetbox
 Author: Zach Moody
 Author-email: [email protected]
 License: Apache2
-Description: UNKNOWN
 Keywords: netbox
 Platform: UNKNOWN
 Classifier: Intended Audience :: Developers
@@ -15,3 +14,7 @@
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.6
+License-File: LICENSE
+
+UNKNOWN
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/pynetbox/core/endpoint.py 
new/pynetbox-6.2.0/pynetbox/core/endpoint.py
--- old/pynetbox-6.1.3/pynetbox/core/endpoint.py        2021-07-31 
01:24:46.000000000 +0200
+++ new/pynetbox-6.2.0/pynetbox/core/endpoint.py        2021-10-29 
23:53:20.000000000 +0200
@@ -312,6 +312,129 @@
             return [self.return_obj(i, self.api, self) for i in req]
         return self.return_obj(req, self.api, self)
 
+    def update(self, objects):
+        r"""Bulk updates existing objects on an endpoint.
+
+        Allows for bulk updating of existing objects on an endpoint.
+        Objects is a list whic contain either json/dicts or Record
+        derived objects, which contain the updates to apply.
+        If json/dicts are used, then the id of the object *must* be
+        included
+
+        :arg list objects: A list of dicts or Record.
+
+        :returns: True if the update succeeded
+
+        :Examples:
+
+        Updating objects on the `devices` endpoint:
+
+        >>> device = netbox.dcim.devices.update([
+        ...    {'id': 1, 'name': 'test'},
+        ...    {'id': 2, 'name': 'test2'},
+        ... ])
+        >>> True
+
+        Use bulk update by passing a list of Records:
+
+        >>> devices = nb.dcim.devices.all()
+        >>> for d in devices:
+        >>>     d.name = d.name+'-test'
+        >>> nb.dcim.devices.update(devices)
+        >>> True
+        """
+        series = []
+        if not isinstance(objects, list):
+            raise ValueError(
+                "Objects passed must be list[dict|Record] - was " + 
type(objects)
+            )
+        for o in objects:
+            if isinstance(o, Record):
+                data = o.updates()
+                if data:
+                    data["id"] = o.id
+                    series.append(data)
+            elif isinstance(o, dict):
+                if "id" not in o:
+                    raise ValueError("id is missing from object: " + str(o))
+                series.append(o)
+            else:
+                raise ValueError(
+                    "Object passed must be dict|Record - was " + type(objects)
+                )
+        req = Request(
+            base=self.url,
+            token=self.token,
+            session_key=self.session_key,
+            http_session=self.api.http_session,
+        ).patch(series)
+
+        if isinstance(req, list):
+            return [self.return_obj(i, self.api, self) for i in req]
+        return self.return_obj(req, self.api, self)
+
+    def delete(self, objects):
+        r"""Bulk deletes objects on an endpoint.
+
+        Allows for batch deletion of multiple objects from
+        a single endpoint
+
+        :arg list objects: A list of either ids or Records or
+            a single RecordSet to delete.
+        :returns: True if bulk DELETE operation was successful.
+
+        :Examples:
+
+        Deleting all `devices`:
+
+        >>> netbox.dcim.devices.delete(netbox.dcim.devices.all(0))
+        >>>
+
+        Use bulk deletion by passing a list of ids:
+
+        >>> netbox.dcim.devices.delete([2, 243, 431, 700])
+        >>>
+
+        Use bulk deletion to delete objects eg. when filtering
+        on a `custom_field`:
+        >>> netbox.dcim.devices.delete([
+        >>>         d for d in netbox.dcim.devices.all(0) \
+        >>>             if d.custom_fields.get('field', False)
+        >>>     ])
+        >>>
+        """
+        cleaned_ids = []
+        if not isinstance(objects, list) and not isinstance(objects, 
RecordSet):
+            raise ValueError(
+                "objects must be list[str|int|Record]"
+                "|RecordSet - was " + str(type(objects))
+            )
+        for o in objects:
+            if isinstance(o, int):
+                cleaned_ids.append(o)
+            elif isinstance(o, str) and o.isnumeric():
+                cleaned_ids.append(int(o))
+            elif isinstance(o, Record):
+                if not hasattr(o, "id"):
+                    raise ValueError(
+                        "Record from '"
+                        + o.url
+                        + "' does not have an id and cannot be bulk deleted"
+                    )
+                cleaned_ids.append(o.id)
+            else:
+                raise ValueError(
+                    "Invalid object in list of " "objects to delete: " + 
str(type(o))
+                )
+
+        req = Request(
+            base=self.url,
+            token=self.token,
+            session_key=self.session_key,
+            http_session=self.api.http_session,
+        )
+        return True if req.delete(data=[{"id": i} for i in cleaned_ids]) else 
False
+
     def choices(self):
         """ Returns all choices from the endpoint.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/pynetbox/core/query.py 
new/pynetbox-6.2.0/pynetbox/core/query.py
--- old/pynetbox-6.1.3/pynetbox/core/query.py   2021-07-31 01:24:46.000000000 
+0200
+++ new/pynetbox-6.2.0/pynetbox/core/query.py   2021-10-29 23:53:20.000000000 
+0200
@@ -245,7 +245,7 @@
         return url
 
     def _make_call(self, verb="get", url_override=None, add_params=None, 
data=None):
-        if verb in ("post", "put"):
+        if verb in ("post", "put") or verb == "delete" and data:
             headers = {"Content-Type": "application/json;"}
         else:
             headers = {"accept": "application/json;"}
@@ -386,18 +386,20 @@
         """
         return self._make_call(verb="post", data=data)
 
-    def delete(self):
+    def delete(self, data=None):
         """Makes DELETE request.
 
         Makes a DELETE request to NetBox's API.
 
+        :param data: (list) Contains a dict that will be turned into a
+            json object and sent to the API.
         Returns:
             True if successful.
 
         Raises:
             RequestError if req.ok doesn't return True.
         """
-        return self._make_call(verb="delete")
+        return self._make_call(verb="delete", data=data)
 
     def patch(self, data):
         """Makes PATCH request.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/pynetbox/core/response.py 
new/pynetbox-6.2.0/pynetbox/core/response.py
--- old/pynetbox-6.1.3/pynetbox/core/response.py        2021-07-31 
01:24:46.000000000 +0200
+++ new/pynetbox-6.2.0/pynetbox/core/response.py        2021-10-29 
23:53:20.000000000 +0200
@@ -127,6 +127,50 @@
                 return 0
             return self.request.count
 
+    def update(self, **kwargs):
+        """Updates kwargs onto all Records in the RecordSet and saves these.
+
+        Updates are only sent to the API if a value were changed, and only for
+        the Records which were changed
+
+        :returns: True if the update succeeded, None if no update were required
+        :example:
+
+        >>> result = nb.dcim.devices.filter(site_id=1).update(status='active')
+        True
+        >>>
+        """
+        updates = []
+        for record in self:
+            # Update each record and determine if anything was updated
+            for k, v in kwargs.items():
+                setattr(record, k, v)
+            record_updates = record.updates()
+            if record_updates:
+                # if updated, add the id to the dict and append to list of 
updates
+                record_updates["id"] = record.id
+                updates.append(record_updates)
+        if updates:
+            return self.endpoint.update(updates)
+        else:
+            return None
+
+    def delete(self):
+        r"""Bulk deletes objects in a RecordSet.
+
+        Allows for batch deletion of multiple objects in a RecordSet
+
+        :returns: True if bulk DELETE operation was successful.
+
+        :Examples:
+
+        Deleting offline `devices` on site 1:
+
+        >>> netbox.dcim.devices.filter(site_id=1, status="offline").delete()
+        >>>
+        """
+        return self.endpoint.delete(self)
+
 
 class Record(object):
     """Create python objects from netbox API responses.
@@ -429,6 +473,30 @@
         )
         return set([i[0] for i in set(current.items()) ^ set(init.items())])
 
+    def updates(self):
+        """Compiles changes for an existing object into a dict.
+
+        Takes a diff between the objects current state and its state at init
+        and returns them as a dictionary, which will be empty if no changes.
+
+        :returns: dict.
+        :example:
+
+        >>> x = nb.dcim.devices.get(name='test1-a3-tor1b')
+        >>> x.serial
+        u''
+        >>> x.serial = '1234'
+        >>> x.updates()
+        {'serial': '1234'}
+        >>>
+        """
+        if self.id:
+            diff = self._diff()
+            if diff:
+                serialized = self.serialize()
+                return {i: serialized[i] for i in diff}
+        return {}
+
     def save(self):
         """Saves changes to an existing object.
 
@@ -446,20 +514,17 @@
         True
         >>>
         """
-        if self.id:
-            diff = self._diff()
-            if diff:
-                serialized = self.serialize()
-                req = Request(
-                    key=self.id,
-                    base=self.endpoint.url,
-                    token=self.api.token,
-                    session_key=self.api.session_key,
-                    http_session=self.api.http_session,
-                )
-                if req.patch({i: serialized[i] for i in diff}):
-                    return True
-
+        updates = self.updates()
+        if updates:
+            req = Request(
+                key=self.id,
+                base=self.endpoint.url,
+                token=self.api.token,
+                session_key=self.api.session_key,
+                http_session=self.api.http_session,
+            )
+            if req.patch(updates):
+                return True
         return False
 
     def update(self, data):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/pynetbox.egg-info/PKG-INFO 
new/pynetbox-6.2.0/pynetbox.egg-info/PKG-INFO
--- old/pynetbox-6.1.3/pynetbox.egg-info/PKG-INFO       2021-07-31 
01:24:56.000000000 +0200
+++ new/pynetbox-6.2.0/pynetbox.egg-info/PKG-INFO       2021-10-29 
23:53:37.000000000 +0200
@@ -1,12 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: pynetbox
-Version: 6.1.3
+Version: 6.2.0
 Summary: NetBox API client library
 Home-page: https://github.com/digitalocean/pynetbox
 Author: Zach Moody
 Author-email: [email protected]
 License: Apache2
-Description: UNKNOWN
 Keywords: netbox
 Platform: UNKNOWN
 Classifier: Intended Audience :: Developers
@@ -15,3 +14,7 @@
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.6
+License-File: LICENSE
+
+UNKNOWN
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/tests/unit/test_endpoint.py 
new/pynetbox-6.2.0/tests/unit/test_endpoint.py
--- old/pynetbox-6.1.3/tests/unit/test_endpoint.py      2021-07-31 
01:24:46.000000000 +0200
+++ new/pynetbox-6.2.0/tests/unit/test_endpoint.py      2021-10-29 
23:53:20.000000000 +0200
@@ -71,6 +71,58 @@
             test = test_obj.get(name="test")
             self.assertEqual(test.id, 123)
 
+    def test_delete_with_ids(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            ids = [1, 3, 5]
+            mock.return_value = True
+            api = Mock(base_url="http://localhost:8000/api";)
+            app = Mock(name="test")
+            test_obj = Endpoint(api, app, "test")
+            test = test_obj.delete(ids)
+            mock.assert_called_with(verb="delete", data=[{"id": i} for i in 
ids])
+            self.assertTrue(test)
+
+    def test_delete_with_objects(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            from pynetbox.core.response import Record
+
+            ids = [1, 3, 5]
+            mock.return_value = True
+            api = Mock(base_url="http://localhost:8000/api";)
+            app = Mock(name="test")
+            test_obj = Endpoint(api, app, "test")
+            objects = [
+                Record({"id": i, "name": "dummy" + str(i)}, api, test_obj) for 
i in ids
+            ]
+            test = test_obj.delete(objects)
+            mock.assert_called_with(verb="delete", data=[{"id": i} for i in 
ids])
+            self.assertTrue(test)
+
+    def test_delete_with_recordset(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            from pynetbox.core.response import RecordSet
+
+            ids = [1, 3, 5]
+
+            class FakeRequest:
+                def get(self):
+                    return iter([{"id": i, "name": "dummy" + str(i)} for i in 
ids])
+
+            mock.return_value = True
+            api = Mock(base_url="http://localhost:8000/api";)
+            app = Mock(name="test")
+            test_obj = Endpoint(api, app, "test")
+            recordset = RecordSet(test_obj, FakeRequest())
+            test = test_obj.delete(recordset)
+            mock.assert_called_with(verb="delete", data=[{"id": i} for i in 
ids])
+            self.assertTrue(test)
+
     def test_get_greater_than_one(self):
         with patch(
             "pynetbox.core.query.Request._make_call", return_value=Mock()
@@ -92,3 +144,46 @@
             test_obj = Endpoint(api, app, "test")
             test = test_obj.get(name="test")
             self.assertIsNone(test)
+
+    def test_bulk_update_records(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            from pynetbox.core.response import Record
+
+            ids = [1, 3, 5]
+            mock.return_value = True
+            api = Mock(base_url="http://localhost:8000/api";)
+            app = Mock(name="test")
+            test_obj = Endpoint(api, app, "test")
+            objects = [
+                Record(
+                    {"id": i, "name": "dummy" + str(i), "unchanged": "yes"},
+                    api,
+                    test_obj,
+                )
+                for i in ids
+            ]
+            for o in objects:
+                o.name = "fluffy" + str(o.id)
+            mock.return_value = [o.serialize() for o in objects]
+            test = test_obj.update(objects)
+            mock.assert_called_with(
+                verb="patch", data=[{"id": i, "name": "fluffy" + str(i)} for i 
in ids]
+            )
+            self.assertTrue(test)
+
+    def test_bulk_update_json(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            ids = [1, 3, 5]
+            changes = [{"id": i, "name": "puffy" + str(i)} for i in ids]
+            mock.return_value = True
+            api = Mock(base_url="http://localhost:8000/api";)
+            app = Mock(name="test")
+            mock.return_value = changes
+            test_obj = Endpoint(api, app, "test")
+            test = test_obj.update(changes)
+            mock.assert_called_with(verb="patch", data=changes)
+            self.assertTrue(test)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pynetbox-6.1.3/tests/unit/test_response.py 
new/pynetbox-6.2.0/tests/unit/test_response.py
--- old/pynetbox-6.1.3/tests/unit/test_response.py      2021-07-31 
01:24:46.000000000 +0200
+++ new/pynetbox-6.2.0/tests/unit/test_response.py      2021-10-29 
23:53:20.000000000 +0200
@@ -2,12 +2,13 @@
 
 import six
 
-from pynetbox.core.response import Record
+from pynetbox.core.response import Record, RecordSet
+from pynetbox.core.endpoint import Endpoint
 
 if six.PY3:
-    from unittest.mock import Mock
+    from unittest.mock import patch, Mock, call
 else:
-    from mock import Mock
+    from mock import patch, Mock, call
 
 
 class RecordTestCase(unittest.TestCase):
@@ -346,3 +347,52 @@
         ]
         test = Record({"id": 123, "tags": test_tags}, None, None).serialize()
         self.assertEqual(test["tags"], test_tags)
+
+
+class RecordSetTestCase(unittest.TestCase):
+    ids = [1, 3, 5]
+
+    @classmethod
+    def init_recordset(cls):
+        data = [
+            {"id": i, "name": "dummy" + str(i), "status": "active"} for i in 
cls.ids
+        ]
+        api = Mock(base_url="http://localhost:8000/api";)
+        app = Mock(name="test")
+
+        class FakeRequest:
+            def get(self):
+                return iter(data)
+
+            def patch(self):
+                return iter(data)
+
+        return RecordSet(Endpoint(api, app, "test"), FakeRequest())
+
+    def test_delete(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            mock.return_value = True
+            test_obj = RecordSetTestCase.init_recordset()
+            test = test_obj.delete()
+            mock.assert_called_with(
+                verb="delete", data=[{"id": i} for i in RecordSetTestCase.ids]
+            )
+            self.assertTrue(test)
+
+    def test_update(self):
+        with patch(
+            "pynetbox.core.query.Request._make_call", return_value=Mock()
+        ) as mock:
+            mock.return_value = [
+                {"id": i, "name": "dummy" + str(i), "status": "offline"}
+                for i in RecordSetTestCase.ids
+            ]
+            test_obj = RecordSetTestCase.init_recordset()
+            test = test_obj.update(status="offline")
+            mock.assert_called_with(
+                verb="patch",
+                data=[{"id": i, "status": "offline"} for i in 
RecordSetTestCase.ids],
+            )
+            self.assertTrue(test)

Reply via email to