Skip to content

Commit b242e4c

Browse files
authored
Merge pull request #75 from ens-lgil/main
Add new files_ids + updated export scripts
2 parents ac75c6c + 3883ced commit b242e4c

8 files changed

Lines changed: 164 additions & 42 deletions

File tree

exports/config_template.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'scoring_files_dir': '<path_to_scoring_files_dataset_directory>', # Uncompressed dataset directory
1111
'method_name': '<dataset_method_name>', # Only needed for GTEx exports
1212
'platform_name': '<platform_name>', # Only needed for GTEx exports
13-
'use_opgs_id_as_gene': '<True_or_False>' # Use the OmicsPred ID in the 'gene' column or not
13+
'use_different_id_as_gene': '<opgs_id_OR_name>' # Use the OmicsPred ID, the score name in the 'gene' column or not. None if the key is missing
1414
}
1515

1616

exports/exports.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
{'name': 'file_url_scoring_files_pgsc_calc', 'label': 'Scoring files (pgsc_calc compatible)', 'skip_auto_import': True},
2828
{'name': 'file_url_scoring_files_hm_38', 'label': 'Harmonised scoring files (mapped to GRCh38)', 'skip_auto_import': True},
2929
{'name': 'file_url_scoring_files', 'label': 'Scoring files', 'skip_auto_import': True},
30+
{'name': 'file_url_predictdb', 'label': 'PredictDB', 'skip_auto_import': True},
31+
{'name': 'file_url_covariance', 'label': 'Covariance', 'skip_auto_import': True},
3032
{'name': 'file_url_validation_results', 'label': 'Validation data file', 'skip_auto_import': True},
3133
{'name': 'file_url_score_variant_info', 'label': 'Score variant info file', 'skip_auto_import': True},
3234
{'name': 'file_url_gwas_sumstats', 'label': 'GWAS summary stats files', 'skip_auto_import': True}

exports/scripts/generate_sqlite_files.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
gtex_prefix_study = 'GTExV8'
1313

14-
if 'use_opgs_id_as_gene' in sqlite_default_values.keys():
15-
use_opgs_id_as_gene = sqlite_default_values['use_opgs_id_as_gene']
14+
if 'use_different_id_as_gene' in sqlite_default_values.keys():
15+
use_different_id_as_gene = sqlite_default_values['use_different_id_as_gene']
1616
else:
17-
use_opgs_id_as_gene = True
17+
use_different_id_as_gene = None
18+
19+
1820

1921
# default_values = {
2022
# 'opp_id': 'OPP000003',
@@ -105,6 +107,6 @@ def run(*args):
105107
# Fetch dataset(s)
106108
datasets = Dataset.objects.filter(publication__id=opp_id).order_by('id')
107109
# Create SqliteExport object
108-
sqlite_export = SqliteExport(opp_id,output_sqlite_dir,scoring_files_dir,datasets,use_opgs_id_as_gene)
110+
sqlite_export = SqliteExport(opp_id,output_sqlite_dir,scoring_files_dir,datasets,use_different_id_as_gene)
109111
# Generate SQLite export file(s)
110112
sqlite_export.generate_sqlite_files()
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import requests
2+
from omicspred.models import Dataset
3+
4+
5+
def rest_api(folder_id:int, dev_token:str, offset:int=0) -> dict:
6+
rest_url = f'https://api.box.com/2.0/folders/{folder_id}/items?fields=name,shared_link'
7+
if offset:
8+
rest_url += f'&offset={offset}'
9+
print(f'- URL: {rest_url}')
10+
response = requests.get(rest_url, headers={"Content-Type" : "application/json", 'Authorization': f'Bearer {dev_token}'})
11+
response_json = response.json()
12+
if response_json:
13+
return response_json
14+
else:
15+
print(response)
16+
return None
17+
18+
19+
def run(*args):
20+
# Update shared links (score.files_ids) using the Box REST API
21+
22+
# Run example: python manage.py updated_shared_links --script-args 365934779636 metadata 123456
23+
24+
# Generate dev token:
25+
# 1 - Go to https://app.box.com/developers/console
26+
# 2 - Click on App "OmicsPred - stats"
27+
# 3 - Click on "Configuration" tab
28+
# 4 - Click on the "Generate Developer Token" button
29+
# 5 - Copy token and add it as parameter to this script
30+
if args:
31+
folder_id = args[0]
32+
file_type = args[1]
33+
dev_token = args[2]
34+
35+
if not folder_id or not file_type or not dev_token:
36+
print("- Missing parameter(s): folder_id, file_type or dev_token")
37+
exit(1)
38+
offset = 0
39+
limit = 100
40+
total_count = 0
41+
42+
count_shared_links = 0
43+
count_total_files = 0
44+
# While loop because of pagination (limit 100)
45+
while offset < total_count or total_count == 0 :
46+
result = rest_api(folder_id, dev_token, offset)
47+
offset += limit
48+
total_count = result['total_count']
49+
count_total_files = total_count
50+
for entry in result['entries']:
51+
name = entry['name']
52+
# print(f"> Entry: {name}:\n{entry}")
53+
# File
54+
if entry['type'] == 'file':
55+
if 'shared_link' in entry.keys():
56+
# print(entry['shared_link'])
57+
url = entry['shared_link']['url']
58+
dataset_id = name.split('_')[0]
59+
file_id = url.replace('https://app.box.com/s/','')
60+
print(f"- {dataset_id}: {file_id}")
61+
count_shared_links += 1
62+
if dataset_id:
63+
try:
64+
dataset = Dataset.objects.get(id=dataset_id)
65+
files_ids = dataset.files_ids
66+
if file_type not in files_ids.keys():
67+
files_ids[file_type] = file_id
68+
print(f" > {files_ids}")
69+
dataset.files_ids = files_ids
70+
dataset.save()
71+
except Dataset.DoesNotExist:
72+
print(f"!!!!! Dataset {dataset_id} doesn't exist in the DB")
73+
else:
74+
print(f"Shared link missing for {name}!")
75+
76+
print(f"\n>> Shared links: {count_shared_links}/{count_total_files}")

