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"])

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]

Notebook samples