88import re
99import boto3
1010from botocore .exceptions import ClientError
11+ from functools import reduce
1112
1213
1314class S3CoverageProvider (CoverageProvider ):
1415
1516 def __init__ (self , src ):
1617 self .client = boto3 .client ('s3' )
1718 self .src = src
19+ split_url = urlparse (self .src )
20+ self .bucket = split_url .netloc
21+ self .prefix = split_url .path .lstrip ('/' )
1822 self .validate_source ()
19- # self.catalog = self.discover_files()
23+ self .catalog = self .discover_files ()
2024
2125 def validate_source (self ):
2226 """
@@ -30,8 +34,8 @@ def validate_source(self):
3034
3135 # Check that objects exists in bucket at prefix.
3236 try :
33- obj_resp = self .client .list_objects_v2 (Bucket = split_url . netloc ,
34- Prefix = split_url . path . lstrip ( '/' ) ,
37+ obj_resp = self .client .list_objects_v2 (Bucket = self . bucket ,
38+ Prefix = self . prefix ,
3539 MaxKeys = 10 )
3640 except ClientError as err :
3741 msg = err .response ['Error' ]['Message' ]
@@ -52,16 +56,38 @@ def validate_source(self):
5256 def evaluate_chrom_representation (self ):
5357 """
5458 All chromosomes expected to be represented in all coverage bins
59+ Return list of messages to the contrary.
60+ """
61+ messages = []
62+ for bin_name , cov_bin in self .catalog .items ():
63+ missing_chroms = [chrom for chrom in self ._chroms if chrom not in cov_bin .keys ()]
64+ if (missing_chroms ):
65+ messages .append (f'Coverage { bin_name } missing chroms: { missing_chroms } ' )
66+ return (messages )
67+
68+ @staticmethod
69+ def simple_dict_flat (acc , d ):
5570 """
56- msgs = []
57- return (msgs )
71+ Helper function for reducing list of non-nested dicts to list of values
72+ """
73+ acc .extend (list (d .values ()))
74+ return (acc )
5875
5976 def evaluate_chrom_readability (self ):
6077 """
61- All coverage files expected to be readable
78+ All coverage files expected to be readable.
79+ Return list of messages to the contrary.
6280 """
63- msgs = []
64- return (msgs )
81+ messages = []
82+ all_paths = reduce (S3CoverageProvider .simple_dict_flat , self .catalog .values (), [])
83+
84+ for path in all_paths :
85+ try :
86+ self .client .head_object (Bucket = self .bucket , Key = path )
87+ except ClientError as err :
88+ err_msg = err .response ['Error' ]['Message' ]
89+ messages .append (f'{ self .bucket } /{ path } : { err_msg } ' )
90+ return (messages )
6591
6692 def evaluate_catalog (self ):
6793 warn_msgs = []
@@ -72,9 +98,31 @@ def evaluate_catalog(self):
7298 def discover_files (self ):
7399 """
74100 Find and organize the chrN.bin_X.YZ.tar.gz coverage files into a dictionary organized by
75- bin then chromosome.
101+ bin then chromosome. Form of bin-chr-file index will be:
102+ {
103+ 'bin_0.25': {'chr1': 's3://bucket/prefix/chr1.bin_0.25.tsv.gz',
104+ ...,
105+ 'chr22': 's3://bucket/prefix/chr22.bin_0.25.tsv.gz'},
106+ ....
107+ 'bin_full': {...}
108+ }
76109 """
110+ split_url = urlparse (self .src )
111+ bucket = split_url .netloc
112+ prefix = split_url .path .lstrip ('/' )
113+
114+ obj_resp = self .client .list_objects_v2 (Bucket = bucket , Prefix = prefix )
115+ all_keys = [item ['Key' ] for item in obj_resp ['Contents' ]]
116+
117+ chr_patt = re .compile (r'.*chr([0-9X]{1,2})' )
77118 result = {}
119+ for bin_name in self ._bins :
120+ result [bin_name ] = {}
121+ bin_keys = [item for item in all_keys if item .startswith (f'{ prefix } /{ bin_name } ' )]
122+
123+ for key in bin_keys :
124+ chr = chr_patt .match (key ).group (1 )
125+ result [bin_name ][chr ] = key
78126 return (result )
79127
80128 def lookup_coverage_path (self , cov_bin , chrom ):
0 commit comments