def create_template(service_id): fetched_service = dao_fetch_service_by_id(service_id=service_id) # permissions needs to be placed here otherwise marshmallow will intefere with versioning permissions = fetched_service.permissions new_template = template_schema.load(request.get_json()).data if not service_has_permission(new_template.template_type, permissions): message = "Creating {} templates is not allowed".format( get_public_notify_type_text(new_template.template_type)) errors = {'template_type': [message]} raise InvalidRequest(errors, 403) new_template.service = fetched_service over_limit = _content_count_greater_than_limit(new_template.content, new_template.template_type) if over_limit: char_count_limit = SMS_CHAR_COUNT_LIMIT message = 'Content has a character count greater than the limit of {}'.format( char_count_limit) errors = {'content': [message]} raise InvalidRequest(errors, status_code=400) check_reply_to(service_id, new_template.reply_to, new_template.template_type) dao_create_template(new_template) return jsonify(data=template_schema.dump(new_template).data), 201
def cancel_notification_for_service(service_id, notification_id): notification = notifications_dao.get_notification_by_id( notification_id, service_id) if not notification: raise InvalidRequest('Notification not found', status_code=404) elif notification.notification_type != LETTER_TYPE: raise InvalidRequest( 'Notification cannot be cancelled - only letters can be cancelled', status_code=400) elif not letter_can_be_cancelled(notification.status, notification.created_at): print_day = letter_print_day(notification.created_at) raise InvalidRequest( "It’s too late to cancel this letter. Printing started {} at 5.30pm" .format(print_day), status_code=400) updated_notification = notifications_dao.update_notification_status_by_id( notification_id, NOTIFICATION_CANCELLED, ) return jsonify( notification_with_template_schema.dump(updated_notification).data), 200
def _update_broadcast_message(broadcast_message, new_status, updating_user): if updating_user not in broadcast_message.service.users: raise InvalidRequest( f'User {updating_user.id} cannot approve broadcast_message {broadcast_message.id} from other service', status_code=400) if new_status not in BroadcastStatusType.ALLOWED_STATUS_TRANSITIONS[ broadcast_message.status]: raise InvalidRequest( f'Cannot move broadcast_message {broadcast_message.id} from {broadcast_message.status} to {new_status}', status_code=400) if new_status == BroadcastStatusType.BROADCASTING: # TODO: Remove this platform admin shortcut when the feature goes live if updating_user == broadcast_message.created_by and not ( # platform admins and trial mode services can approve their own broadcasts updating_user.platform_admin or broadcast_message.service.restricted): raise InvalidRequest( f'User {updating_user.id} cannot approve their own broadcast_message {broadcast_message.id}', status_code=400) else: broadcast_message.approved_at = datetime.utcnow() broadcast_message.approved_by = updating_user if new_status == BroadcastStatusType.CANCELLED: broadcast_message.cancelled_at = datetime.utcnow() broadcast_message.cancelled_by = updating_user current_app.logger.info( f'broadcast_message {broadcast_message.id} moving from {broadcast_message.status} to {new_status}' ) broadcast_message.status = new_status
def process_sinch_response(): client_name = 'Sinch' data = json.loads(request.data) errors = validate_callback_data( data=data, fields=['id', 'from', 'to', 'body', 'received_at'], client_name=client_name) if errors: raise InvalidRequest(errors, status_code=400) short_number = data.get('to') service = fetch_potential_service(short_number, 'sinch') if not service: return jsonify({"status": "ok"}), 200 success, errors = process_shortnumber_keyword_client_response( service=service, short_number=short_number, from_number=data.get('from'), body=data.get('body'), received_at=data.get('received_at'), provider_ref=data.get('id'), client_name=client_name) redacted_data = dict(data.items()) current_app.logger.debug( "Keyword shortnumber acknowledge from {} \n{}".format( client_name, redacted_data)) if errors: raise InvalidRequest(errors, status_code=400) else: return jsonify(result='success', message=success), 200
def get_template_statistics_for_service_by_day(service_id): whole_days = request.args.get('whole_days', request.args.get('limit_days', '')) try: whole_days = int(whole_days) except ValueError: error = '{} is not an integer'.format(whole_days) message = {'whole_days': [error]} raise InvalidRequest(message, status_code=400) if whole_days < 0 or whole_days > 7: raise InvalidRequest( {'whole_days': ['whole_days must be between 0 and 7']}, status_code=400) data = fetch_notification_status_for_service_for_today_and_7_previous_days( service_id, by_template=True, limit_days=whole_days) return jsonify(data=[{ 'count': row.count, 'template_id': str(row.template_id), 'template_name': row.template_name, 'template_type': row.notification_type, 'is_precompiled_letter': row.is_precompiled_letter, 'status': row.status } for row in data])
def process_telstra_response(notification_id): client_name = 'telstra' data = request.json errors = validate_callback_data(data=data, fields=['messageId', 'deliveryStatus'], client_name=client_name) if errors: raise InvalidRequest(errors, status_code=400) success, errors = process_sms_client_response( status=str(data.get('deliveryStatus')), provider_reference=notification_id, client_name=client_name) redacted_data = data.copy() redacted_data.pop("to") current_app.logger.debug( "Full delivery response from {} for notification: {}\n{}".format( client_name, notification_id, redacted_data)) if errors: raise InvalidRequest(errors, status_code=400) return jsonify(result='success', message=success), 200
def update_broadcast_message(service_id, broadcast_message_id): data = request.get_json() validate(data, update_broadcast_message_schema) broadcast_message = dao_get_broadcast_message_by_id_and_service_id(broadcast_message_id, service_id) if broadcast_message.status not in BroadcastStatusType.PRE_BROADCAST_STATUSES: raise InvalidRequest( f'Cannot update broadcast_message {broadcast_message.id} while it has status {broadcast_message.status}', status_code=400 ) if ('areas' in data and 'simple_polygons' not in data) or ('areas' not in data and 'simple_polygons' in data): raise InvalidRequest( f'Cannot update broadcast_message {broadcast_message.id}, areas or polygons are missing.', status_code=400 ) if 'personalisation' in data: broadcast_message.personalisation = data['personalisation'] if 'starts_at' in data: broadcast_message.starts_at = _parse_nullable_datetime(data['starts_at']) if 'finishes_at' in data: broadcast_message.finishes_at = _parse_nullable_datetime(data['finishes_at']) if 'areas' in data and 'simple_polygons' in data: broadcast_message.areas = {"areas": data["areas"], "simple_polygons": data["simple_polygons"]} dao_save_object(broadcast_message) return jsonify(broadcast_message.serialize()), 200
def sns_callback_handler(): message_type = request.headers.get('x-amz-sns-message-type') try: verify_message_type(message_type) except InvalidMessageTypeException: raise InvalidRequest("SES-SNS callback failed: invalid message type", 400) try: message = json.loads(request.data) except json.decoder.JSONDecodeError: raise InvalidRequest("SES-SNS callback failed: invalid JSON given", 400) try: validatesns.validate(message, get_certificate=get_certificate) except validatesns.ValidationError: raise InvalidRequest("SES-SNS callback failed: validation failed", 400) if autoconfirm_subscription(message): return jsonify(result="success", message="SES-SNS auto-confirm callback succeeded"), 200 ok, retry = process_ses_results(message) if ok: return jsonify(result="success", message="SES-SNS callback succeeded"), 200 if retry: raise InvalidRequest("SES callback failed, should retry", 500) raise InvalidRequest("SES-SNS callback failed", 400)
def verify_user_code(user_id): data = request.get_json() validate(data, post_verify_code_schema) user_to_verify = get_user_by_id(user_id=user_id) code = get_user_code(user_to_verify, data['code'], data['code_type']) if(verify_within_time(user_to_verify) >= 2): raise InvalidRequest("Code already sent", status_code=400) if user_to_verify.failed_login_count >= current_app.config.get('MAX_VERIFY_CODE_COUNT'): raise InvalidRequest("Code not found", status_code=404) if not code: increment_failed_login_count(user_to_verify) raise InvalidRequest("Code not found", status_code=404) if datetime.utcnow() > code.expiry_datetime: # sms and email increment_failed_login_count(user_to_verify) raise InvalidRequest("Code has expired", status_code=400) if code.code_used: increment_failed_login_count(user_to_verify) raise InvalidRequest("Code has already been used", status_code=400) user_to_verify.current_session_id = str(uuid.uuid4()) user_to_verify.logged_in_at = datetime.utcnow() user_to_verify.failed_login_count = 0 save_model_user(user_to_verify) use_user_code(code.id) return jsonify({}), 204
def _update_broadcast_message(broadcast_message, new_status, updating_user): if updating_user not in broadcast_message.service.users: raise InvalidRequest( f'User {updating_user.id} cannot approve broadcast_message {broadcast_message.id} from other service', status_code=400) if new_status not in BroadcastStatusType.ALLOWED_STATUS_TRANSITIONS[ broadcast_message.status]: raise InvalidRequest( f'Cannot move broadcast_message {broadcast_message.id} from {broadcast_message.status} to {new_status}', status_code=400) if new_status == BroadcastStatusType.BROADCASTING: # training mode services can approve their own broadcasts if updating_user == broadcast_message.created_by and not broadcast_message.service.restricted: raise InvalidRequest( f'User {updating_user.id} cannot approve their own broadcast_message {broadcast_message.id}', status_code=400) elif len(broadcast_message.areas['simple_polygons']) == 0: raise InvalidRequest( f'broadcast_message {broadcast_message.id} has no selected areas and so cannot be broadcasted.', status_code=400) else: broadcast_message.approved_at = datetime.utcnow() broadcast_message.approved_by = updating_user if new_status == BroadcastStatusType.CANCELLED: broadcast_message.cancelled_at = datetime.utcnow() broadcast_message.cancelled_by = updating_user current_app.logger.info( f'broadcast_message {broadcast_message.id} moving from {broadcast_message.status} to {new_status}' ) broadcast_message.status = new_status
def create_template(service_id): fetched_service = dao_fetch_service_by_id(service_id=service_id) # permissions needs to be placed here otherwise marshmallow will interfere with versioning permissions = [p.permission for p in fetched_service.permissions] template_json = validate(request.get_json(), post_create_template_schema) folder = validate_parent_folder(template_json=template_json) new_template = Template.from_json(template_json, folder) if not service_has_permission(new_template.template_type, permissions): message = "Creating {} templates is not allowed".format( get_public_notify_type_text(new_template.template_type)) errors = {'template_type': [message]} raise InvalidRequest(errors, 403) if not new_template.postage and new_template.template_type == LETTER_TYPE: new_template.postage = SECOND_CLASS new_template.service = fetched_service over_limit = _content_count_greater_than_limit(new_template.content, new_template.template_type) if over_limit: message = 'Content has a character count greater than the limit of {}'.format( SMS_CHAR_COUNT_LIMIT) errors = {'content': [message]} raise InvalidRequest(errors, status_code=400) check_reply_to(service_id, new_template.reply_to, new_template.template_type) dao_create_template(new_template) return jsonify(data=template_schema.dump(new_template).data), 201
def get_template_statistics_for_service_by_day(service_id): whole_days = request.args.get("whole_days", request.args.get("limit_days", "")) try: whole_days = int(whole_days) except ValueError: error = "{} is not an integer".format(whole_days) message = {"whole_days": [error]} raise InvalidRequest(message, status_code=400) if whole_days < 0 or whole_days > 7: raise InvalidRequest({"whole_days": ["whole_days must be between 0 and 7"]}, status_code=400) data = fetch_notification_status_for_service_for_today_and_7_previous_days( service_id, by_template=True, limit_days=whole_days ) return jsonify( data=[ { "count": row.count, "template_id": str(row.template_id), "template_name": row.template_name, "template_type": row.notification_type, "is_precompiled_letter": row.is_precompiled_letter, "status": row.status, } for row in data ] )
def process_twilio_response(notification_id): client_name = 'Twilio' data = request.values errors = validate_callback_data(data=data, fields=['MessageStatus', 'MessageSid'], client_name=client_name) if errors: raise InvalidRequest(errors, status_code=400) success, errors = process_sms_client_response( status=data.get('MessageStatus'), provider_reference=notification_id, client_name=client_name) redacted_data = dict(data.items()) redacted_data.pop('To', None) current_app.logger.debug( "Full delivery response from {} for notification: {}\n{}".format( client_name, notification_id, redacted_data)) if errors: raise InvalidRequest(errors, status_code=400) else: return jsonify(result='success', message=success), 200
def create_user(): data = request.get_json(force=True) validate(data, post_create_user_schema) current_app.logger.info('Creating user {}'.format(data['email'])) if data['email'].split('@')[1] != current_app.config['EMAIL_DOMAIN']: raise InvalidRequest("{} not in correct domain".format(data['email']), 400) if data['email'] in current_app.config['ADMIN_USERS']: data['access_area'] = USER_ADMIN elif data.get('access_area'): for area in data['access_area'].split(','): if area and area not in ACCESS_AREAS: raise InvalidRequest("{} not supported access area".format(area), 400) data['active'] = True user = User(**data) dao_create_user(user) current_app.logger.info('Created user {}'.format(user.email)) return jsonify(user.serialize()), 201
def dao_create_email(email): if email.email_type == EVENT: try: event = dao_get_event_by_id(email.event_id) email.subject = u"{}: {}".format(event.event_type.event_type, event.title) if not email.send_starts_at: email.send_starts_at = datetime.strptime( event.get_first_event_date(), "%Y-%m-%d") - timedelta(weeks=2) if not email.expires: email.expires = event.get_last_event_date() except NoResultFound: raise InvalidRequest('event not found: {}'.format(email.event_id), 400) elif email.email_type == MAGAZINE and not email.old_id: if email.magazine_id: try: magazine = dao_get_magazine_by_id(email.magazine_id) email.subject = u"New Acropolis bi-monthly magazine: {}".format( magazine.title) if not email.send_starts_at: email.send_starts_at = _get_nearest_bi_monthly_send_date() email.expires = email.send_starts_at + timedelta(weeks=2) except NoResultFound: raise InvalidRequest( 'magazine not found: {}'.format(email.event_id), 400) else: current_app.logger.info('No magazine id for email') return db.session.add(email) return True
def validate_invitation_token(invitation_type, token): max_age_seconds = 60 * 60 * 24 * current_app.config[ "INVITATION_EXPIRATION_DAYS"] try: invited_user_id = check_token( token, current_app.config["SECRET_KEY"], current_app.config["DANGEROUS_SALT"], max_age_seconds, ) except SignatureExpired: errors = {"invitation": "invitation expired"} raise InvalidRequest(errors, status_code=400) except BadData: errors = {"invitation": "bad invitation link"} raise InvalidRequest(errors, status_code=400) if invitation_type == "service": invited_user = get_invited_user_by_id(invited_user_id) return jsonify(data=invited_user_schema.dump(invited_user).data), 200 elif invitation_type == "organisation": invited_user = dao_get_invited_organisation_user(invited_user_id) return jsonify(data=invited_user.serialize()), 200 else: raise InvalidRequest( "Unrecognised invitation type: {}".format(invitation_type))
def validate_invitation_token(invitation_type, token): max_age_seconds = 60 * 60 * 24 * current_app.config[ 'INVITATION_EXPIRATION_DAYS'] try: invited_user_id = check_token(token, current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT'], max_age_seconds) except SignatureExpired: errors = { 'invitation': 'Your invitation to GOV.UK Notify has expired. ' 'Please ask the person that invited you to send you another one' } raise InvalidRequest(errors, status_code=400) except BadData: errors = { 'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' } raise InvalidRequest(errors, status_code=400) if invitation_type == 'service': invited_user = get_invited_user_by_id(invited_user_id) return jsonify(data=invited_user_schema.dump(invited_user).data), 200 elif invitation_type == 'organisation': invited_user = dao_get_invited_organisation_user(invited_user_id) return jsonify(data=invited_user.serialize()), 200 else: raise InvalidRequest( "Unrecognised invitation type: {}".format(invitation_type))
def isValidGUID(organisation_id): valid = False try: # Regex to check valid # GUID (Globally Unique Identifier) regex = "^[{]?[0-9a-fA-F]{8}" + "-([0-9a-fA-F]{4}-)" + "{3}[0-9a-fA-F]{12}[}]?$" # Compile the ReGex p = re.compile(regex) # Return if the string # matched the ReGex if (organisation_id is None or not organisation_id or re.search(p, organisation_id)): valid = True else: valid = False except ValueError: raise InvalidRequest( message="You must choose an organisation from the list", status_code=400) if not valid: raise InvalidRequest( message="You must choose an organisation from the list", status_code=400) return organisation_id
def _validate_folder_move(target_template_folder, target_template_folder_id, template_folder, template_folder_id): if str(target_template_folder_id) == str(template_folder_id): msg = 'You cannot move a folder to itself' raise InvalidRequest(msg, status_code=400) if target_template_folder and template_folder.is_parent_of( target_template_folder): msg = 'You cannot move a folder to one of its subfolders' raise InvalidRequest(msg, status_code=400)
def create_user(): user_to_create, errors = create_user_schema.load(request.get_json()) req_json = request.get_json() if not req_json.get('password', None): errors.update({'password': ['Missing data for required field.']}) raise InvalidRequest(errors, status_code=400) if req_json.get('platform_admin'): errors.update({'platform_admin': ['Unknown field name.']}) raise InvalidRequest(errors, status_code=400) save_model_user(user_to_create, pwd=req_json.get('password')) result = user_to_create.serialize() return jsonify(data=result), 201
def remove_user_from_service(service_id, user_id): service = dao_fetch_service_by_id(service_id) user = get_user_by_id(user_id=user_id) if user not in service.users: error = 'User not found' raise InvalidRequest(error, status_code=404) elif len(service.users) == 1: error = 'You cannot remove the only user for a service' raise InvalidRequest(error, status_code=400) dao_remove_user_from_service(service, user) return jsonify({}), 204
def create_job(service_id): service = dao_fetch_service_by_id(service_id) if not service.active: raise InvalidRequest("Create job is not allowed: service is inactive ", 403) data = request.get_json() data.update({"service": service_id}) try: data.update(**get_job_metadata_from_s3(service_id, data['id'])) except KeyError: raise InvalidRequest({'id': ['Missing data for required field.']}, status_code=400) data['template'] = data.pop('template_id') template = dao_get_template_by_id(data['template']) if template.template_type == LETTER_TYPE and service.restricted: raise InvalidRequest( "Create letter job is not allowed for service in trial mode ", 403) if data.get('valid') != 'True': raise InvalidRequest("File is not valid, can't create job", 400) errors = unarchived_template_schema.validate( {'archived': template.archived}) if errors: raise InvalidRequest(errors, status_code=400) data.update({"template_version": template.version}) job = job_schema.load(data).data if job.scheduled_for: job.job_status = JOB_STATUS_SCHEDULED dao_create_job(job) sender_id = data.get('sender_id') if job.job_status == JOB_STATUS_PENDING: process_job.apply_async([str(job.id)], {'sender_id': sender_id}, queue=QueueNames.JOBS) job_json = job_schema.dump(job).data job_json['statistics'] = [] return jsonify(data=job_json), 201
def event_handler(): old_id = request.args.get('eventid') try: int(old_id) except: raise InvalidRequest('invalid event old_id: {}'.format(old_id), 400) event = dao_get_event_by_old_id(old_id) if not event: raise InvalidRequest('event not found for old_id: {}'.format(old_id), 404) return jsonify(event.serialize())
def create_template_object_for_notification(template, personalisation): template_object = get_template_instance(template.__dict__, personalisation) if template_object.missing_data: message = "Missing personalisation: {}".format(", ".join( template_object.missing_data)) errors = {"template": [message]} raise InvalidRequest(errors, status_code=400) if template_object.template_type == SMS_TYPE and template_object.content_count > SMS_CHAR_COUNT_LIMIT: message = "Content has a character count greater than the limit of {}".format( SMS_CHAR_COUNT_LIMIT) errors = {"content": [message]} raise InvalidRequest(errors, status_code=400) return template_object
def update_template(service_id, template_id): fetched_template = dao_get_template_by_id_and_service_id( template_id=template_id, service_id=service_id) if not service_has_permission( fetched_template.template_type, [p.permission for p in fetched_template.service.permissions]): message = "Updating {} templates is not allowed".format( get_public_notify_type_text(fetched_template.template_type)) errors = {'template_type': [message]} raise InvalidRequest(errors, 403) data = request.get_json() validate(data, post_update_template_schema) # if redacting, don't update anything else if data.get('redact_personalisation') is True: return redact_template(fetched_template, data) if "reply_to" in data: check_reply_to(service_id, data.get("reply_to"), fetched_template.template_type) updated = dao_update_template_reply_to(template_id=template_id, reply_to=data.get("reply_to")) return jsonify(data=template_schema.dump(updated).data), 200 current_data = dict(template_schema.dump(fetched_template).data.items()) updated_template = dict( template_schema.dump(fetched_template).data.items()) updated_template.update(data) # Check if there is a change to make. if _template_has_not_changed(current_data, updated_template): return jsonify(data=updated_template), 200 over_limit = _content_count_greater_than_limit( updated_template['content'], fetched_template.template_type) if over_limit: message = 'Content has a character count greater than the limit of {}'.format( SMS_CHAR_COUNT_LIMIT) errors = {'content': [message]} raise InvalidRequest(errors, status_code=400) update_dict = template_schema.load(updated_template).data dao_update_template(update_dict) return jsonify(data=template_schema.dump(update_dict).data), 200
def get_monthly_template_usage(service_id): try: start_date, end_date = get_financial_year(int(request.args.get('year', 'NaN'))) data = fetch_monthly_template_usage_for_service( start_date=start_date, end_date=end_date, service_id=service_id ) stats = list() for i in data: stats.append( { 'template_id': str(i.template_id), 'name': i.name, 'type': i.template_type, 'month': i.month, 'year': i.year, 'count': i.count, 'is_precompiled_letter': i.is_precompiled_letter } ) return jsonify(stats=stats), 200 except ValueError: raise InvalidRequest('Year must be a number', status_code=400)
def check_if_reply_to_address_already_in_use(service_id, email_address): existing_reply_to_addresses = dao_get_reply_to_by_service_id(service_id) if email_address in [i.email_address for i in existing_reply_to_addresses]: raise InvalidRequest( "Your service already uses ‘{}’ as an email reply-to address.". format(email_address), status_code=400)
def get_free_sms_fragment_limit(service_id): financial_year_start = request.args.get('financial_year_start') annual_billing = dao_get_free_sms_fragment_limit_for_year( service_id, financial_year_start) if annual_billing is None: # An entry does not exist in annual_billing table for that service and year. If it is a past year, # we return the oldest entry. # If it is the current or future years, we create an entry in the db table using the newest record, # and return that number. If all fails, we return InvalidRequest. sms_list = dao_get_all_free_sms_fragment_limit(service_id) if not sms_list: raise InvalidRequest( 'no free-sms-fragment-limit entry for service {} in DB'.format( service_id), 404) else: if financial_year_start is None: financial_year_start = get_current_financial_year_start_year() if int(financial_year_start ) < get_current_financial_year_start_year(): # return the earliest historical entry annual_billing = sms_list[0] # The oldest entry else: annual_billing = sms_list[-1] # The newest entry annual_billing = dao_create_or_update_annual_billing_for_year( service_id, annual_billing.free_sms_fragment_limit, financial_year_start) return jsonify(annual_billing.serialize_free_sms_items()), 200
def send_user_confirm_new_email(user_id): user_to_send_to = get_user_by_id(user_id=user_id) email, errors = email_data_request_schema.load(request.get_json()) if errors: raise InvalidRequest(message=errors, status_code=400) template = dao_get_template_by_id(current_app.config['CHANGE_EMAIL_CONFIRMATION_TEMPLATE_ID']) service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID']) saved_notification = persist_notification( template_id=template.id, template_version=template.version, recipient=email['email'], service=service, personalisation={ 'name': user_to_send_to.name, 'url': _create_confirmation_url(user=user_to_send_to, email_address=email['email']), 'feedback_url': current_app.config['ADMIN_BASE_URL'] + '/support/ask-question-give-feedback' }, notification_type=template.template_type, api_key_id=None, key_type=KEY_TYPE_NORMAL, reply_to_text=service.get_default_reply_to_email_address() ) send_notification_to_queue(saved_notification, False, queue=QueueNames.NOTIFY) return jsonify({}), 204
def _get_png_preview_or_overlaid_pdf(url, data, notification_id, json=True): if json: resp = requests_post( url, json=data, headers={ 'Authorization': 'Token {}'.format( current_app.config['TEMPLATE_PREVIEW_API_KEY']) }) else: resp = requests_post( url, data=data, headers={ 'Authorization': 'Token {}'.format( current_app.config['TEMPLATE_PREVIEW_API_KEY']) }) if resp.status_code != 200: raise InvalidRequest( 'Error generating preview letter for {} Status code: {} {}'.format( notification_id, resp.status_code, resp.content), status_code=500) return base64.b64encode(resp.content).decode('utf-8')