def send_sms(From='', To='', Cc='', Bcc='', Message='', language='En', sms_template='', data_record={}, application_name='', caller_area={}): """ send_sms (wrapper) """ _process_name = 'send_sms' _process_entity = 'sms' _process_action = 'send_sms' _process_msgID = f'process:[{_process_name}]' _process_identity_kwargs = {'type': 'process', 'module': module_id, 'name': _process_name, 'action': _process_action, 'entity': _process_entity, 'msgID': _process_msgID,} _process_adapters_kwargs = {'dbsession': None} _process_log_kwargs = {'indent_method': 'AUTO', 'indent_level': None} _process_debug_level = get_debug_level(caller_area.get('debug_level'), **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_files = get_debug_files(_process_debug_level, **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_kwargs={'debug_level':_process_debug_level,'debug_files':_process_debug_files} _process_signature = build_process_signature(**_process_identity_kwargs, **_process_adapters_kwargs, **_process_debug_kwargs, **_process_log_kwargs) _process_call_area = build_process_call_area(_process_signature, caller_area) log_process_start(_process_msgID,**_process_call_area) log_process_input('', 'From', From,**_process_call_area) log_process_input('', 'To', To,**_process_call_area) log_process_input('', 'Cc', Cc,**_process_call_area) log_process_input('', 'Bcc', Bcc,**_process_call_area) log_process_input('', 'Message', Message, **_process_call_area) log_process_input('', 'language', language, **_process_call_area) log_process_input('', 'sms_template', sms_template, **_process_call_area) log_process_input('', 'application_name', application_name, **_process_call_area) log_process_input('', 'caller_area', caller_area, **_process_call_area) if not From: From = thisApp.application_configuration.get('sms_sender') log_process_data('', 'From', From,**_process_call_area) if not From: From = application_name log_process_data('', 'From', From,**_process_call_area) if not(From): msg = f'sms sender not defined' api_result = {'api_status': 'error', 'api_message': msg} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result if not(To): msg = f'sms recipient not defined' api_result = {'api_status': 'error', 'api_message': msg} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result phone_number = get_validated_phone_number(To) if not phone_number.get('api_status') == 'success': msg=phone_number.get('api_message','?') msg = f'invalid recipient number {To}. ({msg})' api_result = {'api_status': 'error', 'api_message': msg} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result To=phone_number.get('international_number') if not(To): msg = f'system error' api_result = {'api_status': 'error', 'api_message': msg} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result if not(Message): msg = f'Message not defined' api_result = {'api_status': 'error', 'api_message': msg} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result if not(Message) and not(sms_template): msg = f'no message or template defined' api_result = {'api_status': 'error', 'api_message': msg} log_process_message('', 'warning', msg,**_process_call_area) else: if sms_template: (t1, t2, t3) = get_template(sms_template,application_name,language) if t1 or t2 or t3: Message = t1 else: msg = f'sms template {sms_template} not found' api_result = {'api_status': 'error', 'api_message': msg} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result if Message.find('#')>=0: Message = string_translate(Message, data_record) log_process_data('', 'translated Message', Message,**_process_call_area) SMS_SERVER_PROVIDER = thisApp.application_configuration.get('SMS_SERVER_PROVIDER') log_process_parameter('', 'config param', 'SMS_SERVER_PROVIDER', SMS_SERVER_PROVIDER, **_process_call_area) if not SMS_SERVER_PROVIDER: SMS_SERVER_PROVIDER = 'CYTA' log_process_parameter('', 'default config param', 'SMS_SERVER_PROVIDER', SMS_SERVER_PROVIDER, **_process_call_area) country_code = phone_number.get('country_code') if country_code == '357': SMS_SERVER_PROVIDER = 'CYTA' log_process_parameter('', 'set config param', 'SMS_SERVER_PROVIDER', SMS_SERVER_PROVIDER, **_process_call_area) To = phone_number.get('national_number') log_process_data('', 'To national number', To,**_process_call_area) else: SMS_SERVER_PROVIDER = 'NEXMO' log_process_parameter('', 'set config param', 'SMS_SERVER_PROVIDER', SMS_SERVER_PROVIDER, **_process_call_area) To = phone_number.get('international_number') log_process_data('', 'To international number', To,**_process_call_area) try: if SMS_SERVER_PROVIDER.upper() == 'SINCH': send_result = sendSMS_through_SINCH(From, To, Cc, Bcc, Message, language, caller_area=_process_call_area) else: if SMS_SERVER_PROVIDER == 'NEXMO': send_result = sendSMS_through_NEXMO(From, To, Cc, Bcc, Message, language, caller_area=_process_call_area) else: send_result = sendSMS_through_CYTA(From, To, Cc, Bcc, Message, language, caller_area=_process_call_area) except Exception as error_text: msg= f'sms send failed. system error:{error_text}' log_process_message('', 'error', msg,**_process_call_area) provider_reply={'provider_reply':f'exception occurred executing provider api','reply_code':'99'} api_result = {'api_status': 'error', 'api_message': msg,'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result if send_result.get('api_status')=='success': smstext=Message[0:5]+'***' msg = f'OK. sms sent To [{To}] from [{From}] with Text {smstext}' api_result = send_result api_result.update({'api_message': msg}) else: api_result = send_result # provider_reply = {'provider_reply': reply, 'reply_code': status_code,'reply_message':reply_message} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result
def sendSMS_through_SINCH(From, To, Cc, Bcc, Message, language='En', caller_area={}): """ sendSMS_through_SINCH """ _process_name = 'sendSMS_through_SINCH' _process_entity = 'sms' _process_action = 'send_sms' _process_msgID = f'process:[{_process_name}]' _process_identity_kwargs = {'type': 'process', 'module': module_id, 'name': _process_name, 'action': _process_action, 'entity': _process_entity, 'msgID': _process_msgID,} _process_adapters_kwargs = {'dbsession': None} _process_log_kwargs = {'indent_method': 'AUTO', 'indent_level': None} _process_debug_level = get_debug_level(caller_area.get('debug_level'), **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_files = get_debug_files(_process_debug_level, **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_kwargs={'debug_level':_process_debug_level,'debug_files':_process_debug_files} _process_signature = build_process_signature(**_process_identity_kwargs, **_process_adapters_kwargs, **_process_debug_kwargs, **_process_log_kwargs) _process_call_area = build_process_call_area(_process_signature, caller_area) log_process_start(_process_msgID,**_process_call_area) log_process_input('', 'From', From,**_process_call_area) log_process_input('', 'To', To,**_process_call_area) log_process_input('', 'Cc', Cc,**_process_call_area) log_process_input('', 'Bcc', Bcc,**_process_call_area) log_process_input('', 'language', language, **_process_call_area) log_process_input('', 'Message', Message, **_process_call_area) log_process_input('', 'caller_area', caller_area, **_process_call_area) SMS_SERVER_SINCH_API_KEY = thisApp.application_configuration.get('SMS_SERVER_SINCH_API_KEY') SMS_SERVER_SINCH_API_SECRET = thisApp.application_configuration.get('SMS_SERVER_SINCH_API_SECRET') SMS_SERVER_SINCH_FROM_NUMBER = thisApp.application_configuration.get('SMS_SERVER_SINCH_FROM_NUMBER') # SMS_SERVER_SINCH_API_KEY = 'be0c283385204338815a88ae81add209' # SMS_SERVER_SINCH_API_SECRET = '1d5c5cab2725437bbd6d68298f78f7b3' # SMS_SERVER_SINCH_FROM_NUMBER = '35799599819' log_process_parameter('', 'config param', 'SMS_SERVER_SINCH_FROM_NUMBER', SMS_SERVER_SINCH_FROM_NUMBER, **_process_call_area) log_process_parameter('', 'config param', 'SMS_SERVER_SINCH_API_KEY', SMS_SERVER_SINCH_API_KEY, **_process_call_area) log_process_parameter('', 'config param', 'SMS_SERVER_SINCH_API_SECRET', SMS_SERVER_SINCH_API_SECRET, **_process_call_area) try: msg='start sending SMS using SINCH' log_process_message('', '', msg,**_process_call_area) if caller_area.get('sms_simulation'): reply='sms_simulation:success' status_code = '0' reply_message = "simulated sms send" sms_uid = "simulated_sms:" log_process_message('','success',"SIMULATION:Message sent successfully.") else: # Create a new SinchSMS Client object: client = SinchSMS(SMS_SERVER_SINCH_API_KEY, SMS_SERVER_SINCH_API_SECRET) # Send the SMS message: response = client.send_message(To, Message) log_process_data('', 'response', response, **_process_call_area) message_id = response['messageId'] sms_uid = message_id response = client.check_status(message_id) ix=0 while response['status'] != 'Successful': ix = ix + 1 log_process_data('', f'{ix}. status', response['status'], **_process_call_area) time.sleep(1) response = client.check_status(message_id) reply = response status_code = response['status'] reply_message = response['status'] provider_reply = {'provider_reply': reply, 'reply_code': status_code,'reply_message':reply_message} if not response['status'] == 'Successful': msg= f'sending SMS provider error:{status_code}-{reply_message}' log_process_message('', 'error', msg,**_process_call_area) else: msg='OK. SMS sent using SINCH' log_process_message('', 'success', msg,**_process_call_area) except Exception as error_text: msg= f'sending SMS system error:{error_text}' log_process_message('', 'error', msg,**_process_call_area) reply='exception occurred executing NEXMO client api' status_code = '99' reply_message = msg sms_uid='' provider_reply = {'provider_reply': reply, 'reply_code': status_code, 'reply_message': reply_message, 'provider': 'SINCH', 'provider_send_id': sms_uid} if not status_code=='0': api_result = {'api_status': 'error', 'api_message': msg,'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result msg = f'SMS sent To [{To}] from [{From}] with Text [{Message}]' smstext=Message[0:5]+'***' msg= f'SMS sent To [{To}] from [{From}] with Text {smstext}' api_result = {'api_status': 'success', 'api_message': msg, 'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result
def sendSMS_through_CYTA(From, To, Cc, Bcc, Message, language='En', caller_area={}): """ sendSMS_through_CYTA """ _process_name = 'sendSMS_through_CYTA' _process_entity = 'sms' _process_action = 'send_sms' _process_msgID = f'process:[{_process_name}]' _process_identity_kwargs = {'type': 'process', 'module': module_id, 'name': _process_name, 'action': _process_action, 'entity': _process_entity, 'msgID': _process_msgID,} _process_adapters_kwargs = {'dbsession': None} _process_log_kwargs = {'indent_method': 'AUTO', 'indent_level': None} _process_debug_level = get_debug_level(caller_area.get('debug_level'), **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_files = get_debug_files(_process_debug_level, **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_kwargs={'debug_level':_process_debug_level,'debug_files':_process_debug_files} _process_signature = build_process_signature(**_process_identity_kwargs, **_process_adapters_kwargs, **_process_debug_kwargs, **_process_log_kwargs) _process_call_area = build_process_call_area(_process_signature, caller_area) log_process_start(_process_msgID,**_process_call_area) log_process_input('', 'From', From,**_process_call_area) log_process_input('', 'To', To,**_process_call_area) log_process_input('', 'Cc', Cc,**_process_call_area) log_process_input('', 'Bcc', Bcc,**_process_call_area) log_process_input('', 'language', language, **_process_call_area) log_process_input('', 'Message', Message, **_process_call_area) log_process_input('', 'caller_area', caller_area, **_process_call_area) SMS_SERVER_CYTA_USERNAME = thisApp.application_configuration.get('SMS_SERVER_CYTA_USERNAME') SMS_SERVER_CYTA_SECRETKEY = thisApp.application_configuration.get('SMS_SERVER_CYTA_SECRETKEY') SMS_SERVER_CYTA_SMS_SENDER = thisApp.application_configuration.get('SMS_SERVER_CYTA_SMS_SENDER') SMS_SERVER_CYTA_URL = thisApp.application_configuration.get('SMS_SERVER_CYTA_URL') # SMS_SERVER_CYTA_URL = 'https://www.cyta.com.cy/cytamobilevodafone/dev/websmsapi/sendsms.aspx' # SMS_SERVER_CYTA_USERNAME = '******' # SMS_SERVER_CYTA_SECRETKEY = 'f69f0d4702814d1fa1768f397ce9b485' # SMS_SERVER_CYTA_SMS_SENDER = 'GanimidesT' log_process_parameter('', 'config param', 'SMS_SERVER_CYTA_URL', SMS_SERVER_CYTA_URL, **_process_call_area) log_process_parameter('', 'config param', 'SMS_SERVER_CYTA_SMS_SENDER', SMS_SERVER_CYTA_SMS_SENDER, **_process_call_area) log_process_parameter('', 'config param', 'SMS_SERVER_CYTA_USERNAME', SMS_SERVER_CYTA_USERNAME, **_process_call_area) log_process_parameter('', 'config param', 'SMS_SERVER_CYTA_SECRETKEY', SMS_SERVER_CYTA_SECRETKEY, **_process_call_area) From = SMS_SERVER_CYTA_SMS_SENDER request_xml=f"""<?xml version="1.0" encoding="UTF-8" ?><websmsapi><version>1.0</version><username>{SMS_SERVER_CYTA_USERNAME}</username><secretkey>{SMS_SERVER_CYTA_SECRETKEY}</secretkey><recipients><count>1</count><mobiles><m>{To}</m></mobiles></recipients><message>{Message}</message><language>{language}</language></websmsapi>""" #print(request_xml) log_process_data('', 'From', From, **_process_call_area) log_process_data('', 'request_xml', request_xml, **_process_call_area) try: msg='start sending SMS using CYTA web api' log_process_message('', '', msg,**_process_call_area) # headers = {'Content-Type': 'application/xml; charset=utf-8', 'Content-length': len(request_xml), 'Connection': 'close',} headers = {'Content-Type': 'application/xml; charset=utf-8'} if caller_area.get('sms_simulation'): reply='sms_simulation:success' status_code = '0' reply_message = 'simulated sms send' sms_uid='simulated_sms:' else: r = requests.post(SMS_SERVER_CYTA_URL, headers=headers, data=request_xml) if r.status_code in (200, 201): if r.headers.get('Content-Type','')=='application/json': log_process_data('', 'response', str(r.json()), **_process_call_area) reply = r.json() status_code = reply.get('status', 99) else: reply = r.text log_process_data('', 'response', reply, **_process_call_area) r = ET.fromstring(reply) #xml parse from string status_node = r.find('status') try: status_code = status_node.text except: status_code = '99' lot_node = r.find('lot') try: sms_uid = lot_node.text except: sms_uid = 'failed_sms:' else: status_code = '99' sms_uid = 'failed_sms:' if not status_code=='0': error_text = get_cyta_error_message(status_code) reply_message=error_text else: reply_message = "" if not status_code=='0': msg= f'sending SMS provider error:{status_code}-{reply_message}' log_process_message('', 'error', msg,**_process_call_area) else: msg='SMS sent using CYTA web api' log_process_message('', 'success', msg,**_process_call_area) except Exception as error_text: msg= f'sending SMS system error:{error_text}' log_process_message('', 'error', msg,**_process_call_area) reply='exception occurred executing CYTA web api' status_code = '99' reply_message = msg sms_uid='' provider_reply = {'provider_reply': reply, 'reply_code': status_code, 'reply_message': reply_message, 'provider': 'CYTA', 'provider_send_id': sms_uid} if not status_code=='0': api_result = {'api_status': 'error', 'api_message': msg,'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result msg = f'SMS sent To [{To}] from [{From}] with Text [{Message}]' smstext=Message[0:5]+'***' msg= f'SMS sent To [{To}] from [{From}] with Text {smstext}' api_result = {'api_status': 'success', 'api_message': msg, 'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result
def sendSMS_through_NEXMO(From, To, Cc, Bcc, Message, language='En', caller_area={}): """ sendSMS_through_NEXMO """ _process_name = 'sendSMS_through_NEXMO' _process_entity = 'sms' _process_action = 'send_sms' _process_msgID = f'process:[{_process_name}]' _process_identity_kwargs = {'type': 'process', 'module': module_id, 'name': _process_name, 'action': _process_action, 'entity': _process_entity, 'msgID': _process_msgID,} _process_adapters_kwargs = {'dbsession': None} _process_log_kwargs = {'indent_method': 'AUTO', 'indent_level': None} _process_debug_level = get_debug_level(caller_area.get('debug_level'), **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_files = get_debug_files(_process_debug_level, **_process_identity_kwargs, **_process_adapters_kwargs) _process_debug_kwargs={'debug_level':_process_debug_level,'debug_files':_process_debug_files} _process_signature = build_process_signature(**_process_identity_kwargs, **_process_adapters_kwargs, **_process_debug_kwargs, **_process_log_kwargs) _process_call_area = build_process_call_area(_process_signature, caller_area) log_process_start(_process_msgID,**_process_call_area) log_process_input('', 'From', From,**_process_call_area) log_process_input('', 'To', To,**_process_call_area) log_process_input('', 'Cc', Cc,**_process_call_area) log_process_input('', 'Bcc', Bcc,**_process_call_area) log_process_input('', 'language', language, **_process_call_area) log_process_input('', 'Message', Message, **_process_call_area) log_process_input('', 'caller_area', caller_area, **_process_call_area) SMS_SERVER_NEXMO_API_KEY = thisApp.application_configuration.get('SMS_SERVER_NEXMO_API_KEY') SMS_SERVER_NEXMO_API_SECRET = thisApp.application_configuration.get('SMS_SERVER_NEXMO_API_SECRET') # SMS_SERVER_NEXMO_API_KEY = '3ee5cdd5' # SMS_SERVER_NEXMO_API_SECRET = 'lgzsdgI4cP9eZl7J' # # SMS_SERVER_NEXMO_FROM_NUMBER = '35799599819' log_process_parameter('', 'config param', 'SMS_SERVER_NEXMO_API_KEY', SMS_SERVER_NEXMO_API_KEY, **_process_call_area) log_process_parameter('', 'config param', 'SMS_SERVER_NEXMO_API_SECRET', SMS_SERVER_NEXMO_API_SECRET, **_process_call_area) # Within the Nexmo Voice API all numbers are in E.164 format. This means that numbers: # Omit both a leading + and the international access code such as 00 or 001. # Contain no special characters, such as a space, () or - To = To.replace('+', '').replace('(', '').replace(')', '').replace('-', '') log_process_data('', 'To number in E.164 format', To, **_process_call_area) try: msg='start sending SMS using NEXMO' log_process_message('', '', msg,**_process_call_area) if caller_area.get('sms_simulation'): reply='sms_simulation:success' status_code = '0' reply_message = "simulated sms send" sms_uid = "simulated_sms:" log_process_message('','success',"SIMULATION:Message sent successfully.") else: # Create a new Nexmo Client object: nexmo_client = nexmo.Client(key=SMS_SERVER_NEXMO_API_KEY, secret=SMS_SERVER_NEXMO_API_SECRET) # Send the SMS message: nexmo_api_result = nexmo_client.send_message({ 'from': From, 'to': To, 'text': Message, }) log_process_data('', 'nexmo_api_result', nexmo_api_result, **_process_call_area) #{'message-count': '1', 'messages': [{'to': 'YOUR-PHONE-NUMBER', 'message-id': '0D00000039FFD940', 'status': '0', 'remaining-balance': '14.62306950', 'message-price': '0.03330000', 'network': '12345'}]} #{'message-count': '1', 'messages': [{'to':'35799359864', 'message-id':'1C00000027D52523', 'status':'0', 'remaining-balance':'1.79360000', 'message-price':'0.06880000', 'network':'28001'}]} reply=nexmo_api_result status_code = nexmo_api_result["messages"][0].get("status","99") reply_message = nexmo_api_result["messages"][0].get("error-text","") sms_uid = nexmo_api_result["messages"][0].get("message-id","failed_sms") if not status_code=='0': msg= f'sending SMS provider error:{status_code}-{reply_message}' log_process_message('', 'error', msg,**_process_call_area) else: msg='OK. SMS sent using NEXMO' log_process_message('', 'success', msg,**_process_call_area) except Exception as error_text: msg= f'sending SMS system error:{error_text}' log_process_message('', 'error', msg, **_process_call_area) reply='exception occurred executing NEXMO client api' status_code = '99' reply_message = msg sms_uid='' provider_reply = {'provider_reply': reply, 'reply_code': status_code, 'reply_message': reply_message, 'provider': 'NEXMO', 'provider_send_id': sms_uid} if not status_code=='0': api_result = {'api_status': 'error', 'api_message': msg,'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result msg = f'SMS sent To [{To}] from [{From}] with Text [{Message}]' smstext=Message[0:5]+'***' msg= f'SMS sent To [{To}] from [{From}] with Text {smstext}' api_result = {'api_status': 'success', 'api_message': msg, 'api_data':provider_reply} log_process_finish(_process_msgID, api_result, **_process_call_area) return api_result
application_configuration.update({'MAIL_PORT': MAIL_PORT}) application_configuration.update({'MAIL_USE_TLS': MAIL_USE_TLS}) application_configuration.update({'MAIL_USE_SSL': MAIL_USE_SSL}) application_configuration.update({'MAIL_USERNAME': MAIL_USERNAME}) application_configuration.update({'MAIL_PASSWORD': MAIL_PASSWORD}) application_configuration.update({'MAIL_APIKEY_PUBLIC': MAIL_APIKEY_PUBLIC}) application_configuration.update({'MAIL_APIKEY_PRIVATE': MAIL_APIKEY_PRIVATE}) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # log_process_parameter('', '', 'MAIL_SERVER', MAIL_SERVER, **_process_call_area) log_process_parameter('', '', 'MAIL_PORT', MAIL_PORT, **_process_call_area) log_process_parameter('', '', 'MAIL_USE_TLS', MAIL_USE_TLS, **_process_call_area) log_process_parameter('', '', 'MAIL_USE_SSL', MAIL_USE_SSL, **_process_call_area) log_process_parameter('', '', 'MAIL_USERNAME', MAIL_USERNAME, **_process_call_area) log_process_parameter('', '', 'MAIL_PASSWORD', MAIL_PASSWORD, **_process_call_area) log_process_parameter('', '', 'MAIL_APIKEY_PUBLIC', MAIL_APIKEY_PUBLIC, **_process_call_area) log_process_parameter('', '', 'MAIL_APIKEY_PRIVATE', MAIL_APIKEY_PRIVATE, **_process_call_area) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # log_process_finish(_process_msgID, {}, **_process_call_area) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #