""" Presentation and formatting of responses to user. Includes json and http status. """ from collections import OrderedDict import json from flask import g from manualadjustment import config from manualadjustment.connectors.mysql import ArtRelationsBaseModel from utils.sql_alchemy_json_encoder import SQLAlchemyJSONEncoder ADJUSTMENT_PROPERTIES = [ 'id', 'adjust_for_period_id', 'amount', 'amount_in_original_currency', 'apply_to_period_id', 'attachment_location', 'category_id', 'comment', 'created_by', 'currencies_id', 'date_added', 'parent_id', 'parent_type', 'updated_timestamp'] FK_PROPERTIES = { 'adjust_for_period': ['month', 'year'], 'apply_to_period': ['month', 'year'], } def get_manual_adj_response(result): """Take raw data and construct a json response. Args: manual_adjustments (list) page_count (int) page_offset (int) page_limit (int) Returns: json (string) containing manual adj and pagination http status (int) """ data = result.data json_response = { 'manual_adjustments': [], 'pagination': {'offset': data['page_offset'], 'limit': data['page_limit'], 'count': data['page_count']}} for manual_adjustment in data['manual_adjustments']: json_response['manual_adjustments'].append( adjustment_dict(manual_adjustment)) return json.dumps( json_response, cls=SQLAlchemyJSONEncoder), result.status def error_response(res): """Return an error response in json format. Args: res (Result object) Returns: json (string) containing error info http status (int) """ error_json = {'error': res.error, 'error_detail': res.error_detail} # TODO: shouldn't this be sent to sentry? g.log.error(json.dumps(error_json)) return json.dumps(error_json), res.status def manual_adj_response(result): """Take result object and construct a json response. Args: manual adjustment result object Returns: json (string) containing manual adjustment http status (int) """ if result.success: return json.dumps(adjustment_json( result.data), cls=SQLAlchemyJSONEncoder), result.status else: return error_response(result) def adjustment_json(adjustment): """Generate a data for a JSON representation of a single manual adjustment. Args: adjustment (ManualAdjustment object) Returns: dict: adjustment data to return in JSON response """ json_dict = OrderedDict() ma_dict = adjustment.__dict__ for prop_name in ADJUSTMENT_PROPERTIES: json_dict[prop_name] = ma_dict.get(prop_name) for table, columns in FK_PROPERTIES.items(): if table not in ma_dict: continue fk_dict = ma_dict[table].__dict__ json_dict[table] = {col: fk_dict.get(col) for col in columns} return json_dict def adjustment_dict(adjustment, max_depth=5, depth=0): """Generate a dict for a JSON serialization of a single manual adjustment. Unlike adjustment_json, which has a filter list, this tries to prepare objects for JSON up to a depth level. Args: adjustment (ManualAdjustment object): result object to make dict. max_depth (int): levels deep (sqlalchemy relationships) to recurse. depth (int): recursion control depth parameter. Returns: dict: adjustment data to use in JSON serialization. """ if depth == max_depth: if isinstance(adjustment, ArtRelationsBaseModel): return adjustment.__dict__ return adjustment if isinstance(adjustment, list): return [adjustment_dict(a, max_depth, depth + 1) for a in adjustment] if not isinstance(adjustment, ArtRelationsBaseModel): return adjustment d = {} for key, value in adjustment.__dict__.items(): if key.startswith('_sa_'): continue d[key] = adjustment_dict(value, max_depth, depth + 1) return d def health_response(health_text): """Construct response for health check. Todo: (possibly) perform vital system checks: db connectivity s3 connectivity respond with latency of each Returns: (tuple) health_text, status_code """ return (health_text, 200) def service_info(): """Return information about the service and environment.""" json_response = {'Environment': config.ENVIRONMENT, 'S3 bucket name': config.bucket_name, 'SQS queue name': config.queue_name} return json.dumps(json_response), 200