Skip to content

Commit f2fa1de

Browse files
committed
more changes
1 parent 359310a commit f2fa1de

File tree

7 files changed

+91
-112
lines changed

7 files changed

+91
-112
lines changed

Cargo.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
[package]
2-
authors = ["Peter Robinson, Rouven Router, Patrick Nairne"]
3-
description = "A Rust library for shared tools between different Phenopacket extraction programs."
2+
authors = ["Patrick Nairne, Rouven Reuter"]
3+
description = "A Rust library for retrieving data from the VariantValidator and HGNC APIs for Phenopackets."
44
edition = "2024"
55
homepage = "https://robinsongroup.github.io/"
6-
license = "MIT"
7-
name = "pivot"
8-
version = "0.1.4"
6+
license-file = "LICENSE"
7+
name = "pivotal"
8+
version = "0.1.6"
9+
keywords = ["variant", "validator", "hgnc", "hgvs", "phenopacket"]
10+
readme = "README.md"
11+
repository = "https://github.com/psnairne/PIVOT"
912

1013

1114
[dependencies]
1215
phenopackets = { version = "0.2.2-post1", features = ["serde"] }
13-
rstest = "0.26.1"
1416
serde = "1.0.228"
1517
thiserror = "2.0.17"
1618
ratelimit = "0.10.0"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## PIVOT
2-
A Rust library for getting data from VariantValidator.
2+
A Rust library for getting data from VariantValidator and HGNC.
33

44
## License
55
This project is licensed under MIT.

src/hgnc/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ pub enum HGNCError {
3232
Request(#[from] reqwest::Error),
3333
#[error("Something went wrong when using Mutex: {0}")]
3434
MutexError(String),
35+
#[error("HGNC fetch request for {gene} failed. Error: {err}.")]
36+
FetchRequest { gene: String, err: String },
37+
#[error("HgncAPI returned an error on {attempts} attempts to retrieve data about gene {gene}")]
38+
HgncAPI { gene: String, attempts: usize },
3539
}

src/hgnc/hgnc_client.rs

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,62 @@ use crate::hgnc::traits::HGNCData;
55
use ratelimit::Ratelimiter;
66
use reqwest::blocking::Client;
77
use std::fmt::{Debug, Formatter};
8+
use std::sync::OnceLock;
89
use std::thread::sleep;
910
use std::time::Duration;
1011

12+
static HGNC_RATE_LIMITER: OnceLock<Ratelimiter> = OnceLock::new();
13+
14+
fn hgnc_rate_limiter() -> &'static Ratelimiter {
15+
HGNC_RATE_LIMITER.get_or_init(|| {
16+
Ratelimiter::builder(10, Duration::from_millis(1100))
17+
.max_tokens(10)
18+
.build()
19+
.expect("Building rate limiter failed")
20+
})
21+
}
22+
1123
pub struct HGNCClient {
12-
rate_limiter: Ratelimiter,
24+
attempts: usize,
1325
api_url: String,
1426
client: Client,
1527
}
1628

