cr-connect-workflow/crc/__init__.py

176 lines
6.5 KiB
Python

import logging.config
import os
import traceback
import click
import sentry_sdk
import connexion
from SpiffWorkflow import WorkflowException
from SpiffWorkflow.exceptions import WorkflowTaskExecException
from connexion import ProblemException
from flask import Response
from flask_cors import CORS
from flask_marshmallow import Marshmallow
from flask_mail import Mail
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from sentry_sdk.integrations.flask import FlaskIntegration
from apscheduler.schedulers.background import BackgroundScheduler
from werkzeug.middleware.proxy_fix import ProxyFix
connexion_app = connexion.FlaskApp(__name__)
app = connexion_app.app
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) # respect the X-Forwarded-Proto if behind a proxy.
app.config.from_object('config.default')
if "TESTING" in os.environ and os.environ["TESTING"] == "true":
app.config.from_object('config.testing')
app.config.from_pyfile('../config/testing.py')
import logging
logging.basicConfig(level=logging.INFO)
else:
app.config.root_path = app.instance_path
app.config.from_pyfile('config.py', silent=True)
from config.logging import logging_config
logging.config.dictConfig(logging_config)
db = SQLAlchemy(app)
""":type: sqlalchemy.orm.SQLAlchemy"""
session = db.session
""":type: sqlalchemy.orm.Session"""
scheduler = BackgroundScheduler()
# Mail settings
mail = Mail(app)
migrate = Migrate(app, db)
ma = Marshmallow(app)
from crc import models
from crc import api
from crc.api import admin
from crc.services.workflow_service import WorkflowService
connexion_app.add_api('api.yml', base_path='/v1.0')
# needed function to avoid circular import
def process_waiting_tasks():
with app.app_context():
WorkflowService.do_waiting()
@app.before_first_request
def init_scheduler():
if app.config['PROCESS_WAITING_TASKS']:
scheduler.add_job(process_waiting_tasks, 'interval', minutes=1)
scheduler.add_job(WorkflowService().process_erroring_workflows, 'interval', minutes=1440)
scheduler.start()
# Convert list of allowed origins to list of regexes
origins_re = [r"^https?:\/\/%s(.*)" % o.replace('.', '\.') for o in app.config['CORS_ALLOW_ORIGINS']]
cors = CORS(connexion_app.app, origins=origins_re)
# Sentry error handling
if app.config['SENTRY_ENVIRONMENT']:
sentry_sdk.init(
environment=app.config['SENTRY_ENVIRONMENT'],
dsn="https://25342ca4e2d443c6a5c49707d68e9f40@o401361.ingest.sentry.io/5260915",
integrations=[FlaskIntegration()]
)
# Connexion Error handling
def render_errors(exception):
from crc.api.common import ApiError, ApiErrorSchema
error = ApiError(code=exception.title, message=exception.detail, status_code=exception.status)
return Response(ApiErrorSchema().dumps(error), status=500, mimetype="text/json")
connexion_app.add_error_handler(ProblemException, render_errors)
print('=== USING THESE CONFIG SETTINGS: ===')
print('APPLICATION_ROOT = ', app.config['APPLICATION_ROOT'])
print('CORS_ALLOW_ORIGINS = ', app.config['CORS_ALLOW_ORIGINS'])
print('DB_HOST = ', app.config['DB_HOST'])
print('LDAP_URL = ', app.config['LDAP_URL'])
print('PB_BASE_URL = ', app.config['PB_BASE_URL'])
print('PB_ENABLED = ', app.config['PB_ENABLED'])
print('PRODUCTION = ', app.config['PRODUCTION'])
print('TESTING = ', app.config['TESTING'])
print('TEST_UID = ', app.config['TEST_UID'])
print('ADMIN_UIDS = ', app.config['ADMIN_UIDS'])
@app.cli.command()
def clear_db():
"""Load example data into the database."""
from example_data import ExampleDataLoader
ExampleDataLoader.clean_db()
@app.cli.command()
@click.argument("study_id")
@click.argument("category", required=False)
@click.argument("spec_id", required=False)
def validate_all(study_id, category=None, spec_id=None):
"""Step through all the local workflows and validate them, returning any errors. This make take forever.
Please provide a real study id to use for validation, an optional category can be specified to only validate
that category, and you can further specify a specific spec, if needed."""
from crc.services.workflow_service import WorkflowService
from crc.services.workflow_processor import WorkflowProcessor
from crc.services.workflow_spec_service import WorkflowSpecService
from crc.api.common import ApiError
from crc.models.study import StudyModel
from crc.models.user import UserModel
from flask import g
logging.root.removeHandler(logging.root.handlers[0])
study = session.query(StudyModel).filter(StudyModel.id == study_id).first()
g.user = session.query(UserModel).filter(UserModel.uid == study.user_uid).first()
g.token = "anything_is_fine_just_need_something."
specs = WorkflowSpecService().get_specs()
statuses = WorkflowProcessor.run_master_spec(WorkflowSpecService().master_spec, study)
for spec in specs:
if spec_id and spec_id != spec.id:
continue
if category and (not spec.category or spec.category.display_name != category):
continue
print("-----------------------------------------")
print(f"{spec.category.display_name} / {spec.id}")
print("-----------------------------------------")
if spec.id in statuses and statuses[spec.id]['status'] == 'disabled':
print(f"Skipping {spec.id} in category {spec.category.display_name}, it is disabled for this study.")
continue
try:
WorkflowService.test_spec(spec.id, validate_study_id=study_id)
print('Success!')
except ApiError as e:
if e.code == 'disabled_workflow':
print(f"Skipping {spec.id} in category {spec.category.display_name}, it is disabled for this study.")
else:
print(f"API Error {e.code}, validate workflow {spec.id} in Category {spec.category.display_name}. {e.message}")
for t in e.task_trace:
print(f"---> {t}")
continue
except WorkflowTaskExecException as e:
print(f"Workflow Error, {e}, in Task {e.task.name} validate workflow {spec.id} in Category {spec.category.display_name}")
for t in e.task_trace:
print(f"---> {t}")
continue
except Exception as e:
print(f"Unexpected Error ({e.__class__.__name__}), {e} validate workflow {spec.id} in Category {spec.category.display_name}")
# printing stack trace
traceback.print_exc()
print(e)
return