2015-12-10 10:56:59 +00:00
|
|
|
import os
|
2022-08-19 15:26:12 +00:00
|
|
|
import secrets
|
2017-04-12 17:56:55 +01:00
|
|
|
import string
|
2021-03-10 13:55:06 +00:00
|
|
|
import time
|
2016-11-10 13:22:38 +00:00
|
|
|
import uuid
|
2021-03-10 13:55:06 +00:00
|
|
|
from time import monotonic
|
2016-05-13 17:15:39 +01:00
|
|
|
|
2020-04-24 15:15:41 +01:00
|
|
|
from celery import current_task
|
2021-03-10 13:55:06 +00:00
|
|
|
from flask import (
|
|
|
|
|
current_app,
|
|
|
|
|
g,
|
|
|
|
|
has_request_context,
|
|
|
|
|
jsonify,
|
|
|
|
|
make_response,
|
|
|
|
|
request,
|
|
|
|
|
)
|
2016-01-11 15:07:13 +00:00
|
|
|
from flask_marshmallow import Marshmallow
|
2017-11-06 12:34:29 +00:00
|
|
|
from flask_migrate import Migrate
|
2021-03-10 13:55:06 +00:00
|
|
|
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
|
2020-04-20 15:59:47 +01:00
|
|
|
from gds_metrics import GDSMetrics
|
2020-04-24 14:36:21 +01:00
|
|
|
from gds_metrics.metrics import Gauge, Histogram
|
2017-11-14 14:26:00 +00:00
|
|
|
from notifications_utils import logging, request_helper
|
2021-11-16 13:29:55 +00:00
|
|
|
from notifications_utils.celery import NotifyCelery
|
2021-03-10 13:55:06 +00:00
|
|
|
from notifications_utils.clients.encryption.encryption_client import Encryption
|
|
|
|
|
from notifications_utils.clients.redis.redis_client import RedisClient
|
|
|
|
|
from notifications_utils.clients.zendesk.zendesk_client import ZendeskClient
|
2020-04-24 14:36:21 +01:00
|
|
|
from sqlalchemy import event
|
2019-07-26 13:26:20 +01:00
|
|
|
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
|
2016-11-10 13:22:38 +00:00
|
|
|
from werkzeug.local import LocalProxy
|
|
|
|
|
|
2020-11-17 12:35:18 +00:00
|
|
|
from app.clients import NotificationProviderClients
|
2023-05-04 07:56:24 -07:00
|
|
|
from app.clients.cloudwatch.aws_cloudwatch import AwsCloudwatchClient
|
2018-04-04 17:31:02 +01:00
|
|
|
from app.clients.document_download import DocumentDownloadClient
|
2016-11-10 13:22:38 +00:00
|
|
|
from app.clients.email.aws_ses import AwsSesClient
|
2020-06-01 17:08:30 +01:00
|
|
|
from app.clients.email.aws_ses_stub import AwsSesStubClient
|
2022-06-17 11:16:23 -07:00
|
|
|
from app.clients.sms.aws_sns import AwsSnsClient
|
2016-07-05 15:38:20 +01:00
|
|
|
|
2019-01-09 12:22:51 +00:00
|
|
|
|
|
|
|
|
class SQLAlchemy(_SQLAlchemy):
|
|
|
|
|
"""We need to subclass SQLAlchemy in order to override create_engine options"""
|
|
|
|
|
|
|
|
|
|
def apply_driver_hacks(self, app, info, options):
|
2022-08-12 15:37:39 +00:00
|
|
|
sa_url, options = super().apply_driver_hacks(app, info, options)
|
2019-01-09 12:22:51 +00:00
|
|
|
if 'connect_args' not in options:
|
|
|
|
|
options['connect_args'] = {}
|
|
|
|
|
options['connect_args']["options"] = "-c statement_timeout={}".format(
|
|
|
|
|
int(app.config['SQLALCHEMY_STATEMENT_TIMEOUT']) * 1000
|
|
|
|
|
)
|
2022-08-12 15:37:39 +00:00
|
|
|
return (sa_url, options)
|
2019-01-09 12:22:51 +00:00
|
|
|
|
|
|
|
|
|
2016-01-07 17:31:17 +00:00
|
|
|
db = SQLAlchemy()
|
2017-11-06 12:34:29 +00:00
|
|
|
migrate = Migrate()
|
2016-01-11 15:07:13 +00:00
|
|
|
ma = Marshmallow()
|
2016-02-16 14:06:56 +00:00
|
|
|
notify_celery = NotifyCelery()
|
2016-02-17 17:48:23 +00:00
|
|
|
aws_ses_client = AwsSesClient()
|
2020-06-01 17:08:30 +01:00
|
|
|
aws_ses_stub_client = AwsSesStubClient()
|
2022-06-17 11:16:23 -07:00
|
|
|
aws_sns_client = AwsSnsClient()
|
2023-05-04 07:56:24 -07:00
|
|
|
aws_cloudwatch_client = AwsCloudwatchClient()
|
2016-02-16 15:28:30 +00:00
|
|
|
encryption = Encryption()
|
2018-04-25 14:22:23 +01:00
|
|
|
zendesk_client = ZendeskClient()
|
2016-11-10 11:50:49 +00:00
|
|
|
redis_store = RedisClient()
|
2018-04-04 17:31:02 +01:00
|
|
|
document_download_client = DocumentDownloadClient()
|
2020-04-20 15:59:47 +01:00
|
|
|
metrics = GDSMetrics()
|
2016-01-07 17:31:17 +00:00
|
|
|
|
2020-11-17 12:35:18 +00:00
|
|
|
notification_provider_clients = NotificationProviderClients()
|
2016-05-06 09:09:47 +01:00
|
|
|
|
2021-07-07 17:35:15 +01:00
|
|
|
api_user = LocalProxy(lambda: g.api_user)
|
|
|
|
|
authenticated_service = LocalProxy(lambda: g.authenticated_service)
|
2015-12-15 10:47:20 +00:00
|
|
|
|
2020-06-12 14:52:04 +01:00
|
|
|
CONCURRENT_REQUESTS = Gauge(
|
|
|
|
|
'concurrent_web_request_count',
|
|
|
|
|
'How many concurrent requests are currently being served',
|
|
|
|
|
)
|
|
|
|
|
|
2015-12-10 10:56:59 +00:00
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
def create_app(application):
|
2016-12-08 12:12:45 +00:00
|
|
|
from app.config import configs
|
|
|
|
|
|
|
|
|
|
notify_environment = os.environ['NOTIFY_ENVIRONMENT']
|
2017-07-19 13:50:29 +01:00
|
|
|
|
2016-12-08 12:12:45 +00:00
|
|
|
application.config.from_object(configs[notify_environment])
|
|
|
|
|
|
2017-11-06 12:34:29 +00:00
|
|
|
application.config['NOTIFY_APP_NAME'] = application.name
|
2016-02-17 09:14:37 +00:00
|
|
|
init_app(application)
|
2020-07-08 16:51:41 +01:00
|
|
|
|
|
|
|
|
# Metrics intentionally high up to give the most accurate timing and reliability that the metric is recorded
|
|
|
|
|
metrics.init_app(application)
|
2017-11-14 14:26:00 +00:00
|
|
|
request_helper.init_app(application)
|
2016-01-07 17:31:17 +00:00
|
|
|
db.init_app(application)
|
2017-11-06 12:34:29 +00:00
|
|
|
migrate.init_app(application, db=db)
|
2016-01-11 15:07:13 +00:00
|
|
|
ma.init_app(application)
|
2018-04-25 14:22:23 +01:00
|
|
|
zendesk_client.init_app(application)
|
2022-11-22 11:05:55 -05:00
|
|
|
logging.init_app(application)
|
2023-04-25 07:50:56 -07:00
|
|
|
aws_sns_client.init_app(application)
|
2020-06-01 17:08:30 +01:00
|
|
|
|
2023-04-25 07:50:56 -07:00
|
|
|
aws_ses_client.init_app()
|
2020-06-01 17:08:30 +01:00
|
|
|
aws_ses_stub_client.init_app(
|
2020-06-03 12:34:04 +01:00
|
|
|
stub_url=application.config['SES_STUB_URL']
|
2020-06-01 17:08:30 +01:00
|
|
|
)
|
2023-05-05 08:09:15 -07:00
|
|
|
aws_cloudwatch_client.init_app(application)
|
2020-06-01 17:08:30 +01:00
|
|
|
# If a stub url is provided for SES, then use the stub client rather than the real SES boto client
|
2020-06-03 12:34:04 +01:00
|
|
|
email_clients = [aws_ses_stub_client] if application.config['SES_STUB_URL'] else [aws_ses_client]
|
2022-03-25 15:03:52 +00:00
|
|
|
notification_provider_clients.init_app(
|
2022-06-17 11:16:23 -07:00
|
|
|
sms_clients=[aws_sns_client],
|
2022-03-25 15:03:52 +00:00
|
|
|
email_clients=email_clients
|
|
|
|
|
)
|
2020-06-01 17:08:30 +01:00
|
|
|
|
2021-04-13 14:53:46 +01:00
|
|
|
notify_celery.init_app(application)
|
2016-02-16 15:28:30 +00:00
|
|
|
encryption.init_app(application)
|
2016-11-10 11:27:57 +00:00
|
|
|
redis_store.init_app(application)
|
2018-04-04 17:31:02 +01:00
|
|
|
document_download_client.init_app(application)
|
2016-02-09 13:31:45 +00:00
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
register_blueprint(application)
|
|
|
|
|
register_v2_blueprints(application)
|
|
|
|
|
|
2018-02-05 14:58:02 +00:00
|
|
|
# avoid circular imports by importing this file later
|
2017-11-06 12:34:29 +00:00
|
|
|
from app.commands import setup_commands
|
|
|
|
|
setup_commands(application)
|
|
|
|
|
|
2020-04-24 14:36:21 +01:00
|
|
|
# set up sqlalchemy events
|
|
|
|
|
setup_sqlalchemy_events(application)
|
|
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
return application
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_blueprint(application):
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.authentication.auth import (
|
|
|
|
|
requires_admin_auth,
|
|
|
|
|
requires_auth,
|
|
|
|
|
requires_no_auth,
|
|
|
|
|
)
|
|
|
|
|
from app.billing.rest import billing_blueprint
|
|
|
|
|
from app.complaint.complaint_rest import complaint_blueprint
|
2022-11-16 15:50:08 -05:00
|
|
|
from app.docs import docs as docs_blueprint
|
2018-02-05 12:02:35 +00:00
|
|
|
from app.email_branding.rest import email_branding_blueprint
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.events.rest import events as events_blueprint
|
2017-08-04 19:26:51 +01:00
|
|
|
from app.inbound_number.rest import inbound_number_blueprint
|
2017-05-31 14:49:14 +01:00
|
|
|
from app.inbound_sms.rest import inbound_sms as inbound_sms_blueprint
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.job.rest import job_blueprint
|
2022-10-03 17:16:59 -07:00
|
|
|
from app.notifications.notifications_ses_callback import (
|
|
|
|
|
ses_callback_blueprint,
|
|
|
|
|
)
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.notifications.receive_notifications import (
|
|
|
|
|
receive_notifications_blueprint,
|
|
|
|
|
)
|
|
|
|
|
from app.notifications.rest import notifications as notifications_blueprint
|
2023-07-10 11:06:29 -07:00
|
|
|
from app.organization.invite_rest import organization_invite_blueprint
|
|
|
|
|
from app.organization.rest import organization_blueprint
|
2021-03-10 11:12:29 +00:00
|
|
|
from app.performance_dashboard.rest import performance_dashboard_blueprint
|
2018-06-28 08:39:25 +01:00
|
|
|
from app.platform_stats.rest import platform_stats_blueprint
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.provider_details.rest import (
|
|
|
|
|
provider_details as provider_details_blueprint,
|
|
|
|
|
)
|
|
|
|
|
from app.service.callback_rest import service_callback_blueprint
|
|
|
|
|
from app.service.rest import service_blueprint
|
2021-03-05 20:13:56 +00:00
|
|
|
from app.service_invite.rest import (
|
|
|
|
|
service_invite as service_invite_blueprint,
|
|
|
|
|
)
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.status.healthcheck import status as status_blueprint
|
|
|
|
|
from app.template.rest import template_blueprint
|
2018-10-30 16:26:25 +00:00
|
|
|
from app.template_folder.rest import template_folder_blueprint
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.template_statistics.rest import (
|
|
|
|
|
template_statistics as template_statistics_blueprint,
|
|
|
|
|
)
|
2019-12-03 15:53:32 +00:00
|
|
|
from app.upload.rest import upload_blueprint
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.user.rest import user_blueprint
|
2021-05-10 22:09:07 +01:00
|
|
|
from app.webauthn.rest import webauthn_blueprint
|
2016-01-15 15:48:05 +00:00
|
|
|
|
2017-03-16 18:15:49 +00:00
|
|
|
service_blueprint.before_request(requires_admin_auth)
|
2016-01-08 17:51:46 +00:00
|
|
|
application.register_blueprint(service_blueprint, url_prefix='/service')
|
2017-03-16 18:15:49 +00:00
|
|
|
|
|
|
|
|
user_blueprint.before_request(requires_admin_auth)
|
2016-01-08 17:51:46 +00:00
|
|
|
application.register_blueprint(user_blueprint, url_prefix='/user')
|
2017-03-16 18:15:49 +00:00
|
|
|
|
2021-05-10 22:09:07 +01:00
|
|
|
webauthn_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(webauthn_blueprint)
|
|
|
|
|
|
2017-03-16 18:15:49 +00:00
|
|
|
template_blueprint.before_request(requires_admin_auth)
|
2016-02-22 12:55:18 +00:00
|
|
|
application.register_blueprint(template_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
|
|
|
|
status_blueprint.before_request(requires_no_auth)
|
2016-03-03 15:18:12 +00:00
|
|
|
application.register_blueprint(status_blueprint)
|
2022-10-14 14:45:27 +00:00
|
|
|
|
2022-11-16 15:50:08 -05:00
|
|
|
docs_blueprint.before_request(requires_no_auth)
|
|
|
|
|
application.register_blueprint(docs_blueprint)
|
|
|
|
|
|
2022-09-15 14:59:13 -07:00
|
|
|
# delivery receipts
|
|
|
|
|
ses_callback_blueprint.before_request(requires_no_auth)
|
|
|
|
|
application.register_blueprint(ses_callback_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
2017-10-16 13:23:19 +01:00
|
|
|
# inbound sms
|
2017-11-10 12:05:06 +00:00
|
|
|
receive_notifications_blueprint.before_request(requires_no_auth)
|
2017-03-16 18:15:49 +00:00
|
|
|
application.register_blueprint(receive_notifications_blueprint)
|
|
|
|
|
|
|
|
|
|
notifications_blueprint.before_request(requires_auth)
|
2016-02-24 17:12:30 +00:00
|
|
|
application.register_blueprint(notifications_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
|
|
|
|
job_blueprint.before_request(requires_admin_auth)
|
2016-01-18 09:57:04 +00:00
|
|
|
application.register_blueprint(job_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
2021-03-05 20:13:56 +00:00
|
|
|
service_invite_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(service_invite_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
2023-07-10 11:06:29 -07:00
|
|
|
organization_invite_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(organization_invite_blueprint)
|
2021-03-11 20:26:44 +00:00
|
|
|
|
2017-08-04 19:26:51 +01:00
|
|
|
inbound_number_blueprint.before_request(requires_admin_auth)
|
2017-08-14 12:46:04 +01:00
|
|
|
application.register_blueprint(inbound_number_blueprint)
|
2017-08-04 19:26:51 +01:00
|
|
|
|
2017-05-31 14:49:14 +01:00
|
|
|
inbound_sms_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(inbound_sms_blueprint)
|
|
|
|
|
|
2017-03-16 18:15:49 +00:00
|
|
|
template_statistics_blueprint.before_request(requires_admin_auth)
|
2016-04-04 12:21:38 +01:00
|
|
|
application.register_blueprint(template_statistics_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
|
|
|
|
events_blueprint.before_request(requires_admin_auth)
|
2016-04-27 10:27:05 +01:00
|
|
|
application.register_blueprint(events_blueprint)
|
2017-03-16 18:15:49 +00:00
|
|
|
|
|
|
|
|
provider_details_blueprint.before_request(requires_admin_auth)
|
2016-05-10 09:04:22 +01:00
|
|
|
application.register_blueprint(provider_details_blueprint, url_prefix='/provider-details')
|
2017-03-16 18:15:49 +00:00
|
|
|
|
2018-02-05 12:02:35 +00:00
|
|
|
email_branding_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(email_branding_blueprint, url_prefix='/email-branding')
|
2015-12-15 17:14:13 +00:00
|
|
|
|
2017-08-15 17:26:10 +01:00
|
|
|
billing_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(billing_blueprint)
|
2017-11-29 16:28:01 +00:00
|
|
|
|
|
|
|
|
service_callback_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(service_callback_blueprint)
|
2017-08-15 17:26:10 +01:00
|
|
|
|
2023-07-10 11:06:29 -07:00
|
|
|
organization_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(organization_blueprint, url_prefix='/organizations')
|
2018-02-08 14:54:08 +00:00
|
|
|
|
2018-06-05 14:25:24 +01:00
|
|
|
complaint_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(complaint_blueprint)
|
|
|
|
|
|
2021-03-10 11:12:29 +00:00
|
|
|
performance_dashboard_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(performance_dashboard_blueprint)
|
2021-03-04 16:10:53 +00:00
|
|
|
|
2018-06-28 08:39:25 +01:00
|
|
|
platform_stats_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(platform_stats_blueprint, url_prefix='/platform-stats')
|
|
|
|
|
|
2018-10-30 16:26:25 +00:00
|
|
|
template_folder_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(template_folder_blueprint)
|
|
|
|
|
|
2019-12-03 15:53:32 +00:00
|
|
|
upload_blueprint.before_request(requires_admin_auth)
|
|
|
|
|
application.register_blueprint(upload_blueprint)
|
|
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
|
|
|
|
|
def register_v2_blueprints(application):
|
2021-09-15 15:21:20 +01:00
|
|
|
from app.authentication.auth import requires_auth
|
2022-04-05 17:06:08 +01:00
|
|
|
from app.v2.inbound_sms.get_inbound_sms import v2_inbound_sms_blueprint
|
|
|
|
|
from app.v2.notifications import ( # noqa
|
|
|
|
|
get_notifications,
|
|
|
|
|
post_notifications,
|
|
|
|
|
v2_notification_blueprint,
|
2021-03-10 13:55:06 +00:00
|
|
|
)
|
2022-04-05 17:06:08 +01:00
|
|
|
from app.v2.template import ( # noqa
|
|
|
|
|
get_template,
|
|
|
|
|
post_template,
|
|
|
|
|
v2_template_blueprint,
|
2021-03-10 13:55:06 +00:00
|
|
|
)
|
2022-04-05 17:06:08 +01:00
|
|
|
from app.v2.templates.get_templates import v2_templates_blueprint
|
2017-03-22 10:54:15 +00:00
|
|
|
|
2022-04-05 17:06:08 +01:00
|
|
|
v2_notification_blueprint.before_request(requires_auth)
|
|
|
|
|
application.register_blueprint(v2_notification_blueprint)
|
2017-03-28 10:41:25 +01:00
|
|
|
|
2022-04-05 17:06:08 +01:00
|
|
|
v2_templates_blueprint.before_request(requires_auth)
|
|
|
|
|
application.register_blueprint(v2_templates_blueprint)
|
2017-03-22 10:54:15 +00:00
|
|
|
|
2022-04-05 17:06:08 +01:00
|
|
|
v2_template_blueprint.before_request(requires_auth)
|
|
|
|
|
application.register_blueprint(v2_template_blueprint)
|
2015-12-10 10:56:59 +00:00
|
|
|
|
2022-04-05 17:06:08 +01:00
|
|
|
v2_inbound_sms_blueprint.before_request(requires_auth)
|
|
|
|
|
application.register_blueprint(v2_inbound_sms_blueprint)
|
2017-11-03 16:35:22 +00:00
|
|
|
|
2016-01-14 17:45:30 +00:00
|
|
|
|
2017-03-17 16:21:41 +00:00
|
|
|
def init_app(app):
|
2020-04-24 14:36:21 +01:00
|
|
|
|
2016-05-12 13:56:27 +01:00
|
|
|
@app.before_request
|
2016-08-05 10:44:43 +01:00
|
|
|
def record_request_details():
|
2020-04-24 14:36:21 +01:00
|
|
|
CONCURRENT_REQUESTS.inc()
|
|
|
|
|
|
2016-05-12 13:56:27 +01:00
|
|
|
g.start = monotonic()
|
2016-08-05 10:44:43 +01:00
|
|
|
g.endpoint = request.endpoint
|
2016-05-12 13:56:27 +01:00
|
|
|
|
2016-01-20 15:28:39 +00:00
|
|
|
@app.after_request
|
|
|
|
|
def after_request(response):
|
2020-04-24 14:36:21 +01:00
|
|
|
CONCURRENT_REQUESTS.dec()
|
|
|
|
|
|
2023-04-19 09:27:16 -04:00
|
|
|
response.headers.add('X-Content-Type-Options', 'nosniff')
|
2016-01-20 15:51:13 +00:00
|
|
|
return response
|
2016-01-20 15:28:39 +00:00
|
|
|
|
2016-11-24 17:09:56 +00:00
|
|
|
@app.errorhandler(Exception)
|
|
|
|
|
def exception(error):
|
|
|
|
|
app.logger.exception(error)
|
|
|
|
|
# error.code is set for our exception types.
|
2019-07-26 13:26:20 +01:00
|
|
|
msg = getattr(error, 'message', str(error))
|
|
|
|
|
code = getattr(error, 'code', 500)
|
2023-04-19 09:27:16 -04:00
|
|
|
response = make_response(
|
|
|
|
|
jsonify(result='error', message=msg),
|
|
|
|
|
code,
|
|
|
|
|
error.get_headers()
|
|
|
|
|
)
|
|
|
|
|
response.content_type = "application/json"
|
|
|
|
|
return response
|
2019-07-26 13:26:20 +01:00
|
|
|
|
|
|
|
|
@app.errorhandler(WerkzeugHTTPException)
|
|
|
|
|
def werkzeug_exception(e):
|
2023-04-19 09:27:16 -04:00
|
|
|
response = make_response(
|
2019-07-26 13:26:20 +01:00
|
|
|
jsonify(result='error', message=e.description),
|
|
|
|
|
e.code,
|
|
|
|
|
e.get_headers()
|
|
|
|
|
)
|
2023-04-19 09:27:16 -04:00
|
|
|
response.content_type = 'application/json'
|
|
|
|
|
return response
|
2016-11-24 17:09:56 +00:00
|
|
|
|
2016-10-27 11:46:37 +01:00
|
|
|
@app.errorhandler(404)
|
|
|
|
|
def page_not_found(e):
|
|
|
|
|
msg = e.description or "Not found"
|
2023-04-19 09:27:16 -04:00
|
|
|
response = make_response(
|
|
|
|
|
jsonify(result='error', message=msg),
|
|
|
|
|
404,
|
|
|
|
|
e.get_headers()
|
|
|
|
|
)
|
|
|
|
|
response.content_type = 'application/json'
|
|
|
|
|
return response
|
2016-10-27 11:46:37 +01:00
|
|
|
|
2015-12-10 10:56:59 +00:00
|
|
|
|
2016-02-24 17:12:30 +00:00
|
|
|
def create_uuid():
|
|
|
|
|
return str(uuid.uuid4())
|
2016-11-25 13:27:36 +00:00
|
|
|
|
|
|
|
|
|
2017-04-12 17:56:55 +01:00
|
|
|
def create_random_identifier():
|
2022-08-19 15:26:12 +00:00
|
|
|
return ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(16))
|
2017-04-12 17:56:55 +01:00
|
|
|
|
|
|
|
|
|
2023-08-14 15:32:22 -07:00
|
|
|
# TODO maintainability what is the purpose of this? Debugging?
|
2020-04-24 14:36:21 +01:00
|
|
|
def setup_sqlalchemy_events(app):
|
|
|
|
|
|
|
|
|
|
TOTAL_DB_CONNECTIONS = Gauge(
|
|
|
|
|
'db_connection_total_connected',
|
2020-06-12 14:52:04 +01:00
|
|
|
'How many db connections are currently held (potentially idle) by the server',
|
2020-04-24 14:36:21 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
TOTAL_CHECKED_OUT_DB_CONNECTIONS = Gauge(
|
|
|
|
|
'db_connection_total_checked_out',
|
|
|
|
|
'How many db connections are currently checked out by web requests',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
DB_CONNECTION_OPEN_DURATION_SECONDS = Histogram(
|
|
|
|
|
'db_connection_open_duration_seconds',
|
|
|
|
|
'How long db connections are held open for in seconds',
|
|
|
|
|
['method', 'host', 'path']
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# need this or db.engine isn't accessible
|
|
|
|
|
with app.app_context():
|
|
|
|
|
@event.listens_for(db.engine, 'connect')
|
2023-08-08 11:57:57 -07:00
|
|
|
def connect(dbapi_connection, connection_record): # noqa
|
2020-04-24 14:36:21 +01:00
|
|
|
# connection first opened with db
|
|
|
|
|
TOTAL_DB_CONNECTIONS.inc()
|
|
|
|
|
|
|
|
|
|
@event.listens_for(db.engine, 'close')
|
2023-08-08 11:57:57 -07:00
|
|
|
def close(dbapi_connection, connection_record): # noqa
|
2020-04-24 14:36:21 +01:00
|
|
|
# connection closed (probably only happens with overflow connections)
|
|
|
|
|
TOTAL_DB_CONNECTIONS.dec()
|
|
|
|
|
|
|
|
|
|
@event.listens_for(db.engine, 'checkout')
|
2023-08-08 11:57:57 -07:00
|
|
|
def checkout(dbapi_connection, connection_record, connection_proxy): # noqa
|
2020-08-28 11:23:51 +01:00
|
|
|
try:
|
|
|
|
|
# connection given to a web worker
|
|
|
|
|
TOTAL_CHECKED_OUT_DB_CONNECTIONS.inc()
|
|
|
|
|
|
|
|
|
|
# this will overwrite any previous checkout_at timestamp
|
|
|
|
|
connection_record.info['checkout_at'] = time.monotonic()
|
|
|
|
|
|
|
|
|
|
# checkin runs after the request is already torn down, therefore we add the request_data onto the
|
|
|
|
|
# connection_record as otherwise it won't have that information when checkin actually runs.
|
|
|
|
|
# Note: this is not a problem for checkouts as the checkout always happens within a web request or task
|
|
|
|
|
|
|
|
|
|
# web requests
|
|
|
|
|
if has_request_context():
|
|
|
|
|
connection_record.info['request_data'] = {
|
|
|
|
|
'method': request.method,
|
|
|
|
|
'host': request.host,
|
|
|
|
|
'url_rule': request.url_rule.rule if request.url_rule else 'No endpoint'
|
|
|
|
|
}
|
|
|
|
|
# celery apps
|
|
|
|
|
elif current_task:
|
|
|
|
|
connection_record.info['request_data'] = {
|
|
|
|
|
'method': 'celery',
|
|
|
|
|
'host': current_app.config['NOTIFY_APP_NAME'], # worker name
|
|
|
|
|
'url_rule': current_task.name, # task name
|
|
|
|
|
}
|
2021-02-18 12:02:34 +00:00
|
|
|
# anything else. migrations possibly, or flask cli commands.
|
2020-08-28 11:23:51 +01:00
|
|
|
else:
|
|
|
|
|
current_app.logger.warning('Checked out sqlalchemy connection from outside of request/task')
|
|
|
|
|
connection_record.info['request_data'] = {
|
|
|
|
|
'method': 'unknown',
|
|
|
|
|
'host': 'unknown',
|
|
|
|
|
'url_rule': 'unknown',
|
|
|
|
|
}
|
|
|
|
|
except Exception:
|
|
|
|
|
current_app.logger.exception("Exception caught for checkout event.")
|
2020-04-24 14:36:21 +01:00
|
|
|
|
|
|
|
|
@event.listens_for(db.engine, 'checkin')
|
2023-08-08 11:57:57 -07:00
|
|
|
def checkin(dbapi_connection, connection_record): # noqa
|
2020-08-28 11:23:51 +01:00
|
|
|
try:
|
|
|
|
|
# connection returned by a web worker
|
|
|
|
|
TOTAL_CHECKED_OUT_DB_CONNECTIONS.dec()
|
|
|
|
|
|
|
|
|
|
# duration that connection was held by a single web request
|
|
|
|
|
duration = time.monotonic() - connection_record.info['checkout_at']
|
|
|
|
|
|
|
|
|
|
DB_CONNECTION_OPEN_DURATION_SECONDS.labels(
|
|
|
|
|
connection_record.info['request_data']['method'],
|
|
|
|
|
connection_record.info['request_data']['host'],
|
|
|
|
|
connection_record.info['request_data']['url_rule']
|
|
|
|
|
).observe(duration)
|
|
|
|
|
except Exception:
|
|
|
|
|
current_app.logger.exception("Exception caught for checkin event.")
|