Skip to content

Commit 736f89f

Browse files
committed
merge conflicts
2 parents 50fa752 + dd3242a commit 736f89f

13 files changed

+767
-688
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@ start with 'Added', 'Changed', 'Deprecated', 'Removed', 'Fix(ed)', or
1111
Unreleased
1212
==========
1313

14+
15+
16+
[0.3.2] - 2016-07-18
17+
====================
18+
1419
- Added this CHANGELOG.rst file
1520
- Added support for patient_id_list constraint in getHighdimData
1621
- Fix bugs in error handling, e.g. when no Content-Type is provided by the server
22+
- implement getPatientSet
23+
- use inTrialId as patient id: The former 'id' field of patients should not be used outside of Transmart, instead the inTrialId should be used. New versions of rest-api don't export the 'id' field anymore.
24+
- Change from `rCurl` to `httr` to fix authentication with new Transmart servers using an updated OAuth plugin
1725

1826
[0.3.1] - 2016-04-06
1927
====================

DESCRIPTION

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Package: transmartRClient
22
Type: Package
33
Title: R Client for accessing the tranSMART RESTful API
4-
Version: 0.3.1
5-
Date: 2016-04-06
6-
Depends: RCurl, rjson, plyr, RProtoBuf, hash, reshape, XML
4+
Version: 0.3.2
5+
Date: 2016-07-18
6+
Depends: httr, jsonlite, plyr, RProtoBuf, hash, reshape, XML
77
Author: Tim Dorscheidt, Jan Kanis, Rianne Jansen
88
Maintainer: <support@thehyve.nl>
99
Description: This package exposes tranSMART's RESTful API as a set of R functions. It uses tranSMART's OAuth authentication to access the data for which the user is authorized, and allows exploring and downloading the data.

LICENSE.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ apply:
2020
| Dependency | License | Reference
2121
| ---------- | -------- | -----------------------------------------------------------
2222
| Protobuf | BSD | <http://grails.org/License>
23-
| RCurl | BSD | <http://cran.r-project.org/web/packages/RCurl/index.html>
24-
| rjson | GPL2 | <http://cran.r-project.org/web/packages/rjson/index.html>
25-
| RProtoBuf | GPL2 | <http://cran.r-project.org/web/packages/RProtoBuf/index.html>
23+
| httr | MIT | <http://cran.r-project.org/web/packages/httr/index.html>
24+
| jsonlite | MIT | <http://cran.r-project.org/web/packages/jsonlite/index.html>
25+
| RProtoBuf | GPL2+ | <http://cran.r-project.org/web/packages/RProtoBuf/index.html>
2626
| plyr | MIT | <http://cran.r-project.org/web/packages/plyr/index.html>
27-
| hash | GPL3 | <http://cran.r-project.org/web/packages/hash/index.html>
28-
| bitopts | GPL3 | <http://cran.r-project.org/web/packages/bitops/index.html>
29-
| RCpp | GPL2 | <http://cran.r-project.org/web/packages/Rcpp/index.html>
27+
| hash | GPL2+ | <http://cran.r-project.org/web/packages/hash/index.html>
28+
| reshape | MIT | <http://cran.r-project.org/web/packages/reshape/>
29+
| bitopts | GPL2+ | <http://cran.r-project.org/web/packages/bitops/index.html>
30+
| RCpp | GPL2+ | <http://cran.r-project.org/web/packages/Rcpp/index.html>
3031
| R overall | Multiple | <http://www.r-project.org/Licenses/>
3132

3233
This program is free software: you can redistribute it and/or modify it under

R/RClientConnectionManager.R

Lines changed: 78 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,13 @@ function (oauthDomain = transmartClientEnv$transmartDomain, prefetched.request.t
9393
return(FALSE)
9494
}
9595

