How-to Guide¶
For the full JSON-RPC reference, see the RemoteControl 2 API docs.
Automatically close the session with a context manager¶
from citric import Client
LS_URL = "http://localhost:8001/index.php/admin/remotecontrol"
with Client(LS_URL, "iamadmin", "secret") as client:
# Do stuff with the client
...
Otherwise, you can manually close the session with client.close().
Get surveys and questions¶
from citric import Client
LS_URL = "http://localhost:8001/index.php/admin/remotecontrol"
client = Client(LS_URL, "iamadmin", "secret")
# Get all surveys from user "iamadmin"
surveys = client.list_surveys("iamadmin")
for s in surveys:
print(s["surveyls_title"])
# Get all questions, regardless of group
questions = client.list_questions(s["sid"])
for q in questions:
print(q["title"], q["question"])
Update question answers using the REST API¶
from citric.rest import RESTClient
client = RESTClient(
"https://example.com/rest/v1",
"your_username",
"your_password",
)
survey_id = 142423
# Get survey details
survey = client.get_survey(survey_id)
print(survey)
# Inspect question 247 to find answer IDs
for question in survey["questionGroups"][0]["questions"]:
if answers := question.get("answers"):
print("Answers for question", question["qid"])
for answer in answers:
print(f" Answer ID: {answer['aid']}, Code: {answer['code']}")
# Define patch operations
operations = [
{
"entity": "answer",
"op": "update",
# Question ID
"id": 247,
"props": [
{
# Answer ID
"aid": 31,
"code": "AO01",
"sortOrder": 1,
"assessmentValue": 0,
"scaleId": 1,
"l10ns": {
"de": {"answer": "ANTW1 scale 1", "language": "de"},
"en": {"answer": "ANS1 scale 1", "language": "en"},
},
},
{
# Answer ID
"aid": 32,
"code": "AO02",
"sortOrder": 1,
"assessmentValue": 0,
"scaleId": 1,
"l10ns": {
"de": {"answer": "ANTW2 scale 1", "language": "de"},
"en": {"answer": "ANS2 scale 1", "language": "en"},
},
},
{
# Answer ID
"aid": 33,
"code": "AO03",
"sortOrder": 1,
"assessmentValue": 0,
"scaleId": 1,
"l10ns": {
"de": {"answer": "ANTW3 scale 1", "language": "de"},
"en": {"answer": "ANS3 scale 1", "language": "en"},
},
},
],
}
]
result = client.patch_survey(survey_id, operations)
print(f"Operations applied: {result['operationsApplied']}") # Operations applied: 1
Export responses to a pandas dataframe¶
import io
import pandas as pd
from citric import Client
survey_id = 123456
client = Client(
"https://mylimeserver.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
)
# Export responses to CSV and read into a Pandas DataFrame
df = pd.read_csv(
io.BytesIO(client.export_responses(survey_id, file_format="csv")),
delimiter=";",
parse_dates=["datestamp", "startdate", "submitdate"],
index_col="id",
)
Export responses to a DuckDB database and analyze with SQL¶
from pathlib import Path
import citric
import duckdb
client = citric.Client(
"https://mylimeserver.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
)
Path("responses.csv").write_bytes(client.export_responses(12345, file_format="csv"))
duckdb.execute("CREATE TABLE responses AS SELECT * FROM 'responses.csv'")
duckdb.sql("""
SELECT
token,
submitdate - startdate AS duration
FROM responses
ORDER BY 2 DESC
LIMIT 10
""").show()
Change the default HTTP session attributes¶
import requests
from citric import Client
session = requests.Session()
# Set custom headers to be sent on each request
# https://requests.readthedocs.io/en/latest/api/#requests.Session.headers
session.headers["My-Custom-Header"] = "My-Custom-Value"
client = Client(
"https://mylimeserver.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
requests_session=session,
)
Use a custom requests session¶
It’s possible to use a custom session object to make requests. For example, to cache the requests
and reduce the load on your server in read-intensive applications, you can use
requests-cache:
import requests_cache
from citric import Client
cached_session = requests_cache.CachedSession(
expire_after=60,
allowable_methods=["POST"],
)
client = Client(
"https://example.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
requests_session=cached_session,
)
# Get all surveys from user "iamadmin".
# All responses will be cached for 1 minute.
surveys = client.list_surveys("iamadmin")
Use a different authentication plugin¶
By default, this client uses the internal database for authentication but
different plugins are supported using the
auth_plugin argument.
from citric import Client
client = Client(
"https://example.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
auth_plugin="AuthLDAP",
)
Common plugins are Authdb (default), AuthLDAP and Authwebserver.
Upload files and use them to update a response¶
import json
from citric import Client
PARTICIPANT_TOKEN = "T00000"
survey_id = 12
group_id = 34
question_id = 56
# You can also find the field name by using client.get_fieldmap(survey_id)
field_name = f"{survey_id}X{group_id}X{question_id}"
# Connection parameters
URL = "http://localhost:8001/index.php/admin/remotecontrol"
USERNAME = "iamadmin"
PASSWORD = "secret"
with Client(URL, USERNAME, PASSWORD) as client:
# Upload files to the question using the field name
with open("image_1.png", "rb") as file:
content1 = file.read()
result1 = client.upload_file_object(survey_id, field_name, "image_1.png", file)
with open("image_2.png", "rb") as file:
content2 = file.read()
result2 = client.upload_file_object(survey_id, field_name, "image_2.png", file)
# Add a response
response_files = [result1, result2]
responses = [
{
"token": PARTICIPANT_TOKEN,
field_name: json.dumps(response_files),
f"{field_name}_filecount": len(response_files),
},
]
# Download files
paths = client.download_files("./downloads", survey_id, PARTICIPANT_TOKEN)
Get files uploaded to a survey and move them to S3¶
import boto3
from citric import Client
s3 = boto3.client("s3")
client = Client(
"https://mylimeserver.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
)
survey_id = 12345
# Get all uploaded files and upload them to S3
for file in client.get_uploaded_file_objects(survey_id):
s3.upload_fileobj(
file["content"],
"my-s3-bucket",
f"uploads/sid={survey_id}/qid={file['meta']['question']['qid']}/{file['meta']['filename']}",
)
Use the session attribute for low-level interaction¶
This library doesn’t implement all RPC methods, so if you’re in dire need of using a method not currently supported, you can use the session attribute to invoke the underlying RPC interface without having to pass a session key explicitly:
client = Client(
"https://mylimeserver.com/index.php/admin/remotecontrol",
"iamadmin",
"secret",
)
# Get the raw response from mail_registered_participants
result = client.session.call("mail_registered_participants", 35239)
# Get the raw response from remind_participants
result = client.session.call("remind_participants", 35239)
Use with R via reticulate¶
If you prefer using R instead of Python, you can use the reticulate package to get survey data downloaded with citric.
Write a Python script to get the survey data, making sure to assign a dataframe to a variable (e.g., survey_data):
# export_ls_responses.py
import io
import citric
import pandas as pd
client = citric.Client(
"http://localhost:8001/index.php/admin/remotecontrol",
"iamadmin",
"secret",
)
data = client.export_responses(123456, file_format="csv")
survey_data = pd.read_csv(
io.BytesIO(data),
delimiter=";",
parse_dates=["datestamp", "startdate", "submitdate"],
index_col="id",
)
Then, in R, use the reticulate package to call the Python script and use the df variable:
> library(reticulate)
> reticulate::use_virtualenv("venv")
> reticulate::source_python("export_ls_responses.py")
> py$survey_data
submitdate lastpage startlanguage seed G01Q01 G01Q02 G02Q03 G02Q03[filecount]
1 [nan] [1] ['en'] [245240561] ['lalala'] [5] [nan] [nan]