Skip to content

Commit fbe0202

Browse files
author
“Carmel
committed
Set up environment variables for api clients
1 parent ad28ec4 commit fbe0202

9 files changed

Lines changed: 99 additions & 82 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
venv/
2+
.venv/
3+
24
new_ldot_workflows/ldot_config.json
35
new_ldot_workflows/qualtrics_config.json
6+
new_ldot_workflows/__pycache__/
7+
.env
48

59
scratch.py
610
logs/api_calls.log

app.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
from dataclasses import dataclass
33
from typing import Optional
44

5-
from flask import Flask, json, render_template, request, jsonify
5+
from dotenv import load_dotenv
6+
from flask import Flask, render_template, request, jsonify
67
import requests
78
import yaml
8-
import json
9+
import os
910

1011
from new_ldot_workflows.ldot_client import LdotClient
12+
from new_ldot_workflows.qualtrics_client import QualtricsClient
1113
from new_ldot_workflows.b_1_get_new_subjects import get_new_subjects
1214
from new_ldot_workflows.b_2_get_qualtrics_links import add_individuals_to_survey
1315
from new_ldot_workflows.b_2_send_links_to_ldot import send_links_to_ldot
@@ -22,6 +24,28 @@
2224
CONFIG_PATH = Path(__file__).resolve().with_name("study_configs.yaml")
2325
STUDIES = yaml.safe_load(CONFIG_PATH.read_text(encoding="utf-8"))
2426

27+
load_dotenv()
28+
29+
LDOT_TOKEN_URL = os.environ["LDOT_TOKEN_URL"]
30+
LDOT_API_URL = os.environ["LDOT_API_URL"]
31+
LDOT_client_id = os.environ["LDOT_client_id"]
32+
LDOT_client_secret = os.environ["LDOT_client_secret"]
33+
34+
QUALTRICS_BASE_URL = os.environ["QUALTRICS_BASE_URL"]
35+
QUALTRICS_API_TOKEN = os.environ["QUALTRICS_API_TOKEN"]
36+
37+
38+
ldot_client = LdotClient(
39+
token_url=LDOT_TOKEN_URL,
40+
api_url=LDOT_API_URL,
41+
client_id=LDOT_client_id,
42+
client_secret=LDOT_client_secret
43+
)
44+
45+
qualtrics_client = QualtricsClient(
46+
api_url=QUALTRICS_BASE_URL,
47+
token=QUALTRICS_API_TOKEN
48+
)
2549

2650
@dataclass
2751
class StudySettings:
@@ -67,20 +91,6 @@ def get_study_settings(study_key: str):
6791
)
6892

6993

