Skip to content

Commit 18df106

Browse files
committed
Refactor pc_prop() to use GET instead of POST to make CI tests more robust
1 parent bd034ce commit 18df106

2 files changed

Lines changed: 133 additions & 118 deletions

File tree

R/pubchem.R

Lines changed: 127 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -338,142 +338,154 @@ pc_prop <- function(cid, properties = NULL, verbose = getOption("verbose"), ...)
338338
if (!ping_service("pc")) stop(webchem_message("service_down"))
339339

340340
cid_o <- cid
341-
342-
if (verbose) message("Coercing queries to positive integers. ", appendLF = FALSE)
343-
344341
cid <- suppressWarnings(as.integer(cid))
345342

343+
invalid <- is.na(cid) | cid <= 0
344+
cid[invalid] <- NA_integer_
345+
346346
if (verbose) {
347-
index <- which(is.na(cid) & !is.na(cid_o))
347+
message("Coercing queries to positive integers. ", appendLF = FALSE)
348+
index <- which(invalid & !is.na(cid_o))
348349
if (length(index) > 0) {
349350
for (i in index) {
350-
message(paste0(cid_o[index], " coerced to NA. "), appendLF = FALSE)
351-
}
352-
}
353-
}
354-
355-
if (any(cid <= 0, na.rm = TRUE)) {
356-
index <- which(cid <= 0)
357-
cid[index] <- NA
358-
if (verbose) {
359-
for (i in index) {
360-
message(paste0(cid_o[index], " coerced to NA. "), appendLF = FALSE)
351+
message(paste0(cid_o[i], " coerced to NA. "), appendLF = FALSE)
361352
}
362353
}
354+
message("Done.")
363355
}
364356

365-
if (verbose) message("Done.")
357+
vcids <- tibble::tibble(
358+
query = cid_o,
359+
cid = cid
360+
)
366361

