Skip to content

Commit 8fdeb2c

Browse files
committed
fix: userviced_requests fix #110
1 parent 46ec025 commit 8fdeb2c

File tree

4 files changed

+101
-18
lines changed

4 files changed

+101
-18
lines changed

docs/user/advanced.rst

+4
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,7 @@ The following can improve performance:
132132
* Increase (or decrease) the default :ref:`batch_size` for GlideRecord
133133
* According to `KB0534905 <https://support.servicenow.com/kb_view.do?sysparm_article=KB0534905>`_ try disabling display values if they are not required via `gr.display_value = False`
134134
* Try setting a query :ref:`limit` if you do not need all results
135+
136+
2. Why am I consuming so much memory?
137+
138+
By default, GlideRecord can be 'rewound' in that it stores all previously fetched records in memory. This can be disabled by setting :ref:`rewind` to False on GlideRecord

pysnc/client.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,14 @@ def _transform_response(self, req: requests.PreparedRequest, serviced_request) -
365365

366366
return response
367367

368-
def execute(self):
368+
def execute(self, attempt=0):
369+
if attempt > 2:
370+
# just give up and tell em we tried
371+
for h in self.__hooks:
372+
self.__hooks[h](None)
373+
self.__hooks = {}
374+
self.__requests = []
375+
self.__stored_requests = {}
369376
bid = self._next_id()
370377
body = {
371378
'batch_request_id': bid,
@@ -374,20 +381,18 @@ def execute(self):
374381
r = self.session.post(self._batch_target(), json=body)
375382
self._validate_response(r)
376383
data = r.json()
377-
try:
378-
assert str(bid) == data['batch_request_id'], f"How did we get a response id different from {bid}"
379-
380-
for response in data['serviced_requests'] + data['unserviced_requests']:
381-
response_id = response['id']
382-
assert response_id in self.__hooks, f"Somehow has no hook for {response_id}"
383-
assert response_id in self.__stored_requests, f"Somehow we did not store request for {response_id}"
384-
self.__hooks[response['id']](self._transform_response(self.__stored_requests[response_id], response))
385-
386-
return bid
387-
finally:
388-
self.__stored_requests = {}
389-
self.__requests = []
390-
self.__hooks = {}
384+
assert str(bid) == data['batch_request_id'], f"How did we get a response id different from {bid}"
385+
386+
for response in data['serviced_requests']:
387+
response_id = response['id']
388+
assert response_id in self.__hooks, f"Somehow has no hook for {response_id}"
389+
assert response_id in self.__stored_requests, f"Somehow we did not store request for {response_id}"
390+
self.__hooks[response['id']](self._transform_response(self.__stored_requests.pop(response_id), response))
391+
del self.__hooks[response_id]
392+
self.__requests = list(filter(lambda x: x['id'] != response_id, self.__requests))
393+
394+
if len(data['unserviced_requests']) > 0:
395+
self.execute(attempt=attempt+1)
391396

392397
def get(self, record: GlideRecord, sys_id: str, hook: Callable) -> None:
393398
params = self._set_params(record)

pysnc/record.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ def delete_multiple(self) -> bool:
706706
allRecordsWereDeleted = True
707707
def handle(response):
708708
nonlocal allRecordsWereDeleted
709-
if response.status_code != 204:
709+
if response is None or response.status_code != 204:
710710
allRecordsWereDeleted = False
711711

712712
for e in self:
@@ -716,12 +716,17 @@ def handle(response):
716716

717717
def update_multiple(self, custom_handler=None) -> bool:
718718
"""
719-
Updates multiple records at once
719+
Updates multiple records at once. A ``custom_handler`` of the form ``def handle(response: requests.Response | None)`` can be passed in,
720+
which may be useful if you wish to handle errors in a specific way. Note that if a custom_handler is used this
721+
method will always return ``True``
722+
723+
724+
:return: ``True`` on success, ``False`` if any records failed. If custom_handler is specified, always returns ``True``
720725
"""
721726
updated = True
722727
def handle(response):
723728
nonlocal updated
724-
if response.status_code != 200:
729+
if response is None or response.status_code != 200:
725730
updated = False
726731

727732
for e in self:

test/test_snc_api_write.py

+69
Original file line numberDiff line numberDiff line change
@@ -254,5 +254,74 @@ def custom_handle(response):
254254
tgr.delete_multiple()
255255
client.session.close()
256256

257+
def test_multi_update_with_failures(self):
258+
client = ServiceNowClient(self.c.server, self.c.credentials)
259+
br = client.GlideRecord('sys_script')
260+
261+
# in order to test the failure case, let's insert a BR that will reject specific values
262+
br.add_query('name', 'test_multi_update_with_failures')
263+
br.query()
264+
if not br.next():
265+
br.initialize()
266+
br.name = 'test_multi_update_with_failures'
267+
br.collection = 'problem'
268+
br.active = True
269+
br.when = 'before'
270+
br.order = 100
271+
br.action_insert = True
272+
br.action_update = True
273+
br.abort_action = True
274+
br.add_message = True
275+
br.message = 'rejected by test_multi_update_with_failures br'
276+
br.filter_condition = 'short_descriptionLIKEEICAR^ORdescriptionLIKEEICAR^EQ'
277+
br.insert()
278+
279+
280+
gr = client.GlideRecord('problem')
281+
gr.add_query('short_description', 'LIKE', 'BUNKZZ')
282+
gr.query()
283+
self.assertTrue(gr.delete_multiple()) # try to make sure weh ave none first
284+
gr.query()
285+
self.assertEqual(len(gr), 0, 'should have had none left')
286+
257287

288+
total_count = 10
289+
# insert five bunk records
290+
# TODO: insert multiple
291+
for i in range(total_count):
292+
gr = client.GlideRecord('problem')
293+
gr.initialize()
294+
gr.short_description = f"Unit Test - BUNKZZ Multi update {i}"
295+
assert gr.insert(), "Failed to insert a record"
296+
297+
gr = client.GlideRecord('problem')
298+
gr.add_query('short_description', 'LIKE', 'BUNKZZ')
299+
gr.query()
300+
self.assertEqual(len(gr), total_count)
301+
302+
i = 0
303+
# half append
304+
print(f"for i < {total_count//2}")
305+
while i < (total_count//2) and gr.next():
306+
gr.short_description = gr.short_description + ' -- APPENDEDZZ'
307+
i += 1
308+
# half error
309+
while gr.next():
310+
gr.short_description = gr.short_description + ' -- EICAR'
311+
312+
self.assertFalse(gr.update_multiple())
313+
# make sure we cleaned up as expected
314+
self.assertEqual(gr._client.batch_api._BatchAPI__hooks, {})
315+
self.assertEqual(gr._client.batch_api._BatchAPI__stored_requests, {})
316+
self.assertEqual(gr._client.batch_api._BatchAPI__requests, [])
317+
318+
tgr = client.GlideRecord('problem')
319+
tgr.add_query('short_description', 'LIKE', 'APPENDEDZZ')
320+
tgr.query()
321+
self.assertEqual(len(tgr), total_count//2)
322+
323+
tgr = client.GlideRecord('problem')
324+
tgr.add_query('short_description', 'LIKE', 'EICAR')
325+
tgr.query()
326+
self.assertEqual(len(tgr), 0)
258327

0 commit comments

Comments
 (0)