1729
impl HGNCClient {
18-
pub fn new(rate_limiter: Ratelimiter, api_url: String) -> Self {
30+
pub fn new(attempts: usize, api_url: String) -> Self {
1931
HGNCClient {
20-
rate_limiter,
32+
attempts,
2133
api_url,
2234
client: Client::new(),
2335
}
2436
}
2537

26-
fn fetch_request(&self, url: &str) -> Result<Vec<GeneDoc>, HGNCError> {
27-
if let Err(duration) = self.rate_limiter.try_wait() {
28-
sleep(duration);
38+
fn fetch_request(&self, url: &str, query: &GeneQuery) -> Result<Vec<GeneDoc>, HGNCError> {
39+
for _ in 0..self.attempts {
40+
if let Err(duration) = hgnc_rate_limiter().try_wait() {
41+
sleep(duration);
42+
}
43+
let response = self
44+
.client
45+
.get(url)
46+
.header("User-Agent", "PIVOT")
47+
.header("Accept", "application/json")
48+
.send()
49+
.map_err(|err| HGNCError::FetchRequest {
50+
gene: query.inner().to_string(),
51+
err: err.to_string(),
52+
})?;
53+
54+
if response.status().is_success() {
55+
let gene_response = response.json::<GeneResponse>()?;
56+
return Ok(gene_response.response.docs);
57+
}
2958
}
30-
let response = self
31-
.client
32-
.get(url)
33-
.header("User-Agent", "PIVOT")
34-
.header("Accept", "application/json")
35-
.send()?;
3659

37-
let gene_response = response.json::<GeneResponse>()?;
38-
39-
Ok(gene_response.response.docs)
60+
Err(HGNCError::HgncAPI {
61+
gene: query.inner().to_string(),
62+
attempts: self.attempts,
63+
})
4064
}
4165
}
4266

@@ -46,7 +70,7 @@ impl HGNCData for HGNCClient {
4670
GeneQuery::Symbol(symbol) => format!("{}fetch/symbol/{}", self.api_url, symbol),
4771
GeneQuery::HgncId(id) => format!("{}fetch/hgnc_id/{}", self.api_url, id),
4872
};
49-
let docs = self.fetch_request(&fetch_url)?;
73+
let docs = self.fetch_request(&fetch_url, &query)?;
5074

5175
if docs.len() == 1 {
5276
Ok(docs.first().unwrap().clone())
@@ -62,12 +86,7 @@ impl HGNCData for HGNCClient {
6286

6387
impl Default for HGNCClient {
6488
fn default() -> Self {
65-
let rate_limiter = Ratelimiter::builder(10, Duration::from_secs(1))
66-
.max_tokens(10)
67-
.build()
68-
.expect("Building rate limiter failed");
69-
70-
HGNCClient::new(rate_limiter, "https://rest.genenames.org/".to_string())
89+
HGNCClient::new(3, "https://rest.genenames.org/".to_string())
7190
}
7291
}
7392

src/hgvs/hgvs_client.rs

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,24 @@ use reqwest::blocking::Client;
1111
use serde_json::Value;
1212
use std::fmt::Debug;
1313
use std::string::ToString;
14+
use std::sync::OnceLock;
1415
use std::thread::sleep;
1516
use std::time::Duration;
1617

1718
const ALLOWED_FLAGS: [&str; 2] = ["gene_variant", "mitochondrial"];
1819

20+
static HGVS_RATE_LIMITER: OnceLock<Ratelimiter> = OnceLock::new();
21+
22+
fn hgvs_rate_limiter() -> &'static Ratelimiter {
23+
HGVS_RATE_LIMITER.get_or_init(|| {
24+
Ratelimiter::builder(2, Duration::from_millis(1250))
25+
.max_tokens(2)
26+
.build()
27+
.expect("Building rate limiter failed")
28+
})
29+
}
30+
1931
pub struct HGVSClient {
20-
rate_limiter: Ratelimiter,
2132
attempts: usize,
2233
api_url: String,
2334
client: Client,
@@ -26,19 +37,9 @@ pub struct HGVSClient {
2637

2738
impl Default for HGVSClient {
2839
fn default() -> Self {
29-
let rate_limiter = Ratelimiter::builder(2, Duration::from_millis(1180))
30-
.max_tokens(2)
31-
.build()
32-
.expect("Building rate limiter failed");
3340
let api_url =
3441
"https://rest.variantvalidator.org/VariantValidator/variantvalidator/".to_string();
35-
HGVSClient::new(
36-
rate_limiter,
37-
3,
38-
api_url.to_string(),
39-
Client::new(),
40-
GenomeAssembly::Hg38,
41-
)
42+
HGVSClient::new(3, api_url.to_string(), Client::new(), GenomeAssembly::Hg38)
4243
}
4344
}
4445

@@ -55,14 +56,12 @@ impl Debug for HGVSClient {
5556

5657
impl HGVSClient {
5758
pub fn new(
58-
rate_limiter: Ratelimiter,
5959
attempts: usize,
6060
api_url: String,
6161
client: Client,
6262
genome_assembly: GenomeAssembly,
6363
) -> Self {
6464
HGVSClient {
65-
rate_limiter,
6665
attempts,
6766
api_url,
6867
client,
@@ -83,7 +82,7 @@ impl HGVSClient {
8382
unvalidated_hgvs: &str,
8483
) -> Result<VariantValidatorResponse, HGVSError> {
8584
for _ in 0..self.attempts {
86-
if let Err(duration) = self.rate_limiter.try_wait() {
85+
if let Err(duration) = hgvs_rate_limiter().try_wait() {
8786
sleep(duration);
8887
}
8988

@@ -245,49 +244,48 @@ mod tests {
245244
use crate::hgvs::traits::HGVSData;
246245
use rstest::{fixture, rstest};
247246

248-
// this forces tests to run sequentially
249-
#[rstest]
250-
fn hgvs_client_tests() {
251-
let client = HGVSClient::default();
252-
test_request_and_validate_hgvs_c_autosomal(&client);
253-
test_request_and_validate_hgvs_c_x(&client);
254-
test_request_and_validate_hgvs_n(&client);
255-
test_request_and_validate_hgvs_m(&client);
256-
test_request_and_validate_hgvs_wrong_reference_base_err(&client);
257-
test_request_and_validate_hgvs_not_c_or_n_hgvs_err(&client);
247+
#[fixture]
248+
fn client() -> HGVSClient {
249+
HGVSClient::default()
258250
}
259251

260-
fn test_request_and_validate_hgvs_c_autosomal(client: &HGVSClient) {
252+
#[rstest]
253+
fn test_request_and_validate_hgvs_c_autosomal(client: HGVSClient) {
261254
let unvalidated_hgvs = "NM_001173464.1:c.2860C>T";
262255
let validated_hgvs = client.request_and_validate_hgvs(unvalidated_hgvs).unwrap();
263256
assert_eq!(validated_hgvs.transcript_hgvs(), unvalidated_hgvs);
264257
}
265258

266-
fn test_request_and_validate_hgvs_c_x(client: &HGVSClient) {
259+
#[rstest]
260+
fn test_request_and_validate_hgvs_c_x(client: HGVSClient) {
267261
let unvalidated_hgvs = "NM_000132.4:c.3637A>T";
268262
let validated_hgvs = client.request_and_validate_hgvs(unvalidated_hgvs).unwrap();
269263
assert_eq!(validated_hgvs.transcript_hgvs(), unvalidated_hgvs);
270264
}
271265

272-
fn test_request_and_validate_hgvs_n(client: &HGVSClient) {
266+
#[rstest]
267+
fn test_request_and_validate_hgvs_n(client: HGVSClient) {
273268
let unvalidated_hgvs = "NR_002196.1:n.601G>T";
274269
let validated_hgvs = client.request_and_validate_hgvs(unvalidated_hgvs).unwrap();
275270
assert_eq!(validated_hgvs.transcript_hgvs(), unvalidated_hgvs);
276271
}
277272

278-
fn test_request_and_validate_hgvs_m(client: &HGVSClient) {
273+
#[rstest]
274+
fn test_request_and_validate_hgvs_m(client: HGVSClient) {
279275
let unvalidated_hgvs = "NC_012920.1:m.616T>C";
280276
let validated_hgvs = client.request_and_validate_hgvs(unvalidated_hgvs).unwrap();
281277
assert_eq!(validated_hgvs.transcript_hgvs(), unvalidated_hgvs);
282278
}
283279

284-
fn test_request_and_validate_hgvs_wrong_reference_base_err(client: &HGVSClient) {
280+
#[rstest]
281+
fn test_request_and_validate_hgvs_wrong_reference_base_err(client: HGVSClient) {
285282
let unvalidated_hgvs = "NM_001173464.1:c.2860G>T";
286283
let result = client.request_and_validate_hgvs(unvalidated_hgvs);
287284
assert!(matches!(result, Err(HGVSError::InvalidHgvs { .. })));
288285
}
289286

290-
fn test_request_and_validate_hgvs_not_c_or_n_hgvs_err(client: &HGVSClient) {
287+
#[rstest]
288+
fn test_request_and_validate_hgvs_not_c_or_n_hgvs_err(client: HGVSClient) {
291289
let unvalidated_hgvs = "NC_000012.12:g.39332405G>A";
292290
let result = client.request_and_validate_hgvs(unvalidated_hgvs);
293291
assert!(matches!(

tests/hgnc_stress_test.rs

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use pivot::hgnc::{GeneQuery, HGNCClient, HGNCData};
1+
use pivotal::hgnc::{GeneQuery, HGNCClient, HGNCData};
22
use rstest::{fixture, rstest};
33

44
#[macro_export]
@@ -18,7 +18,7 @@ macro_rules! skip_in_ci {
1818
}
1919

2020
// found here: https://www.genenames.org/download/statistics-and-files/
21-
// 319 genes
21+
// 274 genes
2222
#[fixture]
2323
fn genes() -> String {
2424
"A1BG
@@ -294,52 +294,7 @@ ADAMDEC1
294294
ADAMTS1
295295
ADAMTS2
296296
ADAMTS3
297-
ADAMTS4
298-
ADAMTS5
299-
ADAMTS6
300-
ADAMTS7
301-
ADAMTS8
302-
ADAMTS9
303-
ADAMTS10
304-
ADAMTS12
305-
ADAMTS13
306-
ADAMTS14
307-
ADAMTS15
308-
ADAMTS16
309-
ADAMTS17
310-
ADAMTS18
311-
ADAMTS19
312-
ADAMTS20
313-
ADAMTSL1
314-
ADAMTSL2
315-
ADAMTSL3
316-
ADAMTSL4
317-
ADAMTSL5
318-
ADAP1
319-
ADAP2
320-
ADAR
321-
ADARB1
322-
ADARB2
323-
ADAT1
324-
ADAT2
325-
ADAT3
326-
ADCK1
327-
ADCK2
328-
ADCK5
329-
ADCY1
330-
ADCY2
331-
ADCY3
332-
ADCY4
333-
ADCY5
334-
ADCY6
335-
ADCY7
336-
ADCY8
337-
ADCY9
338-
ADCY10
339-
ADCYAP1
340-
ADCYAP1R1
341-
ADD1
342-
ADD2"
297+
ADAMTS4"
343298
.to_string()
344299
}
345300

@@ -350,7 +305,8 @@ fn hgnc_stress_test(genes: String) {
350305
let gene_vec = genes.lines().collect::<Vec<&str>>();
351306
for gene in gene_vec {
352307
let query = GeneQuery::Symbol(gene);
353-
let gene_doc = client.request_gene_data(query).unwrap();
308+
let gene_doc_result = client.request_gene_data(query);
309+
let gene_doc = gene_doc_result.unwrap();
354310
assert_eq!(gene_doc.symbol.unwrap(), gene);
355311
}
356312
}

tests/hgvs_stress_test.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use pivot::hgvs::{HGVSClient, HGVSData};
1+
use pivotal::hgvs::{HGVSClient, HGVSData};
22
use rstest::{fixture, rstest};
33

44
#[macro_export]
@@ -36,13 +36,13 @@ fn create_hgvs_variants_from_transcript(
3636
}
3737

3838
// found here: https://www.ncbi.nlm.nih.gov/CCDS/CcdsBrowse.cgi?REQUEST=NUCID&DATA=1677538156
39-
//283 characters
39+
//270 characters
4040
#[fixture]
4141
fn kif21a_transcript_beginning() -> String {
4242
let str = "ATGTTGGGCGCCCCGGACGAGAGCTCCGTGCGGGTGGCTGTCAGAATAAGACCACAGCTTGCCAAAGAGA
4343
AGATTGAAGGATGCCATATTTGTACATCTGTCACACCAGGAGAGCCTCAGGTCTTCCTAGGGAAAGATAA
4444
GGCTTTTACTTTTGACTATGTATTTGACATTGACTCCCAGCAAGAGCAGATCTACATTCAATGTATAGAA
45-
AAACTAATTGAAGGTTGCTTTGAAGGATACAATGCTACAGTTTTTGCTTATGGACAAACTGGAGCTGGTA"
45+
AAACTAATTGAAGGTTGCTTTGAAGGATACAATGCTACAGTTTTTGCTTATGGACAA"
4646
.to_string();
4747
str.replace('\n', "")
4848
}

0 commit comments

Comments
 (0)