def nomenklatura_create_entity(): data = request.values if data: if 'name' not in data: raise exceptions.APIError(message='Missing field: name', field='name', resource=rule_link(request.url_rule)) if 'attributes' in data: # rapidpro doesnt send json post data. instead we'll have a form field # with a json string inside rather than a dict from request.json if not isinstance(data['attributes'], dict): try: # request.values is a CombinedMultiDict, so convert to dict data = data.to_dict() data['attributes'] = json.loads(data['attributes']) except ValueError: # we didn't get json or anything json-like, so abort abort(400) payload = {'format': 'json'} payload['api_key'] = NOMENKLATURA_API_KEY # nomenklatura complains if we don't include `attributes` payload['attributes'] = {} # attribute values for nomenklatura must be strings (no ints!) # as nomenklatura uses postgres' hstore to store attribute kv pairs # TODO this smells dodgy if 'attributes' in data: payload['attributes'] = { str(k): str(v) for k, v in data['attributes'].items() } payload['dataset'] = data['dataset'] payload['name'] = data['name'] if 'description' in data: payload['description'] = data['description'] result = requests.post(NOMENKLATURA_URL + 'entities', headers=NOMENKLATURA_HEADERS, json=payload) if result.json().get('errors') is not None: abort( make_response(jsonify(message=result.json().get('errors')), 400)) # TODO pass on a better error message from nomenklatura if non200 if result.status_code == requests.codes.ok: return create_response({ 'entity': result.json(), '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def nomenklatura_update_entity_attributes(): data = request.values if data: if 'entity' not in data: raise exceptions.APIError(message='Missing field: entity', field='entity', resource=rule_link(request.url_rule)) if 'attributes' not in data: raise exceptions.APIError(message='Missing field: attributes', field='attributes', resource=rule_link(request.url_rule)) # rapidpro doesnt send json post data. instead we'll have a form field # with a json string inside rather than a dict from request.json if not isinstance(data['attributes'], dict): try: # request.values is a CombinedMultiDict, so convert to dict data = data.to_dict() data['attributes'] = json.loads(data['attributes']) except JSONDecodeError: # we didn't get json or anything json-like, so abort abort(400) payload = {'format': 'json'} payload['api_key'] = NOMENKLATURA_API_KEY result = requests.get(_url_for_entity(data['entity']), json=payload) results = result.json() # update endpoint requires name payload['name'] = results['name'] updated_attributes = results['attributes'] updated_attributes.update(data['attributes']) # attribute values for nomenklatura must be strings (no ints!) # as nomenklatura uses postgres' hstore to store attribute kv pairs # TODO this smells dodgy payload['attributes'] = { str(k): str(v) for k, v in updated_attributes.items() } result = requests.post(_url_for_entity(data['entity']), headers=NOMENKLATURA_HEADERS, json=payload) # TODO pass on a better error message from nomenklatura if non200 if result.status_code == requests.codes.ok: return create_response({ 'entity': result.json(), '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def list_resources(): # http://en.wikipedia.org/wiki/HATEOAS children = [] for rule in current_app.url_map.iter_rules(): # e.g, `api.list_resources` if rule.endpoint.startswith(core_bp.name): # don't add current endpoint as child if rule.endpoint != request.url_rule.endpoint: children.append(rule_link(rule)) return create_response({'_links': { 'self': rule_link(request.url_rule), 'child': children}})
def categorize_birth_year(): data = request.json if data: values = data.get('values') years = [value for value in values if value.get('label') == 'born'] if years: year = years[0]['value'] try: year = int(year) except BaseException: year = int(year.split(' ')[0]) age = datetime.datetime.utcnow().year - year if age <= 13: category = 'child' elif 14 < age < 18: category = 'youth' elif age >= 18: category = 'adult' else: abort(400) return create_response({ 'age': age, 'age_category': category, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def nominatum(): """ Look up a given place name on OSM via http://wiki.openstreetmap.org/wiki/Nominatim Nominatim does not do any fuzzy matching, so not so helpful for user-provided spellings. """ data = request.values if data: payload = {'format': 'json'} payload['q'] = data['query'] country = data.get('country') if country: payload['countrycodes'] = country result = requests.get(NOMINATUM_URL, params=payload) results = result.json() matches = list() for match in results: if match['type'] == 'village': if match['class'] == 'place': matches.append(match) return create_response({ 'matches': matches, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def calculate_last_menses(): data = request.json if data: values = data.get('values') weeks = [ value for value in values if value.get('label') == 'weeks_since_last_menses' ] if weeks: weeks = weeks[0]['value'] try: weeks = int(weeks) except BaseException: weeks = int(weeks.split(' ')[0]) days_since_menses = weeks * 7 last_menses_date = datetime.datetime.utcnow() - \ datetime.timedelta(days=days_since_menses) expected_delivery_date = last_menses_date + \ datetime.timedelta(days=280) return create_response({ 'days_since_menses': days_since_menses, 'last_menses': last_menses_date, 'expected_delivery_date': expected_delivery_date, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def nomenklatura_retrieve_entity_attributes(): data = request.values if data: if 'entity' not in data: raise exceptions.APIError(message='Missing field: entity', field='entity', resource=rule_link(request.url_rule)) payload = {'format': 'json'} payload['api_key'] = NOMENKLATURA_API_KEY result = requests.get(_url_for_entity(data['entity']), json=payload) results = result.json() if 'attribute' in data: query = data['attribute'] if (results.get('attributes') is not None) and (query in results['attributes'].keys()): return create_response({ 'entity': results, query: results['attributes'][query], '_links': { 'self': rule_link(request.url_rule) } }) else: return create_response({ 'entity': results, query: None, '_links': { 'self': rule_link(request.url_rule) } }) return create_response({ 'entity': results, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def shipment_received(): """ Called when shipment has been received by end user """ shipment = _update_shipment_status(request, SHIPMENT_RECEIVED_FIELDS) if shipment: return create_response({ 'shipment': shipment, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def notify_death(): data = request.json if data: data.update({'type': 'death'}) g.db.save_doc(data) docid = data['_id'] return create_response({ 'id': docid, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def update_shipment(): """ Called when shipment status is updated by end user """ shipment = _update_shipment_status(request, SHIPMENT_UPDATE_FIELDS) # TODO if we have a revised date estimate, # then schedule flow for after revised date if shipment: return create_response({ 'shipment': shipment, '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def expected_shipments_for_contact(): if request.json is not None: data = request.json else: data = request.values if data: phone = _format_phone(data.get('phone')) if phone: shipments_doc = get_or_create_shipments_doc(phone) shipments_doc = g.db.open_doc('shipments-%s' % phone) shipments = shipments_doc.get('shipments') if shipments: return create_response({ 'shipment': shipments.pop(), '_links': { 'self': rule_link(request.url_rule) } }) abort(400)
def nomenklatura(): # TODO better logging log = dict({'timestamp': datetime.datetime.utcnow().isoformat()}) data = request.values if data: try: # `data` is likely werkzeug's ImmutableDict inside a # CombinedMultiDict so try to cast with `to_dict` log.update({'request_data': data.to_dict()}) except AttributeError: # `data` can also be a vanilla dict log.update({'request_data': data}) if 'query' not in data: raise exceptions.APIError(message='Missing field: query', field='query', resource=rule_link(request.url_rule)) if 'dataset' not in data: raise exceptions.APIError(message='Missing field: dataset', field='dataset', resource=rule_link(request.url_rule)) payload = {'format': 'json'} # titlecase the query for better chance of exact match query = data['query'].title() payload['query'] = query payload['api_key'] = NOMENKLATURA_API_KEY log.update({'nomenklatura_payload': payload}) result = requests.get(_url_for_dataset(data['dataset']), params=payload) try: results = result.json() except JSONDecodeError: results = [] matches = list() if len(results) > 0: for match in results['result']: if match['match'] is True: matches.append(match) else: if match['score'] >= 50: matches.append(match) log.update({'nomenklatura_response': results}) log.update({'matches': matches}) g.db.save_doc(log) if len(matches) < 1: # attempt to match each token of query match = _try_harder(data, query, 1) if match is None: # attempt to match each bigram of query match = _try_harder(data, query, 2) if match is None: return create_response({ 'message': _localized_fail(data.get('lang', 'eng')) % _format_type(data['dataset']), 'match': None, '_links': { 'self': rule_link(request.url_rule) } }) if match is not None: # dataset_name = match[0]['type'][0]['name'] return create_response({ 'message': _localized_success(data.get('lang', 'eng')) % { 'loc_type': _format_type(data['dataset']), 'match': match[0]['name'] }, 'match': match[0]['name'], '_links': { 'self': rule_link(request.url_rule) } }) else: # dataset_name = matches[0]['type'][0]['name'] return create_response({ 'message': _localized_success(data.get('lang', 'eng')) % { 'loc_type': _format_type(data['dataset']), 'match': matches[0]['name'] }, 'match': matches[0]['name'], '_links': { 'self': rule_link(request.url_rule) } }) abort(400)