Skip to content

Commit 773c238

Browse files
committed
Merge branch 'develop'
2 parents a4bdda6 + 4bd0a08 commit 773c238

File tree

12 files changed

+162
-87
lines changed

12 files changed

+162
-87
lines changed

README.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,45 @@ Clone the repo from github `$ git clone [email protected]:RyanSept/HealthTools.KE-a
2222

2323
Change directory into package `$ cd HealthTools.KE-api`
2424

25-
Install the dependencies by running `$ pip install requirements.txt`
25+
Install the dependencies by running `$ pip install -r requirements.txt`
26+
27+
Install Memcached
28+
* If on linux follow this [link](https://github.com/memcached/memcached/wiki/Install)
29+
* On mac use `brew install memcached`
2630

2731
You can set the required environment variables like so
2832
```<>
33+
$ export APP_DEBUG=<False> # True or False
34+
$ export MEMCACHED_URL=<memcache_url:port> # defaults to 127.0.0.1:8000
2935
$ export GA_TRACKING_ID=<google-analytics-tracking-id>
3036
$ export SMS_USER=<sms-provider-user-id>
3137
$ export SMS_PASS=<sms-provider-passcode>
3238
$ export SMS_SHORTCODE=<sms-provider-shortcode>
3339
$ export SMS_SEND_URL=<url-for-sms-provider>
34-
$ export CONFIG=<config-mode> # eg. "healthtools_ke_api.settings.DevConfig"
35-
$ export AWS_ACCESS_KEY_ID=<aws-access-key-id>
40+
$ export AWS_ACCESS_KEY=<aws-access-key-id>
3641
$ export AWS_SECRET_KEY=<aws-secret-key>
37-
$ export AWS_REGION="<aws-region>
42+
$ export ES_HOST=<elasticsearch_host_endpoint> (DO NOT SET THIS IF YOU WOULD LIKE TO USE ELASTIC SEARCH LOCALLY ON YOUR MACHINE)
43+
$export ES_PORT=<elasticsearch_port>
3844
```
45+
**If you want to use elasticsearch locally on your machine use the following instructions to set it up**
46+
47+
For linux and windows users, follow instructions from this [link](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html)
48+
49+
For mac users run `brew install elasticsearch` on your terminal
50+
51+
Run memcached on your terminal `$ memcached -p <port you set MEMCACHED_URL to run on>(default: 8000)`
52+
53+
If you set up elasticsearch locally run it `$ elasticsearch`
3954

4055
You can now run the server `$ python manage.py` or `gunicorn manage:app` for production.
4156

4257

58+
4359
## Running the tests
4460

61+
Run memcached on your terminal `$ memcached -p <port you set MEMCACHED_URL to run on>(default: 8000)`
62+
63+
_**make sure if you use elasticsearch locally, it's running**_
64+
4565
Use nosetests to run tests (with stdout) like this:
46-
```$ nosetests --nocapture```
66+
```$ nosetests --nocapture```

healthtools_ke_api/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@
1212

1313

1414
app = Flask(__name__)
15-
try:
16-
app.config.from_object(os.getenv('CONFIG'))
17-
except KeyError:
18-
print "No config has been specified for use in the environment variables."
19-
sys.exit()
2015

2116
app.register_blueprint(doctors_api, url_prefix='/doctors')
2217
app.register_blueprint(nurses_api, url_prefix='/nurses')

healthtools_ke_api/settings.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,28 @@
11
import os
22

3+
DEBUG = os.getenv("APP_DEBUG", False)
4+
35
# Url of memcached server
4-
MEMCACHED_URL = os.getenv("MEMCACHED_URL")
6+
MEMCACHED_URL = os.getenv("MEMCACHED_URL", "127.0.0.1:11211")
57

68
# Amazon Web Services configs
7-
AWS_CONFIGS = {
8-
"aws_access_key_id": os.getenv("AWS_ACCESS_KEY_ID"),
9-
"aws_secret_access_key": os.getenv("AWS_SECRET_KEY"),
10-
"region_name": os.getenv("AWS_REGION"),
9+
AWS = {
10+
"access_key": os.getenv("AWS_ACCESS_KEY"),
11+
"secret_key": os.getenv("AWS_SECRET_KEY"),
12+
"region": os.getenv("AWS_REGION", "eu-west-1"),
1113
}
1214

15+
# Elastic Search configs
16+
ES = {
17+
"host": os.getenv("ES_HOST", "127.0.0.1"),
18+
"port": os.getenv("ES_PORT", "9200"),
19+
"index": "healthtools-ke"
20+
}
1321

14-
class Config(object):
15-
# Google Analytics tracking id
16-
GA_TRACKING_ID = os.environ.get('GA_TRACKING_ID')
17-
18-
# SMS provider credentials
19-
SMS_USER = os.environ.get('SMS_USER')
20-
SMS_PASS = os.environ.get('SMS_PASS')
21-
SMS_SHORTCODE = os.environ.get('SMS_SHORTCODE')
22-
23-
DOCTORS_SEARCH_URL = "https://6ujyvhcwe6.execute-api.eu-west-1.amazonaws.com/prod"
24-
NURSE_SEARCH_URL = "https://api.healthtools.codeforafrica.org/nurses/search.json"
25-
CO_SEARCH_URL = "https://vfblk3b8eh.execute-api.eu-west-1.amazonaws.com/prod"
26-
NHIF_SEARCH_URL = "https://t875kgqahj.execute-api.eu-west-1.amazonaws.com/prod"
27-
HF_SEARCH_URL = "https://187mzjvmpd.execute-api.eu-west-1.amazonaws.com/prod"
28-
22+
# Google Analytics tracking id
23+
GA_TRACKING_ID = os.environ.get('GA_TRACKING_ID')
2924

30-
# development config
31-
class DevConfig(Config):
32-
DEBUG = True
33-
SERVER_NAME = "127.0.0.1:8000"
25+
# SMS provider credentials
26+
SMS_USER = os.environ.get('SMS_USER')
27+
SMS_PASS = os.environ.get('SMS_PASS')
28+
SMS_SHORTCODE = os.environ.get('SMS_SHORTCODE')

healthtools_ke_api/tests/test_clinical_officers_api.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from unittest import TestCase
22
from healthtools_ke_api import app
3-
from healthtools_ke_api.views.clinical_officers import get_clinical_officers_from_cloudsearch
3+
from healthtools_ke_api.views.elastic_search import Elastic
44

55

66
class TestClinicalOfficersAPI(TestCase):
77
def setUp(self):
88
self.client = app.test_client()
9+
self.es = Elastic()
910

10-
def test_gets_cos_from_cloudsearch(self):
11-
clinical_officers = get_clinical_officers_from_cloudsearch("Marie")
11+
def test_gets_cos_from_elasticsearch(self):
12+
clinical_officers = self.es.get_from_elasticsearch("clinical-officers", "Jacob")
1213
self.assertTrue(len(clinical_officers) > 0)
1314

1415
def test_cos_endpoint_with_bad_query(self):

healthtools_ke_api/tests/test_doctors_api.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from unittest import TestCase
22
from healthtools_ke_api import app
3-
from healthtools_ke_api.views.doctors import get_doctors_from_cloudsearch
3+
from healthtools_ke_api.views.elastic_search import Elastic
44

55

66
class TestDoctorsAPI(TestCase):
77
def setUp(self):
88
self.client = app.test_client()
9+
self.es = Elastic()
910

10-
def test_gets_doctors_from_cloudsearch(self):
11-
doctors = get_doctors_from_cloudsearch("Marie")
11+
def test_gets_doctors_from_elasticsearch(self):
12+
doctors = self.es.get_from_elasticsearch("doctors", "BHATT")
1213
self.assertTrue(len(doctors) > 0)
1314

1415
def test_doctors_endpoint_with_bad_query(self):

healthtools_ke_api/views/clinical_officers.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import boto3
21
from flask import Blueprint, request, jsonify, current_app
32

3+
from elastic_search import Elastic
44
from healthtools_ke_api.analytics import track_event
5-
from healthtools_ke_api.settings import AWS_CONFIGS
6-
75

86
clinical_officers_api = Blueprint('clinical_officers_api', __name__)
9-
COS_CLOUDSEARCH_ENDPOINT = "http://doc-cfa-healthtools-ke-cos-nhxtw3w5goufkzram4er7sciz4.eu-west-1.cloudsearch.amazonaws.com/"
10-
cloudsearch_client = boto3.client("cloudsearchdomain",
11-
endpoint_url=COS_CLOUDSEARCH_ENDPOINT,
12-
**AWS_CONFIGS)
137

148

159
@clinical_officers_api.route('/', methods=['GET'])
@@ -21,7 +15,9 @@ def index():
2115
"name": "API to Kenyan Clinical Officers registry",
2216
"authentication": [],
2317
"endpoints": {
24-
"/": {"methods": ["GET"]},
18+
"/": {
19+
"methods": ["GET"]
20+
},
2521
"/clinical-officers/search.json": {
2622
"methods": ["GET"],
2723
"args": {
@@ -46,7 +42,8 @@ def search():
4642

4743
# get clinical_officers by that name from aws
4844
response = {}
49-
clinical_officers = get_clinical_officers_from_cloudsearch(query)
45+
es = Elastic()
46+
clinical_officers = es.get_from_elasticsearch('clinical-officers', query)
5047

5148
if not clinical_officers:
5249
response["message"] = "No clinical officer by that name found."
@@ -65,11 +62,3 @@ def search():
6562
"message": str(err),
6663
"data": {"clinical_officers": []}
6764
})
68-
69-
70-
def get_clinical_officers_from_cloudsearch(query):
71-
'''
72-
Get clinical officers from AWS cloudsearch
73-
'''
74-
results = cloudsearch_client.search(query=query, size=10000)
75-
return results["hits"]["hit"]

healthtools_ke_api/views/doctors.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import boto3
21
from flask import Blueprint, request, jsonify, current_app
32

43
from healthtools_ke_api.analytics import track_event
5-
from healthtools_ke_api.settings import AWS_CONFIGS
6-
4+
from elastic_search import Elastic
75

86
doctors_api = Blueprint('doctors_api', __name__)
9-
DOCTORS_CLOUDSEARCH_ENDPOINT = "http://doc-cfa-healthtools-ke-doctors-m34xee6byjmzcgzmovevkjpffy.eu-west-1.cloudsearch.amazonaws.com/"
10-
cloudsearch_client = boto3.client("cloudsearchdomain",
11-
endpoint_url=DOCTORS_CLOUDSEARCH_ENDPOINT,
12-
**AWS_CONFIGS)
137

148

159
@doctors_api.route('/', methods=['GET'])
@@ -46,13 +40,14 @@ def search():
4640

4741
# get doctors by that name from aws
4842
response = {}
49-
doctors = get_doctors_from_cloudsearch(query)
43+
es = Elastic()
44+
doctors = es.get_from_elasticsearch('doctors', query)
5045

5146
if not doctors:
5247
response["message"] = "No doctor by that name found."
5348

5449
track_event(current_app.config.get('GA_TRACKING_ID'), 'Doctor', 'search',
55-
request.remote_addr, label=query, value=len(doctors))
50+
request.remote_addr, label=query, value=len(doctors))
5651
response["data"] = {"doctors": doctors}
5752
response["status"] = "success"
5853

@@ -64,11 +59,3 @@ def search():
6459
"message": str(err),
6560
"data": {"doctors": []}
6661
})
67-
68-
69-
def get_doctors_from_cloudsearch(query):
70-
'''
71-
Get doctors from AWS cloudsearch
72-
'''
73-
results = cloudsearch_client.search(query=query, size=10000)
74-
return results["hits"]["hit"]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from elasticsearch import Elasticsearch, RequestsHttpConnection
2+
from requests_aws4auth import AWS4Auth
3+
4+
from healthtools_ke_api.settings import AWS, ES
5+
from serializer import JSONSerializerPython2
6+
import re
7+
8+
9+
class Elastic(object):
10+
"""
11+
Common class for elastic search client and methods
12+
"""
13+
def __init__(self):
14+
# client host for aws elastic search service
15+
if "aws" in ES["host"]:
16+
# set up authentication credentials
17+
awsauth = AWS4Auth(AWS["access_key"], AWS["secret_key"], AWS["region"], 'es')
18+
self.es_client = Elasticsearch(
19+
hosts=ES["host"],
20+
port=int(ES["port"]),
21+
http_auth=awsauth,
22+
use_ssl=True,
23+
verify_certs=True,
24+
connection_class=RequestsHttpConnection,
25+
serializer=JSONSerializerPython2()
26+
)
27+
else:
28+
self.es_client = Elasticsearch("{}:{}".format(ES["host"], ES["port"]))
29+
30+
@staticmethod
31+
def remove_keyword(query):
32+
"""
33+
Remove keyword from search term
34+
"""
35+
query_formatted = query.strip().lower()
36+
keywords = ['dr', 'dr.', 'doctor', 'nurse', 'co', 'c.o.', 'c.o', 'clinical officer']
37+
for word in keywords:
38+
regex = r'(?<![\w\d]){0}(?![\w\d])'.format(word)
39+
query_formatted = re.sub(regex, "", query_formatted)
40+
return query_formatted.strip()
41+
42+
def get_from_elasticsearch(self, doc_type, query):
43+
"""
44+
get data from elasticsearch
45+
:return: Query results from elasticsearch
46+
"""
47+
search_term = self.remove_keyword(query)
48+
results = self.es_client.search(
49+
index=ES['index'],
50+
doc_type=doc_type,
51+
body={
52+
"query": {
53+
"match": {
54+
"name": {
55+
"query": search_term,
56+
"fuzziness": "auto",
57+
"prefix_length": 1
58+
}
59+
}
60+
}
61+
}
62+
)
63+
return results["hits"]["hits"]

healthtools_ke_api/views/nurses.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
import requests
88
import memcache
99

10-
1110
nurses_api = Blueprint('nurses_api', __name__)
12-
cache = memcache.Client([(MEMCACHED_URL)], debug=True) # cache server
11+
cache = memcache.Client([MEMCACHED_URL], debug=True) # cache server
1312

1413
nurse_fields = ["name", "licence_no", "valid_till"]
1514
NURSING_COUNCIL_URL = "http://nckenya.com/services/search.php?p=1&s={}"
@@ -45,7 +44,7 @@ def search():
4544
"error": "A query is required.",
4645
"results": "",
4746
"data": {"nurses": []}
48-
})
47+
})
4948

5049
# try to get queried result first
5150
cached_result = cache.get(query.replace(" ", ""))
@@ -98,7 +97,7 @@ def get_nurses_from_nc_registry(query):
9897

9998
# make soup for parsing out of response and get the table
10099
soup = BeautifulSoup(response.content, "html.parser")
101-
table = soup.find('table', {"class": "zebra"}).find("tbody")
100+
table = soup.find("table", {"class": "zebra"}).find("tbody")
102101
rows = table.find_all("tr")
103102

104103
# parse table for the nurses data
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import json
2+
from elasticsearch import serializer, compat, exceptions
3+
4+
5+
class JSONSerializerPython2(serializer.JSONSerializer):
6+
"""Override elasticsearch library serializer to ensure it encodes utf characters during json dump.
7+
See original at: https://github.com/elastic/elasticsearch-py/blob/master/elasticsearch/serializer.py#L42
8+
A description of how ensure_ascii encodes unicode characters to ensure they can be sent across the wire
9+
as ascii can be found here: https://docs.python.org/2/library/json.html#basic-usage
10+
"""
11+
def dumps(self, data):
12+
# don't serialize strings
13+
if isinstance(data, compat.string_types):
14+
return data
15+
try:
16+
return json.dumps(data, default=self.default, ensure_ascii=True)
17+
except (ValueError, TypeError) as e:
18+
raise exceptions.SerializationError(data, e)

0 commit comments

Comments
 (0)