commit python-pynetbox for openSUSE:Factory
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 <mardnh@gmx.de> + +- 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: zmoody@do.co 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: zmoody@do.co 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)
participants (1)
-
Source-Sync