exports/sqlite_export.py

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -73,32 +73,45 @@ class SqliteExport:
7373
}
7474

7575

76-
def __init__(self, opp_id:str, sqlite_dir:str, scoring_files_dir:str, datasets: list, use_opgs_id_as_gene:bool, insert_block_max:int=100):
76+
def __init__(self, opp_id:str, sqlite_dir:str, scoring_files_dir:str, datasets: list, use_different_id_as_gene:bool, insert_block_max:int=100):
7777
self.opp_id = opp_id
7878
self.sqlite_dir = sqlite_dir
7979
self.scoring_files_dir = scoring_files_dir
8080
self.datasets = datasets # List of Dataset models
81-
self.use_opgs_id_as_gene = use_opgs_id_as_gene
81+
self.use_different_id_as_gene = use_different_id_as_gene
8282
self.insert_block_max = insert_block_max
8383

8484

8585
def read_scoring_file(self, dataset_dir:str, score_id:str, dataset_name:str, reported_trait_id:str, molecular_traits:dict, genome_build:str) -> list:
8686
''' Read content of the scoring file '''
87+
88+
data = []
89+
# Fetch correct directory (usually composed of the dataset ID + suffix)
90+
if not os.path.isdir(f'{self.scoring_files_dir}/{dataset_dir}'):
91+
for ds_dir in os.listdir(self.scoring_files_dir):
92+
if ds_dir.startswith(dataset_dir) and os.path.isdir(f'{self.scoring_files_dir}/{ds_dir}'):
93+
dataset_dir = ds_dir
94+
break
95+
if not os.path.isdir(f'{self.scoring_files_dir}/{dataset_dir}'):
96+
print(f">> Can't find the directory {self.scoring_files_dir}/{dataset_dir}")
97+
exit()
98+
99+
# Initiate file path(s)
87100
scoring_file = f'{self.scoring_files_dir}/{dataset_dir}/{score_id}.txt'
88101
scoring_file_gz = f'{self.scoring_files_dir}/{dataset_dir}/{score_id}.txt.gz'
89-
data = []
102+
90103
if not os.path.isfile(scoring_file):
91104
scoring_file = f'{self.scoring_files_dir}/{dataset_dir}/{score_id}_model.txt'
92105

