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)