Skip to content

Commit 2695d92

Browse files
author
cris_gk
committed
First pass at SL events pull
1 parent 4cca4b5 commit 2695d92

File tree

4 files changed

+254
-13
lines changed

4 files changed

+254
-13
lines changed

src/server/alembic/versions/9687db7928ee_shelterluv_animals.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""empty message
1+
"""Create SL_animals table
22
33
Revision ID: 9687db7928ee
44
Revises: a3ba63dee8f4
@@ -11,7 +11,7 @@
1111

1212
# revision identifiers, used by Alembic.
1313
revision = '9687db7928ee'
14-
down_revision = 'a3ba63dee8f4'
14+
down_revision = 'fc7325372396'
1515
branch_labels = None
1616
depends_on = None
1717

src/server/api/API_ingest/shelterluv_db.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,88 @@ def insert_animals(animal_list):
5151
def truncate_animals():
5252
"""Truncate the shelterluv_animals table"""
5353

54-
55-
Session = sessionmaker(engine)
56-
session = Session()
54+
Session = sessionmaker(engine)
55+
session = Session()
5756
metadata = MetaData()
5857
sla = Table("shelterluv_animals", metadata, autoload=True, autoload_with=engine)
5958

60-
6159
truncate = "TRUNCATE table shelterluv_animals;"
6260
result = session.execute(truncate)
6361

64-
session.commit() # Commit all inserted rows
62+
session.commit() # Commit all inserted rows
63+
session.close()
64+
65+
return 0
66+
67+
68+
def truncate_events():
69+
"""Truncate the shelterluv_events table"""
70+
71+
Session = sessionmaker(engine)
72+
session = Session()
73+
metadata = MetaData()
74+
sla = Table("sl_animal_events", metadata, autoload=True, autoload_with=engine)
75+
76+
truncate = "TRUNCATE table sl_animal_events;"
77+
result = session.execute(truncate)
78+
79+
session.commit() # Commit all inserted rows
6580
session.close()
6681

6782
return 0
83+
84+
85+
def insert_events(event_list):
86+
"""Insert event records into sl_animal_events table and return row count. """
87+
88+
# Always a clean insert
89+
truncate_events()
90+
91+
Session = sessionmaker(engine)
92+
session = Session()
93+
metadata = MetaData()
94+
sla = Table("sl_animal_events", metadata, autoload=True, autoload_with=engine)
95+
96+
# TODO: Pull from DB
97+
event_map = {
98+
"Outcome.Adoption": 1,
99+
"Outcome.Foster": 2,
100+
"Outcome.ReturnToOwner": 3,
101+
"Intake.AdoptionReturn": 4,
102+
}
103+
104+
# Event record: [ AssociatedRecords[Type = Person]["Id"]',
105+
# AssociatedRecords[Type = Animal]["Id"]',
106+
# "Type",
107+
# "Time"
108+
# ]
109+
#
110+
# In db: ['id',
111+
# 'person_id',
112+
# 'animal_id',
113+
# 'event_type',
114+
# 'time']
115+
116+
ins_list = [] # Create a list of per-row dicts
117+
for rec in event_list:
118+
ins_list.append(
119+
{
120+
"person_id": next(
121+
filter(lambda x: x["Type"] == "Person", rec["AssociatedRecords"])
122+
)["Id"],
123+
"animal_id": next(
124+
filter(lambda x: x["Type"] == "Animal", rec["AssociatedRecords"])
125+
)["Id"],
126+
"event_type": event_map[rec["Type"]],
127+
"time": rec["Time"],
128+
}
129+
)
130+
131+
# TODO: Wrap with try/catch
132+
ret = session.execute(sla.insert(ins_list))
133+
134+
session.commit() # Commit all inserted rows
135+
session.close()
136+
137+
return ret.rowcount
138+
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import os, time, json
2+
import posixpath as path
3+
4+
import requests
5+
6+
from api.API_ingest import shelterluv_db
7+
from server.api.API_ingest.shelterluv_db import insert_animals
8+
9+
# There are a number of different record types. These are the ones we care about.
10+
keep_record_types = [
11+
"Outcome.Adoption",
12+
"Outcome.Foster",
13+
"Outcome.ReturnToOwner",
14+
"Intake.AdoptionReturn",
15+
]
16+
17+
# from config import engine
18+
# from flask import current_app
19+
# from sqlalchemy.sql import text
20+
21+
BASE_URL = "http://shelterluv.com/api/"
22+
MAX_COUNT = 100 # Max records the API will return for one call
23+
24+
# Get the API key
25+
try:
26+
from secrets_dict import SHELTERLUV_SECRET_TOKEN
27+
except ImportError:
28+
# Not running locally
29+
from os import environ
30+
31+
try:
32+
SHELTERLUV_SECRET_TOKEN = environ["SHELTERLUV_SECRET_TOKEN"]
33+
except KeyError:
34+
# Not in environment
35+
# You're SOL for now
36+
print("Couldn't get SHELTERLUV_SECRET_TOKEN from file or environment")
37+
38+
39+
headers = {"Accept": "application/json", "X-API-Key": SHELTERLUV_SECRET_TOKEN}
40+
41+
logger = print # print to console for testing
42+
43+
44+
# Sample response from events request:
45+
46+
# {
47+
# "success": 1,
48+
# "events": [
49+
# {
50+
# "Type": "Outcome.Adoption",
51+
# "Subtype": "PAC",
52+
# "Time": "1656536900",
53+
# "User": "phlp_mxxxx",
54+
# "AssociatedRecords": [
55+
# {
56+
# "Type": "Animal",
57+
# "Id": "5276xxxx"
58+
# },
59+
# {
60+
# "Type": "Person",
61+
# "Id": "5633xxxx"
62+
# }
63+
# ]
64+
# },
65+
# {...}
66+
# ],
67+
# "has_more": true,
68+
# "total_count": 67467
69+
# }
70+
71+
72+
def get_event_count():
73+
"""Test that server is operational and get total event count."""
74+
events = "v1/events&offset=0&limit=1"
75+
URL = path.join(BASE_URL, events)
76+
77+
try:
78+
response = requests.request("GET", URL, headers=headers)
79+
except Exception as e:
80+
logger("get_event_count failed with ", e)
81+
return -2
82+
83+
if response.status_code != 200:
84+
logger("get_event_count ", response.status_code, "code")
85+
return -3
86+
87+
try:
88+
decoded = json.loads(response.text)
89+
except json.decoder.JSONDecodeError as e:
90+
logger("get_event_count JSON decode failed with", e)
91+
return -4
92+
93+
if decoded["success"]:
94+
return decoded["total_count"]
95+
else:
96+
return -5 # AFAICT, this means URL was bad
97+
98+
99+
def get_events_bulk():
100+
"""Pull all event records from SL """
101+
102+
# Interesting API design - event record 0 is the newest. But since we pull all records each time it doesn't
103+
# really matter which direction we go. Simplest to count up, and we can pull until 'has_more' goes false.
104+
# Good news, the API is robust and won't blow up if you request past the end.
105+
# At 100 per request, API returns about 5000 records/minute
106+
107+
event_records = []
108+
109+
raw_url = path.join(BASE_URL, "v1/events&offset={0}&limit={1}")
110+
offset = 0
111+
limit = MAX_COUNT
112+
more_records = True
113+
114+
while more_records:
115+
116+
url = raw_url.format(offset, limit)
117+
118+
try:
119+
response = requests.request("GET", url, headers=headers)
120+
except Exception as e:
121+
logger("get_events failed with ", e)
122+
return -2
123+
124+
if response.status_code != 200:
125+
logger("get_event_count ", response.status_code, "code")
126+
return -3
127+
128+
try:
129+
decoded = json.loads(response.text)
130+
except json.decoder.JSONDecodeError as e:
131+
logger("get_event_count JSON decode failed with", e)
132+
return -4
133+
134+
if decoded["success"]:
135+
for evrec in decoded["events"]:
136+
if evrec["Type"] in keep_record_types:
137+
event_records.append(evrec)
138+
139+
more_records = decoded["has_more"] # if so, we'll make another pass
140+
offset += limit
141+
if offset % 1000 == 0:
142+
print("Reading offset ", str(offset))
143+
144+
else:
145+
return -5 # AFAICT, this means URL was bad
146+
147+
return event_records
148+
149+
150+
def slae_test():
151+
total_count = get_event_count()
152+
print("Total events:", total_count)
153+
154+
b = get_events_bulk()
155+
print("Records:", len(b))
156+
157+
# f = filter_events(b)
158+
# print(f)
159+
160+
count = shelterluv_db.insert_events(b)
161+
return count

src/server/api/admin_api.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -388,18 +388,17 @@ def generate_dummy_rfm_scores():
388388
return count
389389

390390

391+
# ########### Test API endpoints
392+
# TODO: Remove for production
391393

392-
393-
# Use this as a way to trigger functions for testing
394-
# TODO: Remove when not needed
394+
# trigger rfm scoring process
395395
@admin_api.route("/api/admin/test_endpoint_gdrs", methods=["GET"])
396396
def hit_gdrs():
397397
num_scores = generate_dummy_rfm_scores()
398398
return jsonify({"scores added" : num_scores})
399399

400400

401-
402-
401+
# trigger pull of SL animals
403402
@admin_api.route("/api/admin/test_sla", methods=["GET"])
404403
def trigger_sla_pull():
405404

@@ -408,7 +407,7 @@ def trigger_sla_pull():
408407
num_rows = api.API_ingest.shelterluv_animals.sla_test()
409408
return jsonify({"rows added" : num_rows})
410409

411-
410+
# trigger pull of SL people
412411
@admin_api.route("/api/admin/test_slp", methods=["GET"])
413412
def trigger_slp_pull():
414413

@@ -417,6 +416,16 @@ def trigger_slp_pull():
417416
num_rows = api.API_ingest.shelterluv_api_handler.store_shelterluv_people_all()
418417
return jsonify({"rows added" : num_rows})
419418

419+
# trigger pull of SL animal events
420+
@admin_api.route("/api/admin/test_slae", methods=["GET"])
421+
def trigger_slae_pull():
422+
423+
import api.API_ingest.sl_animal_events
424+
425+
num_rows = api.API_ingest.sl_animal_events.slae_test()
426+
return jsonify({"rows added" : num_rows})
427+
428+
420429

421430

422431

0 commit comments

Comments
 (0)