367-
if (mean(is.na(cid)) == 1) {
362+
if (mean(is.na(vcids$cid)) == 1) {
368363
if (verbose) webchem_message("na")
369364
return(NA)
370365
}
371366

372-
napos <- which(is.na(cid))
373-
cid <- cid[!is.na(cid)]
367+
cid <- vcids$cid[!is.na(vcids$cid)]
374368
prolog <- "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
375-
input <- "/compound/cid"
376-
if (is.null(properties))
377-
properties <- c(
378-
"MolecularFormula",
379-
"MolecularWeight",
380-
"SMILES",
381-
"ConnectivitySMILES",
382-
"InChI",
383-
"InChIKey",
384-
"IUPACName",
385-
"Title",
386-
"XLogP",
387-
"ExactMass",
388-
"MonoisotopicMass",
389-
"TPSA",
390-
"Complexity",
391-
"Charge",
392-
"HBondDonorCount",
393-
"HBondAcceptorCount",
394-
"RotatableBondCount",
395-
"HeavyAtomCount",
396-
"IsotopeAtomCount",
397-
"AtomStereoCount",
398-
"DefinedAtomStereoCount",
399-
"UndefinedAtomStereoCount",
400-
"BondStereoCount",
401-
"DefinedBondStereoCount",
402-
"UndefinedBondStereoCount",
403-
"CovalentUnitCount",
404-
"PatentCount",
405-
"PatentFamilyCount",
406-
"AnnotationTypes",
407-
"AnnotationTypeCount",
408-
"SourceCategories",
409-
"LiteratureCount",
410-
"Volume3D",
411-
"XStericQuadrupole3D",
412-
"YStericQuadrupole3D",
413-
"ZStericQuadrupole3D",
414-
"FeatureCount3D",
415-
"FeatureAcceptorCount3D",
416-
"FeatureDonorCount3D",
417-
"FeatureAnionCount3D",
418-
"FeatureCationCount3D",
419-
"FeatureRingCount3D",
420-
"FeatureHydrophobeCount3D",
421-
"ConformerModelRMSD3D",
422-
"EffectiveRotorCount3D",
423-
"ConformerCount3D",
424-
"Fingerprint2D"
425-
)
369+
input <- "/compound/cid/"
370+
all_properties <- c(
371+
"MolecularFormula",
372+
"MolecularWeight",
373+
"SMILES",
374+
"ConnectivitySMILES",
375+
"InChI",
376+
"InChIKey",
377+
"IUPACName",
378+
"Title",
379+
"XLogP",
380+
"ExactMass",
381+
"MonoisotopicMass",
382+
"TPSA",
383+
"Complexity",
384+
"Charge",
385+
"HBondDonorCount",
386+
"HBondAcceptorCount",
387+
"RotatableBondCount",
388+
"HeavyAtomCount",
389+
"IsotopeAtomCount",
390+
"AtomStereoCount",
391+
"DefinedAtomStereoCount",
392+
"UndefinedAtomStereoCount",
393+
"BondStereoCount",
394+
"DefinedBondStereoCount",
395+
"UndefinedBondStereoCount",
396+
"CovalentUnitCount",
397+
"PatentCount",
398+
"PatentFamilyCount",
399+
"AnnotationTypes",
400+
"AnnotationTypeCount",
401+
"SourceCategories",
402+
"LiteratureCount",
403+
"Volume3D",
404+
"XStericQuadrupole3D",
405+
"YStericQuadrupole3D",
406+
"ZStericQuadrupole3D",
407+
"FeatureCount3D",
408+
"FeatureAcceptorCount3D",
409+
"FeatureDonorCount3D",
410+
"FeatureAnionCount3D",
411+
"FeatureCationCount3D",
412+
"FeatureRingCount3D",
413+
"FeatureHydrophobeCount3D",
414+
"ConformerModelRMSD3D",
415+
"EffectiveRotorCount3D",
416+
"ConformerCount3D",
417+
"Fingerprint2D"
418+
)
419+
if (is.null(properties)) {
420+
properties <- all_properties
421+
} else {
422+
invalid_props <- setdiff(properties, all_properties)
423+
if (length(invalid_props) > 0) {
424+
stop(
425+
"Invalid properties: ",
426+
paste(invalid_props, collapse = ", "),
427+
". Valid properties: ",
428+
all_properties |> sort() |> paste(collapse = ", "),
429+
call. = FALSE
430+
)
431+
}
432+
}
426433
properties <- paste(properties, collapse = ",")
427434
output <- paste0("/property/", properties, "/JSON")
428435

429-
qurl <- paste0(prolog, input, output)
430-
if (verbose) webchem_message("query_all", appendLF = FALSE)
431-
webchem_sleep(type = 'API')
432-
res <- try(httr::RETRY("POST",
433-
qurl,
434-
httr::user_agent(webchem_url()),
435-
body = list("cid" = paste(cid, collapse = ",")),
436-
terminate_on = 404,
437-
quiet = TRUE), silent = TRUE)
438-
if (inherits(res, "try-error")) {
439-
if (verbose) webchem_message("service_down")
440-
return(NA)
441-
}
442-
if (verbose) message(httr::message_for_status(res))
443-
if (res$status_code == 200) {
444-
cont <- jsonlite::fromJSON(rawToChar(res$content))
445-
if (names(cont) == "Fault") {
446-
if (verbose) {
447-
message(cont$Fault$Message, ". ", cont$Fault$Details, ". Returning NA.")
448-
}
449-
return(NA)
436+
foo <- function(x) {
437+
qurl <- paste0(prolog, input, x, output)
438+
if (verbose) webchem_message("query", x, appendLF = FALSE)
439+
webchem_sleep(type = 'API')
440+
res <- try(httr::RETRY("GET",
441+
qurl,
442+
httr::user_agent(webchem_url()),
443+
terminate_on = 404,
444+
quiet = TRUE), silent = TRUE)
445+
if (inherits(res, "try-error")) {
446+
if (verbose) webchem_message("service_down")
447+
return(data.frame())
450448
}
451-
out <- cont$PropertyTable[[1]]
452-
# insert NA rows
453-
narow <- rep(NA, ncol(out))
454-
for (i in seq_along(napos)) {
455-
#capture NAs at beginning
456-
firstnna <- min(which(!is.na(cid_o)))
457-
if (napos[i] < firstnna) {
458-
out <- rbind(narow, out)
459-
} else {
460-
# capture NAs at end
461-
if (napos[i] > nrow(out)) {
462-
# print(napos[i])
463-
out <- rbind(out, narow)
464-
} else {
465-
out <- rbind(out[1:(napos[i] - 1), ], narow, out[napos[i]:nrow(out), ])
449+
if (verbose) message(httr::message_for_status(res))
450+
if (res$status_code == 200) {
451+
cont <- jsonlite::fromJSON(rawToChar(res$content))
452+
if (names(cont) == "Fault") {
453+
if (verbose) {
454+
message(cont$Fault$Message, ". ", cont$Fault$Details, ". Returning NA.")
466455
}
467-
}}
468-
rownames(out) <- NULL
469-
out$CID <- cid_o
470-
out <- tibble::as_tibble(out)
471-
class(out) <- c("pc_prop", class(out))
472-
return(out)
473-
}
474-
else {
475-
return(NA)
456+
return(data.frame())
457+
}
458+
out <- cont$PropertyTable[[1]]
459+
out <- out |> dplyr::mutate(CID = x) |> dplyr::relocate(CID)
460+
return(out)
461+
} else {
462+
return(data.frame())
463+
}
476464
}
465+
out <- lapply(cid, foo) |> dplyr::bind_rows()
466+
467+
if (nrow(out) == 0) return(NA)
468+
469+
na_row <- as.data.frame(as.list(rep(NA, ncol(out))))
470+
names(na_row) <- names(out)
471+
out_list <- lapply(seq_len(nrow(vcids)), function(i) {
472+
if (is.na(vcids$cid[i])) {
473+
na_row$CID <- vcids$query[i]
474+
return(na_row)
475+
} else {
476+
hit <- out[which(out$CID == vcids$query[i]),]
477+
if (nrow(hit) == 0) {
478+
na_row$CID <- vcids$query[i]
479+
return(na_row)
480+
} else {
481+
return(hit)
482+
}
483+
}
484+
})
485+
out <- do.call(rbind, out_list)
486+
out <- tibble::as_tibble(out)
487+
class(out) <- c("pc_prop", class(out))
488+
return(out)
477489
}
478490

479491
#' Search synonyms in pubchem

tests/testthat/test-pubchem.R

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ test_that("pc_prop", {
102102
skip_if_not(up, "PubChem service is down")
103103

104104
b <- suppressWarnings(pc_prop("xxx", properties = "SMILES"))
105-
c <- pc_prop("5564", properties = c("SMILES", "InChiKey"))
105+
c <- pc_prop("5564", properties = c("SMILES", "InChIKey"))
106106
expect_true(is.na(b))
107107
expect_equal(ncol(c), 3)
108108

@@ -117,10 +117,13 @@ test_that("pc_prop", {
117117
expect_true(all(
118118
d1m == c(
119119
"Coercing queries to positive integers. ",
120-
"balloon coerced to NA. ",
121120
"-1 coerced to NA. ",
121+
"balloon coerced to NA. ",
122122
"Done.\n",
123-
"Querying. ",
123+
"Querying 5564. ",
124+
"OK (HTTP 200).",
125+
"\n",
126+
"Querying 2244. ",
124127
"OK (HTTP 200).",
125128
"\n"
126129
)))

0 commit comments

Comments
 (0)