2. DSLite: Does my package work?

After completing the first exercise, we have a brand new DataSHIELD package ready. However, how do we know it works?

To assess that, we have in our hands a great tool named DSLite. This tool will create a virtual environment that simulates a server on our own computer (!). This will allow us to run our DataSHILED functions, and moreoever it will bring us easy access to the values on the server R session, so we will be able to check if our assign function has worked, it if has assigned what we expected, etc.

We will now setup a script to setup DSLite with our new packages and test that they work as we expect.

library(dsBase)
library(dsBaseClient)
library(dsClient)
library(dsServer)
builder <- DSI::newDSLoginBuilder()
dslite.server <- DSLite::newDSLiteServer(
  config = DSLite::defaultDSConfiguration(
    include=c("dsBase", "dsServer")
  )
)
builder$append(
  server = "server1",
  url = "dslite.server",
  driver = "DSLiteDriver"
)
logindata.dslite <- builder$build()
conns <- DSI::datashield.login(logindata.dslite)

Now we have created a DSLite virtual server and we have the connection object to talk to it. Note that it has the dsServer and dsBase packages inside of it, so we can use functions from those two packages.

Let’s now run our client function

dsClient::ds.assign.string(
  object = "myObject",
  string = "myString"
)

If our packages are fine, we have now created a variable named myObject that contains the string "myString". How can we check this? Let’s take a look

DSLite::getDSLiteData(
  conns = conns,
  symbol = "myObject"
)

Sending data to my virtual server

This simple example we have just seen is pretty nice. However, one might be thinking hey typically I want my server functions to analyze tabular data, which is loaded by the user on the server session and will be used by my functions. We can also simulate this on DSLite to test our packages. Let’s see an example where we will load iris data on the virtual server.

library(dsBase)
library(dsBaseClient)
data("iris")
myTable <- iris
builder <- DSI::newDSLoginBuilder()
dslite.server <- DSLite::newDSLiteServer(
  tables=list(myTable = myTable),
  config = DSLite::defaultDSConfiguration(
    include=c("dsBase")
  )
)
builder$append(
  server = "server1",
  url = "dslite.server",
  table = "myTable",
  driver = "DSLiteDriver"
)
logindata.dslite <- builder$build()
conns <- DSI::datashield.login(
  logins = logindata.dslite,
  assign=T,
  symbol = "myTable_table"
)

This will assign the iris dataset to a variable on the server named "myTable_table". As before, we can check it

DSLite::getDSLiteData(
  conns = conns,
  symbol = "myTable_table"
)

When developing code, it is useful to be able to place debug points. This means being able to stop the execution on a certain point and look the environment, run code at that stop point etc. In R, a way of doing that is including browser() statements on the code (more on this here).

DSLite offers us the possibility of adding them on the server functions, and when we run them the execution will be stopped so we can take debug server functions.

Exercise 2

As out second exercise of the workshop, we will work on top of Exercise 1 developments.

  1. Create a new aggregate function (named getColnamesDS) on the dsServer package. This function will take as input argument a data table (that will be loaded on the server). It will return to the user the column names on the table.

  2. Create the correspondent function on dsClient (named ds.getColnames) that calls the server function. This function will take as input argument the name of the server table that we want to get the number of columns.

  3. Test the new function using DSLite.