96-
oauth.exchange.token.path <- paste(sep = "",
97-
"/oauth/token?grant_type=authorization_code&client_id=",
98-
transmartClientEnv$client_id,
99-
"&client_secret=", transmartClientEnv$client_secret,
100-
"&code=", URLencode(request.token, TRUE),
101-
"&redirect_uri=", URLencode(transmartClientEnv$oauthDomain, TRUE),
102-
URLencode("/oauth/verify", TRUE))
96+
oauth.exchange.token.path <- "/oauth/token"
97+
post.body <- list(
98+
grant_type="authorization_code",
99+
code=request.token,
100+
redirect_uri=paste(transmartClientEnv$oauthDomain, "/oauth/verify", sep=""))
103101

104-
oauthResponse <- .transmartServerGetOauthRequest(oauth.exchange.token.path, "Authentication")
102+
oauthResponse <- .transmartServerPostOauthRequest(oauth.exchange.token.path, "Authentication", post.body)
105103
if (is.null(oauthResponse)) return(FALSE)
106104

107105
list2env(oauthResponse$content, envir = transmartClientEnv)
@@ -119,16 +117,12 @@ function (oauthDomain = transmartClientEnv$transmartDomain, prefetched.request.t
119117
transmartClientEnv$client_id <- "api-client"
120118
transmartClientEnv$client_secret <- "api-client"
121119
message("Trying to reauthenticate using the refresh token...")
122-
refreshPath <- paste(sep = "",
123-
"/oauth/token?grant_type=refresh_token",
124-
"&client_id=", transmartClientEnv$client_id,
125-
"&client_secret=", transmartClientEnv$client_secret,
126-
"&refresh_token=", URLencode(transmartClientEnv$refresh_token, TRUE),
127-
"&redirect_uri=", URLencode(transmartClientEnv$oauthDomain, TRUE),
128-
URLencode("/oauth/verify", TRUE),
129-
"")
120+
refreshPath <- "/oauth/token"
121+
post.body <- list(grant_type="refresh_token",
122+
refresh_token=transmartClientEnv$refresh_token,
123+
redirect_uri=paste(transmartClientEnv$oauthDomain, "/oauth/verify", sep=""))
130124

131-
oauthResponse <- .transmartServerGetOauthRequest(refreshPath, "Refreshing access")
125+
oauthResponse <- .transmartServerPostOauthRequest(refreshPath, "Refreshing access", post.body)
132126
if (is.null(oauthResponse)) return(FALSE)
133127
if (!'access_token' %in% names(oauthResponse$content)) {
134128
message("Refreshing access failed, server response did not contain access_token. HTTP", statusString)
@@ -139,8 +133,8 @@ function (oauthDomain = transmartClientEnv$transmartDomain, prefetched.request.t
139133
return(TRUE)
140134
}
141135

142-
.transmartServerGetOauthRequest <- function(path, action) {
143-
oauthResponse <- .transmartServerGetRequest(path, onlyContent=F)
136+
.transmartServerPostOauthRequest <- function(path, action, post.body) {
137+
oauthResponse <- .transmartServerGetRequest(path, onlyContent=F, post.body=post.body)
144138
statusString <- paste("status code ", oauthResponse$status, ": ", oauthResponse$headers[['statusMessage']], sep='')
145139
if (!oauthResponse$JSON) {
146140
cat(action, " failed, could not parse server response of type ", oauthResponse$headers[['Content-Type']], ". ", statusString, "\n", sep='')
@@ -209,7 +203,7 @@ function (oauthDomain = transmartClientEnv$transmartDomain, prefetched.request.t
209203
"You can help fix it by contacting us. Type ?transmartRClient for contact details.\n",
210204
"Optional: type options(verbose = TRUE) and replicate the bug to find out more details.")
211205
# If e is a condition adding the call. parameter triggers another warning
212-
if(inherits(args[[1L]], "condition")) {
206+
if(inherits(e, "condition")) {
213207
stop(e)
214208
} else {
215209
stop(e, call.=FALSE)
@@ -255,10 +249,10 @@ function (oauthDomain = transmartClientEnv$transmartDomain, prefetched.request.t
255249
}
256250

257251
.contentType <- function(headers) {
258-
if(! 'Content-Type' %in% names(headers)) {
259-
return('Content-Type header not found')
252+
if(! 'content-type' %in% names(headers)) {
253+
return('content-type header not found')
260254
}
261-
h <- headers[['Content-Type']]
255+
h <- headers[['content-type']]
262256
if(grepl("^application/json(;|\\W|$)", h)) {
263257
return('json')
264258
}
@@ -271,70 +265,80 @@ function (oauthDomain = transmartClientEnv$transmartDomain, prefetched.request.t
271265
return('unknown')
272266
}
273267

268+
# Wrap this in case we need to change json libraries again
269+
.fromJSON <- function(json) {
270+
fromJSON(json, simplifyDataFrame=F, simplifyMatrix=F)
271+
}
272+
274273
.serverMessageExchange <-
275-
function(apiCall, httpHeaderFields, accept.type = "default", progress = .make.progresscallback.download(),
276-
post.content.type = NULL, requestBody = NULL) {
274+
function(apiCall, httpHeaderFields, accept.type = "default", post.body = NULL, post.content.type = 'form',
275+
show.progress = (accept.type == 'binary') ) {
277276
if (any(accept.type == c("default", "hal"))) {
278-
if (accept.type == "hal") { httpHeaderFields <- c(httpHeaderFields, Accept = "application/hal+json;charset=UTF-8") }
279-
curlOptions <- list()
280-
if (!is.null(post.content.type)) {
281-
httpHeaderFields <- c(httpHeaderFields, 'content-type' = post.content.type)
282-
if(is.null(requestBody)) { stop("Missing body for POST request") }
283-
curlOptions[["postfields"]] <- requestBody
277+
if (accept.type == "hal") {
278+
httpHeaderFields <- c(httpHeaderFields, Accept = "application/hal+json;charset=UTF-8")
284279
}
285-
headers <- basicHeaderGatherer()
286280
result <- list(JSON = FALSE)
287-
curlOptions <- c(curlOptions, list(httpheader = httpHeaderFields, headerfunction = headers$update))
288-
result$content <- getURL(paste(sep="", transmartClientEnv$db_access_url, apiCall),
289-
verbose = getOption("verbose"), .opts = curlOptions)
281+
api.url <- paste0(transmartClientEnv$db_access_url, apiCall)
282+
if (is.null(post.body)) {
283+
req <- GET(api.url,
284+
add_headers(httpHeaderFields),
285+
authenticate(transmartClientEnv$client_id, transmartClientEnv$client_secret),
286+
config(verbose = getOption("verbose")))
287+
} else {
288+
req <- POST(api.url,
289+
body = post.body,
290+
add_headers(httpHeaderFields),
291+
authenticate(transmartClientEnv$client_id, transmartClientEnv$client_secret),
292+
encode = if(post.content.type == 'form') 'form' else 'raw',
293+
if(post.content.type != 'form') content_type(post.content.type),
294+
config(verbose = getOption("verbose")))
295+
if (getOption("verbose")) { message("POST body:\n", .list2string(post.body), "\n") }
296+
}
297+
result$content <- content(req, "text")
290298
if (getOption("verbose")) { message("Server response:\n", result$content, "\n") }
291-
if(is.null(result)) { return(NULL) }
292-
result$headers <- headers$value()
293-
result$status <- as.integer(result$headers[['status']])
294-
result$statusMessage <- result$headers[['statusMessage']]
295-
switch(.contentType(result$headers),
299+
result$headers <- headers(req)
300+
result$status <- req$status_code
301+
result$statusMessage <- http_status(req)$message
302+
switch(.contentType(result$headers),
296303
json = {
297-
result$content <- fromJSON(result$content)
304+
result$content <- .fromJSON(result$content)
298305
result$JSON <- TRUE
299306
},
300307
hal = {
301-
result$content <- .simplifyHalList(fromJSON(result$content))
308+
result$content <- .simplifyHalList(.fromJSON(result$content))
302309
result$JSON <- TRUE
303310
})
304311
return(result)
305312
} else if (accept.type == "binary") {
306-
progress$start(NA_integer_)
313+
if(show.progress) cat("Retrieving data...\n")
307314
result <- list(JSON = FALSE)
308-
headers <- basicHeaderGatherer()
309-
result$content <- getBinaryURL(paste(sep="", transmartClientEnv$db_access_url, apiCall),
310-
verbose = getOption("verbose"),
311-
headerfunction = headers$update,
312-
noprogress = FALSE,
313-
progressfunction = function(down, up) {up[which(up == 0)] <- NA; progress$update(down, up) },
314-
httpheader = httpHeaderFields)
315-
progress$end()
316-
result$headers <- headers$value()
317-
result$status <- as.integer(result$headers[['status']])
318-
result$statusMessage <- result$headers[['statusMessage']]
319-
if (getOption("verbose") && .contentType(result$headers) %in% c('json', 'hal', 'html')) {
320-
message("Server response:\n", result$content, "\n")
315+
api.url <- paste(sep="", transmartClientEnv$db_access_url, apiCall)
316+
if (is.null(post.body)) {
317+
req <- GET(api.url,
318+
add_headers(httpHeaderFields),
319+
authenticate(transmartClientEnv$client_id, transmartClientEnv$client_secret),
320+
if(show.progress) progress(),
321+
config(verbose = getOption("verbose")))
322+
} else {
323+
req <- POST(api.url,
324+
body = post.body,
325+
add_headers(httpHeaderFields),
326+
authenticate(transmartClientEnv$client_id, transmartClientEnv$client_secret),
327+
if(show.progress) progress(),
328+
encode = if(post.content.type == 'form') 'form' else 'raw',
329+
if(post.content.type != 'form') content_type(post.content.type),
330+
config(verbose = getOption("verbose")))
321331
}
332+
if(show.progress) cat("\nDownload complete.\n")
333+
result$content <- content(req, "raw")
334+
result$headers <- headers(req)
335+
result$status <- req$status_code
336+
result$statusMessage <- http_status(req)$message
322337
return(result)
323338
}
324339
return(NULL)
325340
}
326341

327-
.make.progresscallback.download <- function() {
328-
start <- function(.total) cat("Retrieving data: \n")
329-
update <- function(current, .total) {
330-
# This trick unfortunately doesn't work in RStudio if we write to stderr.
331-
cat(paste("\r", format(current / 1000000, digits=3, nsmall=3), "MB downloaded."))
332-
}
333-
end <- function() cat("\nDownload complete.\n")
334-
environment()
335-
}
336-
337-
338342
.listToDataFrame <- function(l) {
339343
# TODO: (timdo) dependency on 'plyr' package removed; figure out whether dependency is present elsewhere, or remove dependency
340344
# add each list-element as a new row to a matrix, in two passes
@@ -396,3 +400,10 @@ function(apiCall, httpHeaderFields, accept.type = "default", progress = .make.pr
396400
}
397401
return(halList)
398402
}
403+
404+
.list2string <- function(lst) {
405+
if(is.null(names(lst))) return(paste(lst, sep=", "))
406+
407+
final <- character(length(lst)*2)
408+
paste(mapply(function(name, val) {paste0(name, ': "', encodeString(val), '"')}, names(lst), lst), collapse=", ")
409+
}

R/getHighdimData.R

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,7 @@
2525

2626
# Performance notes
2727
#
28-
# Downloading and parsing large data sets of high dimensional data can take a
29-
# significant amount of time (minutes for several 100 mb). We have attempted to
30-
# optimize the process a reasonable amount.
31-
#
32-
# The current RCurl wrapper doesn't expose functionality to download a binary
33-
# url and process the chunks asynchronously as they come in (that is only
34-
# supported for text urls). Doing the downloading and parsing at the same time
35-
# should give a significant improvement, but that would require changes in RCurl
36-
# or a different way of downloading the data.
37-
#
38-
# The parser has also been optimized up to the level that the R code itself only
28+
# The parser has been optimized up to the level that the R code itself only
3929
# takes a minority of the runtime. The most time consuming operations are the
4030
# foreign function calls to retrieve the fields from messages and to construct
4131
# objects to parse the varint32 preceding each message. Significant further
@@ -45,7 +35,6 @@
4535

4636
getHighdimData <- function(study.name, concept.match = NULL, concept.link = NULL, projection = NULL,
4737
data.constraints = list(), assay.constraints = list(), highdim.type = 1,
48-
progress.download = .make.progresscallback.download(),
4938
progress.parse = .make.progresscallback.parse(),
5039
...) {
5140

@@ -93,7 +82,7 @@ getHighdimData <- function(study.name, concept.match = NULL, concept.link = NULL
9382
}
9483
}
9584

96-
serverResult <- .transmartServerGetRequest(projectionLink, accept.type = "binary", errorHandler = errorHandler, progress = progress.download)
85+
serverResult <- .transmartServerGetRequest(projectionLink, accept.type = "binary", errorHandler = errorHandler)
9786
if (length(serverResult) == 0) {
9887
warning("No data could be found. The server yielded an empty dataset. Returning NULL.")
9988
return(NULL)
@@ -144,8 +133,9 @@ getHighdimData <- function(study.name, concept.match = NULL, concept.link = NULL
144133

145134
# The argument is a single named list
146135
.expandConstraints <- function(constraints) {
147-
# The JSON encoder encodes single item vectors as scalars. We need those to be lists as well sometimes.
148-
j <- function(val) if (length(val) == 1) list(val) else val
136+
# Previously used json packages encode length 1 vectors as scalars, we need them as lists. Jsonlite which we are using
137+
# now doesn't do that so this wrapping function is now a no-op.
138+
j <- function(val) val
149139

150140
# some deep functional/lazy magic
151141
mapply(function(val, con) switch(con,

R/getPatientSet.R

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2014, 2015 The Hyve B.V.
2+
#
3+
# This file is part of tranSMART R Client: R package allowing access to
4+
# tranSMART's data via its RESTful API.
5+
#
6+
# This program is free software: you can redistribute it and/or modify it
7+
# under the terms of the GNU General Public License as published by the
8+
# Free Software Foundation, either version 3 of the License, or (at your
9+
# option) any later version, along with the following terms:
10+
#
11+
# 1. You may convey a work based on this program in accordance with
12+
# section 5, provided that you retain the above notices.
13+
# 2. You may convey verbatim copies of this program code as you receive
14+
# it, in any medium, provided that you retain the above notices.
15+
#
16+
# This program is distributed in the hope that it will be useful, but
17+
# WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19+
# Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License along
22+
# with this program. If not, see <http://www.gnu.org/licenses/>..
23+
24+
getPatientSet <- function(id) {
25+
if (!is.numeric(id) || id %% 1 != 0 || id < 0) {
26+
stop(paste(id, "is not a valid positive integer"))
27+
}
28+
.ensureTransmartConnection()
29+
30+
patientSet <- .transmartGetJSON(paste0("/patient_sets/", id))
31+
32+
# Don't expose id, it should not be used and will be removed from a future version of rest-api
33+
# COMPAT: remove this block if support for the old rest-api is dropped.
34+
if (length(patientSet$patients) && "id" %in% names(patientSet$patients[[1]])) {
35+
for (i in seq_along(patientSet$patients)) {
36+
patientSet$patients[[i]]$id <- NULL
37+
}
38+
}
39+
40+
names(patientSet$patients) <- sapply(patientSet$patients, function(p) {p$inTrialId})
41+
patientSet
42+
}

0 commit comments

Comments
 (0)