def search_for_addresses(request_body, search_type): current_app.logger.info("Performing '%s' address search", search_type) search_results = g.requests.post(config.ADDRESS_API_URL + '/v1/addresses/search', data=json.dumps(request_body, sort_keys=True), headers={ "Content-Type": "application/json", "Accept": "application/json" }) if search_results.status_code == 400: raise ApplicationError(search_results.json(), 400, 400) if search_results.status_code != 200: raise ApplicationError(search_results.json(), 500, 500) if not search_results.json(): current_app.logger.warning("No addresses found for search") raise ApplicationError("No addresses found for search.", 404, 404) current_app.logger.info("Returning address search result") return Response(response=json.dumps( address_response_mapper.map_address_response(search_results.json(), search_type)), status=200, mimetype="application/json")
def search_for_addresses(request_body): current_app.logger.info("Performing address search") search_results = g.requests.post(config.ADDRESS_API_URL + '/v2/addresses/search', data=json.dumps(request_body, sort_keys=True), headers={ "Content-Type": "application/json", "Accept": "application/json" }) if search_results.status_code == 400: raise ApplicationError(search_results.json(), 400, 400) if search_results.status_code != 200: raise ApplicationError(search_results.json(), 500, 500) if not search_results.json(): current_app.logger.warning("No addresses found for search") raise ApplicationError("No addresses found for search.", 404, 404) mapped_resp = address_response_mapper_v_2.map_address_response( search_results.json()) # If index_map argument set to true, get index map for all the addresses if request.args.get('index_map') and request.args.get( 'index_map').lower() == 'true' and config.INDEX_MAP_API_URL: for address in mapped_resp: if 'uprn' in address and address['uprn']: index_map = search_for_index_map(address['uprn']) if index_map: address['index_map'] = index_map current_app.logger.info("Returning address search result") return Response(response=json.dumps(mapped_resp), status=200, mimetype="application/json")
def search_for_index_map(uprn): current_app.logger.info("Performing uprn title search") title_resp = g.requests.get('{}/v1/uprns/{}'.format( config.INDEX_MAP_API_URL, uprn), headers={ "Content-Type": "application/json", "Accept": "application/json" }) if title_resp.status_code == 200: features = [] for title in title_resp.json(): current_app.logger.info("Performing index map search") index_map_resp = g.requests.get('{}/v1/index_map/{}'.format( config.INDEX_MAP_API_URL, title), headers={ "Content-Type": "application/json", "Accept": "application/json" }) if index_map_resp.status_code == 200: index_map_json = index_map_resp.json() features = features + index_map_json['features'] elif index_map_resp.status_code != 404: raise ApplicationError(index_map_resp.json(), 500, 500) if features: return {"type": "FeatureCollection", "features": features} elif title_resp.status_code != 404: raise ApplicationError(title_resp.json(), 500, 500) return None
def get_results_for_boundary(boundary, charge_filter, max_results): """Returns the results of land charges contained in a extent :param boundary: Extent to search for land charges within :param charge_filter: String indicating whether to filter out cancelled charges :param max_results: Max number of land charges to be returned. Defaults to 1000 :return: Json representation of the results """ try: extent_shape = asShape(boundary) geo_extent_shape = shape.from_shape(unary_union(extent_shape), srid=27700) subquery = db.session.query(GeometryFeature.local_land_charge_id, GeometryFeature.geometry) \ .distinct(GeometryFeature.local_land_charge_id) \ .filter(func.ST_DWithin(GeometryFeature.geometry, geo_extent_shape, 0)) \ .subquery() if charge_filter: charge_query = LocalLandCharge.query \ .filter(LocalLandCharge.id == subquery.c.local_land_charge_id) \ .filter(or_(~func.ST_Touches(subquery.c.geometry, geo_extent_shape), ~LocalLandCharge.llc_item.contains( {'charge-sub-category': 'Conditional planning consent'}))) \ .filter(LocalLandCharge.cancelled.isnot(True)) \ .order_by(LocalLandCharge.llc_item[SORT_BY_FIELD].desc()) else: charge_query = LocalLandCharge.query \ .filter(LocalLandCharge.id == subquery.c.local_land_charge_id) \ .filter(or_(~func.ST_Touches(subquery.c.geometry, geo_extent_shape), ~LocalLandCharge.llc_item.contains( {'charge-sub-category': 'Conditional planning consent'}))) \ .order_by(LocalLandCharge.llc_item[SORT_BY_FIELD].desc()) num_results = charge_query.count() if num_results > max_results: current_app.logger.info("Search-area: {0}, " "Number-of-charges: {1}, " "Normal-limit: {2}, " "Too many charges returned".format( boundary, num_results, max_results)) raise ApplicationError("Too many charges, search a smaller area", 507, 507) llc_result = charge_query.all() if llc_result and len(llc_result) > 0: current_app.logger.info("Returning local land charges") return json.dumps( model_mappers.map_llc_result_to_dictionary_list( llc_result)), 200, { 'Content-Type': 'application/json' } else: raise ApplicationError("No land charges found", 404, 404) except (ValueError, TypeError) as err: raise ApplicationError("Unprocessable Entity. {}".format(err), 422, 422)
def get_results_for_originating_authority_charge(authority_charge_id, migrating_authority): """Returns the results of land charges which match a given originating authority charge identifier :param originating_authority_charge_identifier: originating authority charge identifier to search by :return: Json representation of the results """ features = LocalLandCharge.query \ .filter( LocalLandCharge.llc_item["originating-authority-charge-identifier"].astext == authority_charge_id, LocalLandCharge.llc_item["migrating-authority"].astext == migrating_authority ).order_by(LocalLandCharge.llc_item[SORT_BY_FIELD].desc()) \ .all() if features and len(features) > 0: current_app.logger.info( "Returning local land charges by originating authority & migrating authority" ) return json.dumps( model_mappers.map_llc_display_result_to_dictionary_list( features)), 200, { 'Content-Type': 'application/json' } else: raise ApplicationError("No land charges found", 404, 404)
def get_current_timestamp(): cur = None conn = None try: conn = psycopg2.connect(current_app.config['SQLALCHEMY_DATABASE_URI']) cur = conn.cursor() cur.execute("SELECT CURRENT_TIMESTAMP;") result = cur.fetchone() return result[0] except psycopg2.DataError as e: raise ApplicationError( 'Input data error: ' + str(e), 'DB', http_code=400) except (psycopg2.OperationalError, psycopg2.ProgrammingError) as e: raise ApplicationError( 'Database error: ' + str(e), 'DB', http_code=400) finally: if cur: cur.close() if conn: conn.close()
def get_local_land_charge(charge_id): """Get local land charge Returns local land charge with specified charge_id """ current_app.logger.info("Get local land charge by ID '%s'", charge_id) charge_id_param = charge_id.upper() if is_valid_charge_id(charge_id_param): charge_id_base_10 = decode_charge_id(charge_id_param) llc_result = LocalLandCharge.query.get(charge_id_base_10) else: raise ApplicationError("Invalid Land Charge Number", 422, 422) if not llc_result: raise ApplicationError("No land charges found.", 404, 404) current_app.logger.info("Returning local land charge '%s'", charge_id) return json.dumps(model_mappers.map_llc_result_to_dictionary_list(llc_result)), \ 200, {'Content-Type': 'application/json'}
def get_addresses_by_uprn(uprn): current_app.logger.info("Get address by UPRN '%s'", uprn) uprn = uprn.strip() uprn_is_valid = re.match(uprn_regex_check, uprn) if uprn_is_valid is not None: body["search_type"] = "uprn" body["query_value"] = int(uprn) return search_for_addresses(body, "uprn") else: raise ApplicationError("Unprocessable Entity: UPRN is not valid", 422, 422)
def get_addresses_by_postcode(postcode): current_app.logger.info("Get address by postcode '%s'", postcode) postcode = postcode.strip() postcode_is_valid = re.match(postcode_regex_check, postcode) if postcode_is_valid is not None: body["search_type"] = "postcode" body["query_value"] = postcode return search_for_addresses(body, "postcode") else: raise ApplicationError("Unprocessable Entity: Postcode is not valid", 422, 422)
def get_local_land_charges(): """Get a list of land charges Returns all if no parameter is required, otherwise it will return those contained in bounding box. """ current_app.logger.info("Get local land charges by geometry search") geo_json_extent = request.args.get('boundingBox') if geo_json_extent: try: json_extent = json.loads( base64.b64decode(geo_json_extent).decode()) extent_shape = asShape(json_extent) features = GeometryFeature.query.filter( func.ST_Intersects( GeometryFeature.geometry, shape.from_shape(extent_shape, srid=27700))).options( db.joinedload( GeometryFeature.local_land_charge)).all() res_set = set() for feature in features: res_set.add(feature.local_land_charge) llc_result = list(res_set) except (ValueError, TypeError) as err: raise ApplicationError("Unprocessable Entity. {}".format(err), 422, 422) else: current_app.logger.warning( "No bounding box supplied - returning all local land charges") llc_result = LocalLandCharge.query.all() if not llc_result or len(llc_result) == 0: raise ApplicationError("No land charges found", 404, 404) current_app.logger.info("Returning local land charges") return json.dumps(model_mappers.map_llc_result_to_dictionary_list(llc_result)), \ 200, {'Content-Type': 'application/json'}
def post_local_land_charges(): """Get a list of land charges""" current_app.logger.info("Get local land charges by geometry search") geo_json_extent = request.get_json() charge_filter = request.args.get('filter') max_results = (int(request.args.get('maxResults')) if request.args.get('maxResults') else 1000) if geo_json_extent: return get_results_for_boundary(geo_json_extent, charge_filter, max_results) else: raise ApplicationError("Failed to provide a search area.", 400, 400)
def get_local_land_charge_history(charge_id): """Get history of land charge. Returns all history for local land charge with charge_id """ current_app.logger.info("Get local land charge history by ID '%s'", charge_id) charge_id_param = charge_id.upper() if is_valid_charge_id(charge_id_param): charge_id_base_10 = decode_charge_id(charge_id_param) llc_result = LocalLandChargeHistory.query.filter(LocalLandChargeHistory.id == charge_id_base_10)\ .order_by(LocalLandChargeHistory.entry_timestamp).all() else: raise ApplicationError("Invalid Land Charge Number", 422, 422) if not llc_result: raise ApplicationError("No land charge history found.", 404, 404) current_app.logger.info("Returning local land charge history") return json.dumps(model_mappers.map_llc_history_result_to_dictionary_list(llc_result)), \ 200, {'Content-Type': 'application/json'}
def before_request(): # Sets the transaction trace id into the global object if it has been provided in the HTTP header from the caller. # Generate a new one if it has not. We will use this in log messages. g.trace_id = request.headers.get('X-Trace-ID', uuid.uuid4().hex) # We also create a session-level requests object for the app to use with the header pre-set, so other APIs will # receive it. These lines can be removed if the app will not make requests to other LR APIs! g.requests = requests.Session() g.requests.headers.update({'X-Trace-ID': g.trace_id}) if '/health' in request.path: return if 'Authorization' not in request.headers: raise ApplicationError("Missing Authorization header", "AUTH1", 401) try: validate( app.config['AUTHENTICATION_API_URL'] + '/authentication/validate', request.headers['Authorization'], g.requests) except ValidationFailure as fail: raise ApplicationError(fail.message, "AUTH1", 401) bearer_jwt = request.headers['Authorization'] g.requests.headers.update({'Authorization': bearer_jwt})
def get_local_land_charges(): """Get a list of land charges""" current_app.logger.info("Get local land charges by filter search") further_information_reference = request.args.get( 'furtherInformationReference') authority_charge_id = request.args.get('authority_charge_id') migrating_authority = request.args.get('migrating_authority') if further_information_reference: return get_results_for_further_information_reference( further_information_reference) elif (authority_charge_id and migrating_authority): return get_results_for_originating_authority_charge( authority_charge_id, migrating_authority) else: raise ApplicationError("Failed to provide a filter.", 400, 400)
def encode_base_31(charge_id): """Encodes the given base 10 charge_id into a base 31 string. Throws ApplicationError if charge_id is less than ChargeId.FLOOR or greater than ChargeId.CEILING. """ if charge_id < ChargeId.FLOOR or charge_id > ChargeId.CEILING: raise ApplicationError( 'The given charge id ({}) is less than the allowed floor ({}), or greater than the ' 'allowed ceiling ({})'.format(charge_id, ChargeId.FLOOR, ChargeId.CEILING), 500) encoded = '' while charge_id > 0: charge_id, remainder = divmod(charge_id, 31) encoded = ChargeId.CHARACTERS[remainder] + encoded return encoded
def get_results_for_further_information_reference( further_information_reference): """Returns the results of land charges which match a given further information reference :param further_information_reference: Authority reference to search by :return: Json representation of the results """ features = LocalLandCharge.query \ .filter( func.lower(LocalLandCharge.further_information_reference) == func.lower(further_information_reference) ).order_by(LocalLandCharge.llc_item[SORT_BY_FIELD].desc()) \ .all() if features and len(features) > 0: current_app.logger.info("Returning local land charges") return json.dumps( model_mappers.map_llc_result_to_dictionary_list(features)), 200, { 'Content-Type': 'application/json' } else: raise ApplicationError("No land charges found", 404, 404)