Skip to content

Commit

Permalink
Merge pull request #37 from abulgher/timeout
Browse files Browse the repository at this point in the history
Allow request post and get to timeout
  • Loading branch information
simongregorebner authored Nov 8, 2022
2 parents d2cd489 + e141e33 commit 3693184
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 23 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ message, attributes, attachments = logbook.read(23)

```python
# Create new message with some text, attributes (dict of attributes + kwargs) and attachments
new_msg_id = logbook.post('This is message text', attributes=dict_of_attributes, attachments=list_of_attachments, attribute_as_param='value')
new_msg_id = logbook.post('This is message text', attributes=dict_of_attributes, attachments=list_of_attachments,
attribute_as_param='value')
```

What attributes are required is determined by the configuration of the elog server (keywork `Required Attributes`).
Expand Down Expand Up @@ -76,14 +77,16 @@ new_msg_id = logbook.post('This is message text', author='me', type='Routine')

```python
# Reply to message with ID=23
new_msg_id = logbook.post('This is a reply', msg_id=23, reply=True, attributes=dict_of_attributes, attachments=list_of_attachments, attribute_as_param='value')
new_msg_id = logbook.post('This is a reply', msg_id=23, reply=True, attributes=dict_of_attributes,
attachments=list_of_attachments, attribute_as_param='value')
```

## Edit Message

```python
# Edit message with ID=23. Changed message text, some attributes (dict of edited attributes + kwargs) and new attachments
edited_msg_id = logbook.post('This is new message text', msg_id=23, attributes=dict_of_changed_attributes, attachments=list_of_new_attachments, attribute_as_param='new value')
edited_msg_id = logbook.post('This is new message text', msg_id=23, attributes=dict_of_changed_attributes,
attachments=list_of_new_attachments, attribute_as_param='new value')
```