93-
# Text file
106+
# Case of a text file
94107
if os.path.isfile(scoring_file):
95108
with open(scoring_file, newline='') as sc_file:
96109
# Ignore lines starting with '#'
97110
reader = csv.DictReader(filter(lambda row: row[0]!='#', sc_file), delimiter='\t')
98111
for row in reader:
99112
variant_info = self.get_variant_info(row,score_id,dataset_name,reported_trait_id,molecular_traits,genome_build)
100113
data.append(variant_info)
101-
# Zipped file
114+
# Case of a zipped file
102115
elif os.path.isfile(scoring_file_gz):
103116
with gzip.open(scoring_file_gz, mode="rt") as sc_file:
104117
# Ignore lines starting with '#'
@@ -113,18 +126,24 @@ def read_scoring_file(self, dataset_dir:str, score_id:str, dataset_name:str, rep
113126

114127
def get_variant_info(self, row:dict, score_id:str, dataset_name:str, reported_trait_id:str, molecular_traits:dict, genome_build:str) -> tuple:
115128
''' Fetch variant information of a row in the scoring file '''
116-
rsid = row['rsID'] if 'rsID' in row.keys() else row['rsid']
129+
rsid = None
130+
if 'rsID' in row.keys():
131+
rsid = row['rsID']
132+
elif 'rsid' in row.keys():
133+
rsid = row['rsid']
117134
varid = None
118135
if 'chr_name' in row.keys() and 'chr_position' in row.keys():
119136
# 1_156000340_A_G_b38
120137
to_replace = 'GRCh' if genome_build.startswith('GRCh') else 'hg'
121138
build = 'b'+genome_build.replace(to_replace,'')
122139
varid = f"{row['chr_name']}_{row['chr_position']}_{row['other_allele']}_{row['effect_allele']}_{build}"
140+
if not rsid:
141+
rsid = f'chr{row['chr_name']}:{row['chr_position']}'
123142
elif 'variant_description' in row.keys():
124143
if row['variant_description'].startswith('variant_id=chr'):
125144
varid = row['variant_description'].split('=')[1]
126145
gene_id = molecular_traits['gene_id']
127-
if self.gtex_prefix_study in dataset_name:
146+
if (self.gtex_prefix_study in dataset_name) or self.use_different_id_as_gene == 'name':
128147
variant_info_list = [
129148
score_id, # omicspred_id
130149
reported_trait_id, # gene
@@ -148,7 +167,7 @@ def get_variant_info(self, row:dict, score_id:str, dataset_name:str, reported_tr
148167

149168

150169
def create_table_extra(self, cur:sqlite3.Cursor, is_proteomics:bool) -> list:
151-
if self.use_opgs_id_as_gene:
170+
if self.use_different_id_as_gene == 'opgs_id':
152171
cols_extra = ['gene', 'gene_id', 'genename', 'gene_type']
153172
else:
154173
cols_extra = ['omicspred_id', 'gene', 'genename', 'gene_type']
@@ -161,7 +180,7 @@ def create_table_extra(self, cur:sqlite3.Cursor, is_proteomics:bool) -> list:
161180

162181

163182
def create_table_weights(self, cur:sqlite3.Cursor, is_proteomics:bool) -> None:
164-
if self.use_opgs_id_as_gene:
183+
if self.use_different_id_as_gene == 'opgs_id':
165184
cols_weights = ['gene', 'gene_id']
166185
else:
167186
cols_weights = ['omicspred_id', 'gene']
@@ -214,20 +233,28 @@ def get_score_data(self, dataset_name:str, score:Score, gene:Gene, reported_trai
214233
'gene_type': gene.biotype
215234
}
216235
else:
217-
if self.use_opgs_id_as_gene:
218-
score_data = {
219-
'gene': score_id,
220-
'gene_id': gene_id,
221-
'genename': gene.name,
222-
'gene_type': gene.biotype
223-
}
224-
else:
225-
score_data = {
226-
'omicspred_id': score_id,
227-
'gene': gene_id,
228-
'genename': gene.name,
229-
'gene_type': gene.biotype
230-
}
236+
match self.use_different_id_as_gene:
237+
case 'opgs_id':
238+
score_data = {
239+
'gene': score_id,
240+
'gene_id': gene_id,
241+
'genename': gene.name,
242+
'gene_type': gene.biotype
243+
}
244+
case 'name':
245+
score_data = {
246+
'omicspred_id': score_id,
247+
'gene': reported_trait_id,
248+
'genename': gene.name,
249+
'gene_type': gene.biotype
250+
}
251+
case _:
252+
score_data = {
253+
'omicspred_id': score_id,
254+
'gene': gene_id,
255+
'genename': gene.name,
256+
'gene_type': gene.biotype
257+
}
231258
if is_proteomics:
232259
proteins = score.proteins.all()
233260
protein = proteins[0]
@@ -243,7 +270,7 @@ def get_score_data(self, dataset_name:str, score:Score, gene:Gene, reported_trai
243270
return score_data
244271

245272

246-
def generate_sqlite_files(self):
273+
def generate_sqlite_files(self,skip_zip:bool=False):
247274
ds_count = 1
248275
ds_total_count = len(self.datasets)
249276
for dataset in self.datasets:
@@ -310,6 +337,8 @@ def generate_sqlite_files(self):
310337
if self.gtex_prefix_study in dataset_name and score.name.startswith('intron_'):
311338
intron_name = score.name.split('_')
312339
reported_trait_id = f'{intron_name[0]}_{intron_name[1]}_{intron_name[2]}_{intron_name[3]}'
340+
elif self.use_different_id_as_gene == 'name':
341+
reported_trait_id = score.name
313342
# Get performance metrics (Training)
314343
metrics = self.get_metrics(score)
315344

@@ -354,11 +383,12 @@ def generate_sqlite_files(self):
354383
con.commit()
355384

356385
# Gzip the SQLite file
357-
with open(sqlite_filepath, 'rb') as f_in:
358-
with gzip.open(f'{sqlite_filepath}.gz', 'wb') as f_out:
359-
shutil.copyfileobj(f_in, f_out)
360-
os.remove(sqlite_filepath)
361-
print(f"\t-> File zipped")
386+
if not skip_zip:
387+
with open(sqlite_filepath, 'rb') as f_in:
388+
with gzip.open(f'{sqlite_filepath}.gz', 'wb') as f_out:
389+
shutil.copyfileobj(f_in, f_out)
390+
os.remove(sqlite_filepath)
391+
print(f"\t-> File zipped")
362392

363393

364394
## FOR test - begin ##

exports/tests/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
'opp_id': 'OPP000001',
99
'sqlite_dir': current_dir+'/exports/tests/output/',
1010
'scoring_files_dir': current_dir+'/exports/tests/data/scoring_files',
11-
'use_opgs_id_as_gene': True
11+
'use_different_id_as_gene': 'opgs_id'
1212
}

exports/tests/test_sqlite_export.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ExportSQLiteTest(TestCase):
1818
dataset_label = 'DS1'
1919
current_dir = os.getcwd()
2020
scoring_files_dir = sqlite_default_values['scoring_files_dir']
21-
use_opgs_id_as_gene = sqlite_default_values['use_opgs_id_as_gene']
21+
use_different_id_as_gene = sqlite_default_values['use_different_id_as_gene']
2222

2323
sqlite_filename = f'{opd_id}_{dataset_label}.db'
2424

@@ -71,8 +71,8 @@ def get_genome_build_data(self,cursor:sqlite3.Cursor):
7171
def test_sqlite_export(self):
7272
print("# Test SQLite export")
7373
datasets = Dataset.objects.filter(id=self.opd_id)
74-
sqlite_export = SqliteExport(self.opp_id,self.output_sqlite_dir,self.scoring_files_dir,datasets,self.use_opgs_id_as_gene)
75-
sqlite_export.generate_sqlite_files()
74+
sqlite_export = SqliteExport(self.opp_id,self.output_sqlite_dir,self.scoring_files_dir,datasets,self.use_different_id_as_gene)
75+
sqlite_export.generate_sqlite_files(True)
7676

7777
sqlite_ref_connection = sqlite3.connect(self.ref_sqlite_filepath)
7878
sqlite_ref_cursor = sqlite_ref_connection.cursor()

rest_api/static/rest_api/openapi/openapi-schema.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,22 @@ components:
298298
type: string
299299
description: "Scoring files"
300300
example: "https://app.box.com/shared/static/z86fg93jg5gwdmmu4xn6u287mre2g5o7"
301+
scoring_files_pgsc_calc:
302+
type: string
303+
description: "Scoring files - pgsc_calc compatible"
304+
example: "https://app.box.com/shared/static/7qgaa2ci00x9kggyd48qodxd1ffo3ol7"
305+
metadata:
306+
type: string
307+
description: "Metadata Excel spreadsheet"
308+
example: "https://app.box.com/shared/static/anwpkxcpaixbfj67vd6odzqbsb7rk8a8"
309+
predictdb:
310+
type: string
311+
description: "PredictDB prediction weigths - SQLite database"
312+
example: "https://app.box.com/shared/static/unjn1bmt2xhwkd33pp0wczldfzrkevu2"
313+
covariance:
314+
type: string
315+
description: "Covariance file, to be used alongside the PredictDB database in MetaXcan tools"
316+
example: "https://app.box.com/shared/static/24avhdwb2gfo083oz3r6j8w6qqburbp2"
301317
score_variant_info:
302318
type: string
303319
description: "Variant information file"
@@ -306,10 +322,6 @@ components:
306322
type: string
307323
description: "PGS performance metrics"
308324
example: "https://app.box.com/shared/static/7j7233otah0yxl4rypqx44wzsj7xywxp"
309-
scoring_files_pgsc_calc:
310-
type: string
311-
description: "Scoring files - pgsc_calc compatible"
312-
example: "https://app.box.com/shared/static/7qgaa2ci00x9kggyd48qodxd1ffo3ol7"
313325

314326
Dataset_extended:
315327
description: "Based on the **Dataset** schema, with the addition of the '**publication**'."

0 commit comments

Comments
 (0)