70-
with open("new_ldot_workflows/ldot_config.json") as f:
71-
config = json.load(f)
72-
LDOT_TOKEN_URL = config["LDOT_TOKEN_URL"]
73-
LDOT_API_URL = config["LDOT_API_URL"]
74-
CLIENT_ID = config["client_id"]
75-
CLIENT_SECRET = config["client_secret"]
76-
77-
ldot_client = LdotClient(
78-
token_url=LDOT_TOKEN_URL,
79-
api_url=LDOT_API_URL,
80-
client_id=CLIENT_ID,
81-
client_secret=CLIENT_SECRET
82-
)
83-
8494
@app.route("/")
8595
def index():
8696
return render_template("index.html", studies=STUDIES)
@@ -156,6 +166,7 @@ def button2():
156166
try:
157167
subject_id_to_link_dict = add_individuals_to_survey(
158168
ldot_client,
169+
qualtrics_client,
159170
new_subject_ids,
160171
study_variables.ldot_study_id,
161172
study_variables.id_deelnemer_entity,
@@ -230,6 +241,7 @@ def button4():
230241
try:
231242
participant_to_progress_dict = get_individual_progress(
232243
ldot_client,
244+
qualtrics_client,
233245
study_variables.ldot_study_id,
234246
study_variables.id_deelnemer_entity,
235247
study_variables.id_location,
Binary file not shown.
Binary file not shown.

new_ldot_workflows/b_2_get_qualtrics_links.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import requests
2-
import json
3-
42
from new_ldot_workflows.logging_utils import logged_request, QualtricsAPIError
53

6-
with open("new_ldot_workflows/qualtrics_config.json") as f:
7-
config = json.load(f)
8-
QUALTRICS_BASE_URL = config["QUALTRICS_BASE_URL"]
9-
HEADERS = config["HEADERS"]
10-
11-
def check_contact_in_mailing_list(study_identifier: str, mailing_list_id: str, directory_id: str) -> bool:
4+
def check_contact_in_mailing_list(qualtrics_client, study_identifier: str, mailing_list_id: str, directory_id: str) -> bool:
125
"""Find who is already in that mailing list, check if the participant's ID is already there."""
136

147
print("Checking for contact in mailing list... ")
158
try:
169
response = logged_request(
1710
"GET",
18-
f"{QUALTRICS_BASE_URL}/directories/{directory_id}/mailinglists/{mailing_list_id}/contacts",
11+
f"{qualtrics_client.api_url}/directories/{directory_id}/mailinglists/{mailing_list_id}/contacts",
1912
function_name="check_contact_in_mailing_list",
2013
service="Qualtrics",
21-
headers=HEADERS,
14+
headers=qualtrics_client.headers,
2215
raise_for_status=True,
2316
)
2417
contacts = response.json()["result"]["elements"]
@@ -37,7 +30,7 @@ def check_contact_in_mailing_list(study_identifier: str, mailing_list_id: str, d
3730
return False
3831
return True
3932

40-
def add_contact_to_mailing_list(study_identifier: str, embedded_data_field: str, mailing_list_id: str, directory_id: str) -> None:
33+
def add_contact_to_mailing_list(qualtrics_client, study_identifier: str, embedded_data_field: str, mailing_list_id: str, directory_id: str) -> None:
4134
"""Add contact to a mailing list"""
4235

4336
contact_information_payload ={
@@ -49,10 +42,10 @@ def add_contact_to_mailing_list(study_identifier: str, embedded_data_field: str,
4942
try:
5043
response = logged_request(
5144
"POST",
52-
f"{QUALTRICS_BASE_URL}/directories/{directory_id}/mailinglists/{mailing_list_id}/contacts",
45+
f"{qualtrics_client.api_url}/directories/{directory_id}/mailinglists/{mailing_list_id}/contacts",
5346
function_name="add_contact_to_mailing_list",
5447
service="Qualtrics",
55-
headers=HEADERS,
48+
headers=qualtrics_client.get_headers(),
5649
json=contact_information_payload,
5750
raise_for_status=True,
5851
)
@@ -66,7 +59,7 @@ def add_contact_to_mailing_list(study_identifier: str, embedded_data_field: str,
6659
f"Unexpected response format when adding contact to mailing list."
6760
) from exc
6861

69-
def get_personal_link(qualtrics_survey_id: str, distribution_id: str, study_identifier: str) -> str:
62+
def get_personal_link(qualtrics_client, qualtrics_survey_id: str, distribution_id: str, study_identifier: str) -> str:
7063
"""Get the person link of an individual for a specified survey. Returns personal link for the participant, looks like
7164
https://survey.uu.nl/jfe/form/SV_efCMOg6wHU0T8ii?Q_CHLqe2Pdrma&_g_=g
7265
"""
@@ -77,10 +70,10 @@ def get_personal_link(qualtrics_survey_id: str, distribution_id: str, study_iden
7770
try:
7871
response = logged_request(
7972
"GET",
80-
f"{QUALTRICS_BASE_URL}/distributions/{distribution_id}/links",
73+
f"{qualtrics_client.api_url}/distributions/{distribution_id}/links",
8174
function_name="get_personal_link",
8275
service="Qualtrics",
83-
headers=HEADERS,
76+
headers=qualtrics_client.headers,
8477
params=parameters,
8578
raise_for_status=True,
8679
)
@@ -97,7 +90,7 @@ def get_personal_link(qualtrics_survey_id: str, distribution_id: str, study_iden
9790
f"Unexpected response format when fetching personal link of individual {study_identifier}."
9891
) from exc
9992

100-
def add_individuals_to_survey(ldot_client, new_subject_ids: list, ldot_study_id: str, id_deelnemer_entity: str, embedded_data_field: str, distribution_id: str, qualtrics_survey_id: str, mailing_list_id: str, directory_id: str):
93+
def add_individuals_to_survey(ldot_client, qualtrics_client, new_subject_ids: list, ldot_study_id: str, id_deelnemer_entity: str, embedded_data_field: str, distribution_id: str, qualtrics_survey_id: str, mailing_list_id: str, directory_id: str):
10194
def convert_api_subject_id_to_study_identifier(api_subject_id):
10295
"""Convert subject ID used by the Ldot API to study identifier that can be used in Qualtrics surveys"""
10396

@@ -122,15 +115,15 @@ def convert_api_subject_id_to_study_identifier(api_subject_id):
122115
study_identifier = convert_api_subject_id_to_study_identifier(subject_id)
123116
print(f"Processing subject ID: {subject_id} (Study Identifier: {study_identifier})")
124117

125-
contact_exists = check_contact_in_mailing_list(study_identifier, mailing_list_id, directory_id)
118+
contact_exists = check_contact_in_mailing_list(qualtrics_client, study_identifier, mailing_list_id, directory_id)
126119

127120
if not contact_exists:
128121
print(f"Adding participant study ID {study_identifier} to mailing list...")
129-
add_contact_to_mailing_list(study_identifier, embedded_data_field, mailing_list_id, directory_id)
122+
add_contact_to_mailing_list(qualtrics_client, study_identifier, embedded_data_field, mailing_list_id, directory_id)
130123
else:
131124
print(f"Participant study ID {study_identifier} already exists in this mailing list")
132125

133-
personal_link = get_personal_link(qualtrics_survey_id, distribution_id, study_identifier)
126+
personal_link = get_personal_link(qualtrics_client, qualtrics_survey_id, distribution_id, study_identifier)
134127

135128
participant_to_link_dict[subject_id] = personal_link
136129

@@ -147,9 +140,9 @@ def convert_api_subject_id_to_study_identifier(api_subject_id):
147140
distribution_id="EMD_7AEa416lRwFhrkF"
148141
directory_id="POOL_10pyxk9leSUisrT"
149142

150-
link = add_individuals_to_survey(participant_study_id, ldot_study_id, id_deelnemer_entity, embedded_data_field, distribution_id, qualtrics_survey_id, mailing_list_id, directory_id)
143+
# link = add_individuals_to_survey(ldot_client, qualtrics_client, participant_study_id, ldot_study_id, id_deelnemer_entity, embedded_data_field, distribution_id, qualtrics_survey_id, mailing_list_id, directory_id)
151144

152-
print(link)
145+
# print(link)
153146

154147

155148
# print(get_mailing_list_id_of_distribution("SV_efCMOg6wHU0T8ii", "EMD_SZFeoK7LAJBHU4d"))

new_ldot_workflows/b_4_get_individual_progress.py

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,8 @@
66
import json
77

88
from new_ldot_workflows.logging_utils import QualtricsAPIError, logged_request
9-
# from ldot_client import LdotClient
109

11-
with open("new_ldot_workflows/qualtrics_config.json") as f:
12-
config = json.load(f)
13-
QUALTRICS_BASE_URL = config["QUALTRICS_BASE_URL"]
14-
HEADERS = config["HEADERS"]
15-
16-
def create_data_export(survey_id: str, incomplete_bool: bool = True) -> str:
10+
def create_data_export(qualtrics_client, survey_id: str, incomplete_bool: bool = True) -> str:
1711
"""Create a data export request for a given survey.
1812
1913
Args:
@@ -32,10 +26,10 @@ def create_data_export(survey_id: str, incomplete_bool: bool = True) -> str:
3226
try:
3327
response = logged_request(
3428
"POST",
35-
f"{QUALTRICS_BASE_URL}/surveys/{survey_id}/export-responses",
29+
f"{qualtrics_client.api_url}/surveys/{survey_id}/export-responses",
3630
function_name="create_data_export",
3731
service="Qualtrics",
38-
headers=HEADERS,
32+
headers=qualtrics_client.headers,
3933
json=payload,
4034
raise_for_status=True,
4135
)
@@ -51,10 +45,11 @@ def create_data_export(survey_id: str, incomplete_bool: bool = True) -> str:
5145
) from exc
5246

5347

54-
def check_export_progress(request_id: str, survey_id: str, returns: str) -> str|float:
48+
def check_export_progress(qualtrics_client, request_id: str, survey_id: str, returns: str) -> str|float:
5549
"""Check if a an export request is complete
5650
5751
Args:
52+
qualtrics_client: The Qualtrics API client.
5853
request_id (str): The ID of the export request.
5954
survey_id (str): The ID of the survey that was exported.
6055
returns (str): The type of information to return ("percent_complete" or "fileID").
@@ -68,10 +63,10 @@ def check_export_progress(request_id: str, survey_id: str, returns: str) -> str|
6863
try:
6964
result = logged_request(
7065
"GET",
71-
f"{QUALTRICS_BASE_URL}/surveys/{survey_id}/export-responses/{request_id}",
66+
f"{qualtrics_client.api_url}/surveys/{survey_id}/export-responses/{request_id}",
7267
function_name="check_export_progress",
7368
service="Qualtrics",
74-
headers=HEADERS,
69+
headers=qualtrics_client.headers,
7570
raise_for_status=True,
7671
)
7772

@@ -91,10 +86,11 @@ def check_export_progress(request_id: str, survey_id: str, returns: str) -> str|
9186
) from exc
9287

9388

94-
def download_export(file_id: str, survey_id: str) -> pd.DataFrame:
89+
def download_export(qualtrics_client, file_id: str, survey_id: str) -> pd.DataFrame:
9590
"""Download the exported survey data and convert to DataFrame.
9691
9792
Args:
93+
qualtrics_client: The Qualtrics API client.
9894
file_id (str): The ID of the file to download.
9995
survey_id (str): The ID of the survey associated with the file.
10096
Raises:
@@ -106,10 +102,10 @@ def download_export(file_id: str, survey_id: str) -> pd.DataFrame:
106102
try:
107103
download = logged_request(
108104
"GET",
109-
f"{QUALTRICS_BASE_URL}/surveys/{survey_id}/export-responses/{file_id}/file",
105+
f"{qualtrics_client.api_url}/surveys/{survey_id}/export-responses/{file_id}/file",
110106
function_name="download_export",
111107
service="Qualtrics",
112-
headers=HEADERS,
108+
headers=qualtrics_client.headers,
113109
stream=True,
114110
raise_for_status=True,
115111
)
@@ -155,28 +151,29 @@ def check_inputs_validity(participant_study_id: str, embedded_data_field: str, s
155151
return True
156152

157153

158-
def get_responses_as_df(survey_id: str, incomplete_bool: bool) -> pd.DataFrame:
154+
def get_responses_as_df(qualtrics_client, survey_id: str, incomplete_bool: bool) -> pd.DataFrame:
159155
"""Wrapper function to request export, wait for it, download it, and convert to DataFrame.
160156
161157
Args:
158+
qualtrics_client: The Qualtrics API client.
162159
survey_id (str): The ID of the survey to get responses from.
163160
incomplete_bool (bool): Whether to get responses that are in progress or completed.
164161
165162
Returns:
166163
pd.DataFrame: The survey response data as a DataFrame.
167164
"""
168165
# Request a data export
169-
request_id = create_data_export(survey_id, incomplete_bool=incomplete_bool)
166+
request_id = create_data_export(qualtrics_client, survey_id, incomplete_bool=incomplete_bool)
170167

171168
# Ping the export report repeatedly until it's ready
172169
export_progress = 0.0
173170
while export_progress < 100:
174-
export_progress = check_export_progress(request_id, survey_id, returns="percent_complete")
171+
export_progress = check_export_progress(qualtrics_client, request_id, survey_id, returns="percent_complete")
175172
time.sleep(2)
176173

177174
# When export is complete, get the file ID
178-
file_id = check_export_progress(request_id, survey_id, returns="fileID")
179-
df = download_export(file_id, survey_id)
175+
file_id = check_export_progress(qualtrics_client, request_id, survey_id, returns="fileID")
176+
df = download_export(qualtrics_client, file_id, survey_id)
180177
return df
181178

182179

@@ -206,16 +203,16 @@ def subject_id_to_study_identifier(ldot_client, ldot_study_id: str, id_deelnemer
206203
return subject_id_to_study_identifier_dict
207204

208205

209-
def get_individual_progress(ldot_client, ldot_study_id: str, id_deelnemer_entity: str, id_location: str, subject_ids: list, embedded_data_field: str, survey_id: str) -> float:
206+
def get_individual_progress(ldot_client, qualtrics_client,ldot_study_id: str, id_deelnemer_entity: str, id_location: str, subject_ids: list, embedded_data_field: str, survey_id: str) -> float:
210207
"""Wrapper function that gets the individual progress of a participant in a survey."""
211208
# Check for invalid inputs
212209
check_inputs_validity(subject_ids[0], embedded_data_field, survey_id) # TODO: Check this
213210

214211
participant_to_progress_dict = {}
215212
subject_id_to_study_identifier_dict = subject_id_to_study_identifier(ldot_client, ldot_study_id, id_deelnemer_entity, id_location, subject_ids)
216213

217-
completed_responses_df = get_responses_as_df(survey_id, incomplete_bool=False)
218-
incompleted_responses_df = get_responses_as_df(survey_id, incomplete_bool=True)
214+
completed_responses_df = get_responses_as_df(qualtrics_client, survey_id, incomplete_bool=False)
215+
incompleted_responses_df = get_responses_as_df(qualtrics_client, survey_id, incomplete_bool=True)
219216
df = pd.concat([completed_responses_df, incompleted_responses_df], ignore_index=True)
220217

221218
if not embedded_data_field in df.columns:
@@ -233,20 +230,21 @@ def get_individual_progress(ldot_client, ldot_study_id: str, id_deelnemer_entity
233230
return participant_to_progress_dict
234231

235232
if __name__ == "__main__":
236-
with open("ldot_config.json") as f:
237-
config = json.load(f)
238-
LDOT_TOKEN_URL = config["LDOT_TOKEN_URL"]
239-
LDOT_API_URL = config["LDOT_API_URL"]
240-
CLIENT_ID = config["client_id"]
241-
CLIENT_SECRET = config["client_secret"]
242-
243-
ldot_client = LdotClient(
244-
token_url=LDOT_TOKEN_URL,
245-
api_url=LDOT_API_URL,
246-
client_id=CLIENT_ID,
247-
client_secret=CLIENT_SECRET
248-
)
249-
print(ldot_client.headers)
233+
pass
234+
# with open("ldot_config.json") as f:
235+
# config = json.load(f)
236+
# LDOT_TOKEN_URL = config["LDOT_TOKEN_URL"]
237+
# LDOT_API_URL = config["LDOT_API_URL"]
238+
# CLIENT_ID = config["client_id"]
239+
# CLIENT_SECRET = config["client_secret"]
240+
241+
# ldot_client = LdotClient(
242+
# token_url=LDOT_TOKEN_URL,
243+
# api_url=LDOT_API_URL,
244+
# client_id=CLIENT_ID,
245+
# client_secret=CLIENT_SECRET
246+
# )
247+
# print(ldot_client.headers)
250248

251249
# # # Example usage
252250
# survey_id="SV_efCMOg6wHU0T8ii"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import requests
2+
from datetime import datetime, timedelta
3+
4+
class QualtricsClient:
5+
def __init__(self, api_url, token):
6+
self.api_url = api_url
7+
self.api_token = token
8+
9+
# Initialize headers with the current token (if any)
10+
self.headers = self.get_headers()
11+
12+
def get_headers(self):
13+
return {
14+
"Content-Type": "application/json",
15+
"X-API-TOKEN": self.api_token
16+
}
17+

0 commit comments

Comments
 (0)