## Search Messages
Expand All @@ -92,7 +95,7 @@ edited_msg_id = logbook.post('This is new message text', msg_id=23, attributes=d
# Search for text in messages or specify attributes for search, returns list of message ids
logbook.search('Hello World')
logbook.search('Hello World', n_results=20, scope='attribname')
logbook.search({'attribname' : 'Hello World', ... })
logbook.search({'attribname': 'Hello World', ...})
```

## Delete Message (and all its replies)
Expand Down
80 changes: 64 additions & 16 deletions elog/logbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import builtins
import re
import sys
from elog.logbook_exceptions import *
from datetime import datetime

Expand Down Expand Up @@ -105,8 +106,8 @@ def __init__(self, hostname, logbook='', port=None, user=None, password=None, su
self._user = user
self._password = _handle_pswd(password, encrypt_pwd)

def post(self, message, msg_id=None, reply=False, attributes=None, attachments=None,
suppress_email_notification=False, encoding=None, **kwargs):
def post(self, message, msg_id=None, reply=False, attributes=None, attachments=None,
suppress_email_notification=False, encoding=None, timeout=None, **kwargs):
"""
Posts message to the logbook. If msg_id is not specified new message will be created, otherwise existing
message will be edited, or a reply (if reply=True) to it will be created. This method returns the msg_id
Expand All @@ -123,9 +124,11 @@ def post(self, message, msg_id=None, reply=False, attributes=None, attachments=N
- paths to the files
All items will be appended as attachment to the elog entry. In case of unknown
attachment an exception LogbookInvalidAttachment will be raised.
:param suppress_email_notification: If set to True or 1, E-Mail notification will be suppressed, defaults to False.
:param encoding: Defines encoding of the message. Can be: 'plain' -> plain text, 'html'->html-text,
'ELCode' --> elog formatting syntax
:param suppress_email_notification: If set to True or 1, E-Mail notification will be suppressed, defaults to False.
:param timeout: Define the timeout to be used by the post request. Its value is directly passed to the requests
post. Use None to disable the request timeout.
:param kwargs: Anything in the kwargs will be interpreted as attribute. e.g.: logbook.post('Test text',
Author='Rok Vintar), "Author" will be sent as an attribute. If named same as one of the
attributes defined in "attributes", kwargs will have priority.
Expand Down Expand Up @@ -207,7 +210,7 @@ def post(self, message, msg_id=None, reply=False, attributes=None, attachments=N

try:
response = requests.post(self._url, data=attributes_to_edit, files=files_to_attach, allow_redirects=False,
verify=False)
verify=False, timeout=timeout)

# Validate response. Any problems will raise an Exception.
resp_message, resp_headers, resp_msg_id = _validate_response(response)
Expand All @@ -217,6 +220,12 @@ def post(self, message, msg_id=None, reply=False, attributes=None, attachments=N
if hasattr(file_like_object, 'close'):
file_like_object.close()

except requests.Timeout as e:
# Catch here a timeout o the post request.
# Raise the logbook excetion and let the user handle it
raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n'+
'{1}'.format(sys._getframe().f_code.co_name, e))

except requests.RequestException as e:
# Check if message on server.
self._check_if_message_on_server(msg_id) # raises exceptions if no message or no response from server
Expand All @@ -230,14 +239,15 @@ def post(self, message, msg_id=None, reply=False, attributes=None, attachments=N
raise LogbookInvalidMessageID('Invalid message ID: ' + str(resp_msg_id) + ' returned')
return resp_msg_id

def read(self, msg_id):
def read(self, msg_id, timeout=None):
"""
Reads message from the logbook server and returns tuple of (message, attributes, attachments) where:
message: string with message body
attributes: dictionary of all attributes returned by the logbook
attachments: list of urls to attachments on the logbook server
:param msg_id: ID of the message to be read
:param timeout: The timeout value to be passed to the get request.
:return: message, attributes, attachments
"""

Expand All @@ -248,11 +258,21 @@ def read(self, msg_id):
try:
self._check_if_message_on_server(msg_id) # raises exceptions if no message or no response from server
response = requests.get(self._url + str(msg_id) + '?cmd=download', headers=request_headers,
allow_redirects=False, verify=False)
allow_redirects=False, verify=False, timeout=timeout)

# Validate response. If problems Exception will be thrown.
resp_message, resp_headers, resp_msg_id = _validate_response(response)


except requests.Timeout as e:

# Catch here a timeout o the post request.

# Raise the logbook excetion and let the user handle it

raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n' +
'{1}'.format(sys._getframe().f_code.co_name, e))

except requests.RequestException as e:
# If here: message is on server but cannot be downloaded (should never happen)
raise LogbookServerProblem('Cannot access logbook server to read the message with ID: ' + str(msg_id) +
Expand Down Expand Up @@ -284,12 +304,13 @@ def read(self, msg_id):

return message, attributes, attachments

def delete(self, msg_id):
def delete(self, msg_id, timeout=None):
"""
Deletes message thread (!!!message + all replies!!!) from logbook.
It also deletes all of attachments of corresponding messages from the server.
:param msg_id: message to be deleted
:param timeout: timeout value to be passed to the get request
:return:
"""

Expand All @@ -301,10 +322,16 @@ def delete(self, msg_id):
self._check_if_message_on_server(msg_id) # check if something to delete

response = requests.get(self._url + str(msg_id) + '?cmd=Delete&confirm=Yes', headers=request_headers,
allow_redirects=False, verify=False)
allow_redirects=False, verify=False, timeout=timeout)

_validate_response(response) # raises exception if any other error identified

except requests.Timeout as e:
# Catch here a timeout o the post request.
# Raise the logbook excetion and let the user handle it
raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n'+
'{1}'.format(sys._getframe().f_code.co_name, e))

except requests.RequestException as e:
# If here: message is on server but cannot be downloaded (should never happen)
raise LogbookServerProblem('Cannot access logbook server to delete the message with ID: ' + str(msg_id) +
Expand All @@ -316,10 +343,12 @@ def delete(self, msg_id):
if response.status_code == 200:
raise LogbookServerProblem('Cannot process delete command (only logbooks in English supported).')

def search(self, search_term, n_results=20, scope="subtext"):
def search(self, search_term, n_results=20, scope="subtext", timeout=None):
"""
Searches the logbook and returns the message ids.
:param timeout: timeout value to be passed to the get request
"""
request_headers = dict()
if self._user or self._password:
Expand Down Expand Up @@ -347,12 +376,18 @@ def search(self, search_term, n_results=20, scope="subtext"):

try:
response = requests.get(self._url, params=params, headers=request_headers,
allow_redirects=False, verify=False)
allow_redirects=False, verify=False, timeout=timeout)

# Validate response. If problems Exception will be thrown.
_validate_response(response)
resp_message = response

except requests.Timeout as e:
# Catch here a timeout o the post request.
# Raise the logbook excetion and let the user handle it
raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n'+
'{1}'.format(sys._getframe().f_code.co_name, e))

except requests.RequestException as e:
# If here: message is on server but cannot be downloaded (should never happen)
raise LogbookServerProblem('Cannot access logbook server to read message ids '
Expand All @@ -364,26 +399,32 @@ def search(self, search_term, n_results=20, scope="subtext"):
message_ids = [int(m.split("/")[-1]) for m in message_ids]
return message_ids

def get_last_message_id(self):
ids = self.get_message_ids()
def get_last_message_id(self, timeout=None):
ids = self.get_message_ids(timeout)
if len(ids) > 0:
return ids[0]
else:
return None

def get_message_ids(self):
def get_message_ids(self, timeout=None):
request_headers = dict()
if self._user or self._password:
request_headers['Cookie'] = self._make_user_and_pswd_cookie()

try:
response = requests.get(self._url + 'page', headers=request_headers,
allow_redirects=False, verify=False)
allow_redirects=False, verify=False, timeout=timeout)

# Validate response. If problems Exception will be thrown.
_validate_response(response)
resp_message = response

except requests.Timeout as e:
# Catch here a timeout o the post request.
# Raise the logbook excetion and let the user handle it
raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n'+
'{1}'.format(sys._getframe().f_code.co_name, e))

except requests.RequestException as e:
# If here: message is on server but cannot be downloaded (should never happen)
raise LogbookServerProblem('Cannot access logbook server to read message ids '
Expand All @@ -395,11 +436,12 @@ def get_message_ids(self):
message_ids = [int(m.split("/")[-1]) for m in message_ids]
return message_ids

def _check_if_message_on_server(self, msg_id):
def _check_if_message_on_server(self, msg_id, timeout=None):
"""Try to load page for specific message. If there is a htm tag like <td class="errormsg"> then there is no
such message.
:param msg_id: ID of message to be checked
:params timeout: The value of timeout to be passed to the get request
:return:
"""

Expand All @@ -408,7 +450,7 @@ def _check_if_message_on_server(self, msg_id):
request_headers['Cookie'] = self._make_user_and_pswd_cookie()
try:
response = requests.get(self._url + str(msg_id), headers=request_headers, allow_redirects=False,
verify=False)
verify=False, timeout=timeout)

# If there is no message code 200 will be returned (OK) and _validate_response will not recognise it
# but there will be some error in the html code.
Expand All @@ -420,6 +462,12 @@ def _check_if_message_on_server(self, msg_id):
flags=re.DOTALL):
raise LogbookInvalidMessageID('Message with ID: ' + str(msg_id) + ' does not exist on logbook.')

except requests.Timeout as e:
# Catch here a timeout o the post request.
# Raise the logbook excetion and let the user handle it
raise LogbookServerTimeout('{0} method cannot be completed because of a network timeout:\n'+
'{1}'.format(sys._getframe().f_code.co_name, e))

except requests.RequestException as e:
raise LogbookServerProblem('No response from the logbook server.\nDetails: ' + '{0}'.format(e))

Expand Down
2 changes: 2 additions & 0 deletions elog/logbook_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class LogbookAuthenticationError(LogbookError):
""" Raise when problem with username and password."""
pass

class LogbookServerTimeout(LogbookError):
""" Raise when the request to the logbook server timeouts. """

class LogbookServerProblem(LogbookError):
""" Raise when problem accessing logbook server."""
Expand Down
13 changes: 10 additions & 3 deletions tests/test_logbook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
# import logging
import elog
from elog.logbook_exceptions import *



Expand All @@ -19,10 +20,16 @@ def test_get_message_ids(self):
def test_get_last_message_id(self):

logbook = elog.open(self.elog_hostname)
msg_id = logbook.post(self.message, attributes={'Author':'AB', 'Type':'Routine'})
msg_id = logbook.post(self.message, attributes={'Author': 'AB', 'Type': 'Routine'})
message_id = logbook.get_last_message_id()
self.assertEqual(msg_id, message_id, "Created message does not show up as last edited message")


def test_get_last_message_id_with_short_timeout(self):

logbook = elog.open(self.elog_hostname)
self.assertRaises(LogbookServerTimeout, logbook.post,
self.message, attributes={'Author': 'AB', 'Type': 'Routine'}, timeout=0.01)

def test_edit(self):
logbook = elog.open(self.elog_hostname)
logbook.post('hehehehehe', msg_id=logbook.get_last_message_id(), attributes={"Subject": 'py_elog test [mod]'})
Expand All @@ -44,7 +51,7 @@ def test_post_special_characters(self):
attributes = { 'Author' : 'Me', 'Type' : 'Other', 'Category' : 'General',
'Subject' : 'This is a test of UTF-8 characters like èéöä'}
message = 'Just to be clear this is a general test using UTF-8 characters like èéöä.'
msg_id = logbook.post(message, reply=False, attributes=attributes,encoding='HTML')
msg_id = logbook.post(message, reply=False, attributes=attributes, encoding='HTML')
read_msg, read_attr, read_att = logbook.read(msg_id)

mess_ok = message == read_msg
Expand Down

0 comments on commit 3693184

Please sign in to comment.