rcsb.utils.repository 0.51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rcsb/__init__.py +1 -0
- rcsb/utils/__init__.py +1 -0
- rcsb/utils/repository/CurrentHoldingsProvider.py +357 -0
- rcsb/utils/repository/RemovedHoldingsProvider.py +402 -0
- rcsb/utils/repository/RepositoryProvider.py +1335 -0
- rcsb/utils/repository/ScanRepoUtil.py +428 -0
- rcsb/utils/repository/UnreleasedHoldingsProvider.py +163 -0
- rcsb/utils/repository/UpdateHoldingsProvider.py +111 -0
- rcsb/utils/repository/__init__.py +0 -0
- rcsb_utils_repository-0.51.dist-info/METADATA +72 -0
- rcsb_utils_repository-0.51.dist-info/RECORD +13 -0
- rcsb_utils_repository-0.51.dist-info/WHEEL +4 -0
- rcsb_utils_repository-0.51.dist-info/licenses/LICENSE +201 -0
rcsb/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
rcsb/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
##
|
|
2
|
+
# File: CurrentHoldingsProvider.py
|
|
3
|
+
# Date: 18-May-2021 jdw
|
|
4
|
+
#
|
|
5
|
+
# Updates:
|
|
6
|
+
# 12-Jun-2023 dwp Set useCache default to False to force redownloading of holdings files
|
|
7
|
+
# 1-Jul-2024 dwp Stop populating "2fo-fc Map" and "fo-fc Map" content types (DSN6 maps), and
|
|
8
|
+
# only include "Map Coefficients" (MTZ map coefficients) in repository holdings
|
|
9
|
+
# 9-Sep-2024 dwp Always defer to loading holdings data from remote (rather than storing it locally);
|
|
10
|
+
# Add validation coefficients to list of repository_content_types
|
|
11
|
+
# 16-Oct-2024 dwp Remove usage of EDMAPS holdings file
|
|
12
|
+
# 28-Jan-2025 dwp Add support for IHM holdings file loading
|
|
13
|
+
#
|
|
14
|
+
##
|
|
15
|
+
"""Provide inventory of current repository content.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import datetime
|
|
19
|
+
import logging
|
|
20
|
+
import os.path
|
|
21
|
+
|
|
22
|
+
import pytz
|
|
23
|
+
|
|
24
|
+
from rcsb.utils.io.FileUtil import FileUtil
|
|
25
|
+
from rcsb.utils.io.MarshalUtil import MarshalUtil
|
|
26
|
+
from rcsb.utils.struct.EntryInfoProvider import EntryInfoProvider
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CurrentHoldingsProvider(object):
|
|
32
|
+
"""Provide inventory of current repository content."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, cachePath, useCache=False, **kwargs):
|
|
35
|
+
self.__cachePath = cachePath
|
|
36
|
+
self.__dirPath = os.path.join(cachePath, "holdings")
|
|
37
|
+
self.__storeCache = kwargs.get("storeCache", False)
|
|
38
|
+
self.__repoType = kwargs.get("repoType", "pdb") # can be set to "pdb" or "pdb_ihm"
|
|
39
|
+
#
|
|
40
|
+
baseUrl = kwargs.get("holdingsTargetUrl", "https://files.wwpdb.org/pub/pdb/holdings")
|
|
41
|
+
fallbackUrl = kwargs.get("holdingsFallbackUrl", "https://files.wwpdb.org/pub/pdb/holdings")
|
|
42
|
+
#
|
|
43
|
+
if self.__repoType == "pdb_ihm":
|
|
44
|
+
baseUrl = baseUrl.replace("pdb/holdings", "pdb_ihm/holdings")
|
|
45
|
+
fallbackUrl = fallbackUrl.replace("pdb/holdings", "pdb_ihm/holdings")
|
|
46
|
+
#
|
|
47
|
+
entryUrlContent = os.path.join(baseUrl, "current_file_holdings.json.gz")
|
|
48
|
+
entryUrlFallbackContent = os.path.join(fallbackUrl, "current_file_holdings.json.gz")
|
|
49
|
+
entryUrlIds = os.path.join(baseUrl, "released_structures_last_modified_dates.json.gz")
|
|
50
|
+
entryUrlFallbackIds = os.path.join(fallbackUrl, "released_structures_last_modified_dates.json.gz")
|
|
51
|
+
#
|
|
52
|
+
self.__mU = MarshalUtil(workPath=self.__dirPath)
|
|
53
|
+
#
|
|
54
|
+
self.__invD = self.__reloadEntryContent(entryUrlContent, entryUrlFallbackContent, self.__dirPath, useCache=useCache)
|
|
55
|
+
self.__idD = self.__reloadEntryIds(entryUrlIds, entryUrlFallbackIds, self.__dirPath, useCache=useCache)
|
|
56
|
+
self.__refD = {}
|
|
57
|
+
#
|
|
58
|
+
if self.__repoType != "pdb_ihm":
|
|
59
|
+
refdataUrlIds = os.path.join(baseUrl, "refdata_id_list.json.gz")
|
|
60
|
+
refdataUrlFallbackIds = os.path.join(fallbackUrl, "refdata_id_list.json.gz")
|
|
61
|
+
self.__refD = self.__reloadRefdataIds(refdataUrlIds, refdataUrlFallbackIds, self.__dirPath, useCache=useCache)
|
|
62
|
+
#
|
|
63
|
+
# EntryInfoProvider must be cached before this class is invoked -
|
|
64
|
+
self.__eiP = EntryInfoProvider(cachePath=self.__cachePath, useCache=True)
|
|
65
|
+
ok = self.__eiP.testCache()
|
|
66
|
+
if not ok:
|
|
67
|
+
self.__eiP = None
|
|
68
|
+
|
|
69
|
+
def testCache(self, minCount=220000):
|
|
70
|
+
if self.__repoType == "pdb_ihm":
|
|
71
|
+
minCount = 300
|
|
72
|
+
logger.info("Inventory length cD (%d) id list (%d)", len(self.__invD), len(self.__idD))
|
|
73
|
+
# JDW - restore consistency checks
|
|
74
|
+
# if len(self.__invD) > minCount and len(self.__idD) > minCount and len(self.__invD) == len(self.__idD):
|
|
75
|
+
if len(self.__invD) > minCount and len(self.__idD) > minCount:
|
|
76
|
+
return True
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def hasEntryContentType(self, entryId, contentType):
|
|
80
|
+
"""Return if the current content types is available for the input entry identifier"""
|
|
81
|
+
try:
|
|
82
|
+
return contentType in self.__invD[entryId.upper()]
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.exception("Failing for %r with %s", entryId, str(e))
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def getEntryContentTypes(self, entryId):
|
|
88
|
+
"""Return the current content types for the input entry identifier"""
|
|
89
|
+
try:
|
|
90
|
+
return sorted(self.__invD[entryId.upper()].keys())
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.exception("Failing for %r with %s", entryId, str(e))
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
def getAllContentTypes(self):
|
|
96
|
+
"""Return the all current content types for the repository"""
|
|
97
|
+
entryId = None
|
|
98
|
+
try:
|
|
99
|
+
tS = set()
|
|
100
|
+
for entryId, tD in self.__invD.items():
|
|
101
|
+
for ky in tD:
|
|
102
|
+
tS.add(ky)
|
|
103
|
+
return sorted(tS)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.exception("Failing for %r with %s", entryId, str(e))
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
def getEntryContentTypePathList(self, entryId, contentType):
|
|
109
|
+
"""Return the current content types for the input entry identifier"""
|
|
110
|
+
try:
|
|
111
|
+
return self.__invD[entryId.upper()][contentType]
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.debug("Failing for %r %r with %s", entryId, contentType, str(e))
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
def getEntryInventory(self):
|
|
117
|
+
"""Return the current inventory dictionary"""
|
|
118
|
+
try:
|
|
119
|
+
return self.__invD
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.debug("Failing with %s", str(e))
|
|
122
|
+
return {}
|
|
123
|
+
|
|
124
|
+
def getEntryIdList(self, afterDateTimeStamp=None):
|
|
125
|
+
"""Return the ID code list or optionally IDs changed after the input time stamp.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
afterDateTimeStamp (str, optional): ISO format date time stamp. Defaults to None.
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
if afterDateTimeStamp:
|
|
132
|
+
dt = datetime.datetime.fromisoformat(afterDateTimeStamp).replace(tzinfo=pytz.utc)
|
|
133
|
+
return [k for k, v in self.__idD.items() if v > dt]
|
|
134
|
+
else:
|
|
135
|
+
return list(self.__idD.keys())
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error("Failing with %s", str(e))
|
|
138
|
+
#
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
def getRcsbContentAndAssemblies(self):
|
|
142
|
+
return self.__assembleEntryContentTypes(self.__invD)
|
|
143
|
+
|
|
144
|
+
# --- reference data ---
|
|
145
|
+
def getBirdIdList(self):
|
|
146
|
+
return self.__getRefIdListByType("BIRD")
|
|
147
|
+
|
|
148
|
+
def getBirdChemCompIdList(self):
|
|
149
|
+
return self.__getRefIdListByType("BIRD CC")
|
|
150
|
+
|
|
151
|
+
def getBirdFamilyIdList(self):
|
|
152
|
+
return self.__getRefIdListByType("BIRD Family")
|
|
153
|
+
|
|
154
|
+
def getChemCompIdList(self):
|
|
155
|
+
return self.__getRefIdListByType("CC")
|
|
156
|
+
|
|
157
|
+
def __getRefIdListByType(self, refType):
|
|
158
|
+
idL = []
|
|
159
|
+
try:
|
|
160
|
+
for rId, tup in self.__refD.items():
|
|
161
|
+
if tup and tup[0] == refType:
|
|
162
|
+
idL.append(rId)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.exception("Failing with %s", str(e))
|
|
165
|
+
return idL
|
|
166
|
+
|
|
167
|
+
# ---
|
|
168
|
+
def getStatusDetails(self):
|
|
169
|
+
retD = {}
|
|
170
|
+
for entryId in self.__invD:
|
|
171
|
+
retD[entryId] = {"status": "CURRENT", "status_code": "REL"}
|
|
172
|
+
return retD
|
|
173
|
+
|
|
174
|
+
def hasValidationReportData(self, entryId):
|
|
175
|
+
return self.__hasValidationReportData(self.__invD, entryId)
|
|
176
|
+
|
|
177
|
+
def __hasValidationReportData(self, invD, entryId):
|
|
178
|
+
if entryId.upper() in invD:
|
|
179
|
+
tD = invD[entryId.upper()]
|
|
180
|
+
if "validation_report" in tD:
|
|
181
|
+
for pth in tD["validation_report"]:
|
|
182
|
+
if pth[-7:] == ".cif.gz":
|
|
183
|
+
return True
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
def __assembleEntryContentTypes(self, invD):
|
|
187
|
+
# Mapping between repository content types and those used by RCSB.org
|
|
188
|
+
contentTypeD = {
|
|
189
|
+
"assembly_mmcif": "assembly mmCIF",
|
|
190
|
+
"assembly_pdb": "assembly PDB",
|
|
191
|
+
"combined_nmr_data_nef": "Combined NMR data (NEF)",
|
|
192
|
+
"combined_nmr_data_nmr-star": "Combined NMR data (NMR-STAR)",
|
|
193
|
+
"mmcif": "entry mmCIF",
|
|
194
|
+
"nmr_chemical_shifts": "NMR chemical shifts",
|
|
195
|
+
"nmr_restraints_v1": "NMR restraints V1",
|
|
196
|
+
"nmr_restraints_v2": "NMR restraints V2",
|
|
197
|
+
"pdb": "entry PDB",
|
|
198
|
+
"pdb_bundle": "entry PDB bundle",
|
|
199
|
+
"pdbml": "entry PDBML",
|
|
200
|
+
# "pdbml_extatom": "entry PDBML",
|
|
201
|
+
# "pdbml_noatom": "entry PDBML",
|
|
202
|
+
"structure_factors": "structure factors",
|
|
203
|
+
# Missing mappings
|
|
204
|
+
# "validation report",
|
|
205
|
+
# "validation slider image"
|
|
206
|
+
# "validation 2fo-fc coefficients"
|
|
207
|
+
# "validation fo-fc coefficients"
|
|
208
|
+
# "FASTA sequence",
|
|
209
|
+
}
|
|
210
|
+
#
|
|
211
|
+
noPolymerL = self.__eiP.getEntriesByPolymerEntityCount(count=0) if self.__eiP else []
|
|
212
|
+
logger.info("Entries missing polymers (%d)", len(noPolymerL))
|
|
213
|
+
# for id in noPolymerL:
|
|
214
|
+
# logger.info("id: %s", id)
|
|
215
|
+
ctD = {}
|
|
216
|
+
assemD = {}
|
|
217
|
+
for entryId, tD in invD.items():
|
|
218
|
+
assemS = set()
|
|
219
|
+
if entryId not in noPolymerL:
|
|
220
|
+
ctD.setdefault(entryId, []).append("FASTA sequence")
|
|
221
|
+
for contentType, pthL in tD.items():
|
|
222
|
+
if contentType in contentTypeD:
|
|
223
|
+
ctD.setdefault(entryId, []).append(contentTypeD[contentType])
|
|
224
|
+
if contentType == "validation_report":
|
|
225
|
+
# "/pdb/validation_reports/01/201l/201l_full_validation.pdf.gz"
|
|
226
|
+
# "/pdb/validation_reports/01/201l/201l_multipercentile_validation.png.gz"
|
|
227
|
+
# "/pdb/validation_reports/01/201l/201l_multipercentile_validation.svg.gz"
|
|
228
|
+
# "/pdb/validation_reports/01/201l/201l_validation.pdf.gz"
|
|
229
|
+
# "/pdb/validation_reports/01/201l/201l_validation.xml.gz"
|
|
230
|
+
# "/pdb/validation_reports/01/201l/201l_validation_2fo-fc_map_coef.cif.gz"
|
|
231
|
+
# "/pdb/validation_reports/01/201l/201l_validation_fo-fc_map_coef.cif.gz"
|
|
232
|
+
for pth in pthL:
|
|
233
|
+
# Use "_full_validation.pdf.gz" instead of just ".pdf.gz" to avoid re-appending for non-full "_validation.pdf.gz" file
|
|
234
|
+
if "full_validation.pdf.gz" in pth:
|
|
235
|
+
ctD.setdefault(entryId, []).append("validation report")
|
|
236
|
+
elif "validation.svg.gz" in pth:
|
|
237
|
+
ctD.setdefault(entryId, []).append("validation slider image")
|
|
238
|
+
elif "validation.cif.gz" in pth:
|
|
239
|
+
ctD.setdefault(entryId, []).append("validation data mmCIF")
|
|
240
|
+
elif "validation_2fo-fc_map_coef.cif.gz" in pth:
|
|
241
|
+
ctD.setdefault(entryId, []).append("validation 2fo-fc coefficients")
|
|
242
|
+
elif "validation_fo-fc_map_coef.cif.gz" in pth:
|
|
243
|
+
ctD.setdefault(entryId, []).append("validation fo-fc coefficients")
|
|
244
|
+
if contentType == "assembly_mmcif":
|
|
245
|
+
# "/pdb/data/biounit/mmCIF/divided/a0/7a09-assembly1.cif.gz"
|
|
246
|
+
for pth in pthL:
|
|
247
|
+
fn = os.path.basename(pth)
|
|
248
|
+
aId = fn.split(".")[0].split("-")[1].replace("assembly", "")
|
|
249
|
+
assemS.add(aId)
|
|
250
|
+
|
|
251
|
+
if contentType == "assembly_pdb":
|
|
252
|
+
# "/pdb/data/biounit/coordinates/divided/02/302d.pdb1.gz"
|
|
253
|
+
for pth in pthL:
|
|
254
|
+
fn = os.path.basename(pth)
|
|
255
|
+
aId = fn.split(".")[1].replace("pdb", "")
|
|
256
|
+
assemS.add(aId)
|
|
257
|
+
assemD[entryId] = list(assemS)
|
|
258
|
+
return ctD, assemD
|
|
259
|
+
|
|
260
|
+
def __reloadEntryContent(self, urlTarget, urlFallbackTarget, dirPath, useCache=True):
|
|
261
|
+
invD = {}
|
|
262
|
+
fU = FileUtil()
|
|
263
|
+
fn = fU.getFileName(urlTarget)
|
|
264
|
+
if self.__repoType == "pdb_ihm":
|
|
265
|
+
fn = fn.replace(".json", "_ihm.json") # must do this to prevent overlapping filenames with PDB
|
|
266
|
+
fp = os.path.join(dirPath, fn)
|
|
267
|
+
self.__mU.mkdir(dirPath)
|
|
268
|
+
#
|
|
269
|
+
if self.__storeCache and useCache and self.__mU.exists(fp):
|
|
270
|
+
invD = self.__mU.doImport(fp, fmt="json")
|
|
271
|
+
logger.info("Reading cached inventory (%d)", len(invD))
|
|
272
|
+
else:
|
|
273
|
+
invD = self.__mU.doImport(urlTarget, fmt="json")
|
|
274
|
+
logger.info("Loaded inventory from %s (%r)", urlTarget, len(invD))
|
|
275
|
+
if len(invD) == 0:
|
|
276
|
+
invD = self.__mU.doImport(urlFallbackTarget, fmt="json")
|
|
277
|
+
logger.info("Loaded fallback inventory from %s (%r)", urlFallbackTarget, len(invD))
|
|
278
|
+
#
|
|
279
|
+
# previous method - save file locally
|
|
280
|
+
if self.__storeCache:
|
|
281
|
+
logger.info("Fetch inventory from %s", urlTarget)
|
|
282
|
+
ok = fU.get(urlTarget, fp)
|
|
283
|
+
if not ok:
|
|
284
|
+
ok = fU.get(urlFallbackTarget, fp)
|
|
285
|
+
if ok:
|
|
286
|
+
ofp = fp[:-3] if fp.endswith(".gz") else fp
|
|
287
|
+
ok = self.__mU.doExport(ofp, invD, fmt="json", indent=3)
|
|
288
|
+
if fp.endswith(".gz"):
|
|
289
|
+
logger.info("Updating the current entry contents (%r) in %r", ok, fp)
|
|
290
|
+
fU.compress(ofp, fp)
|
|
291
|
+
return invD
|
|
292
|
+
|
|
293
|
+
def __reloadEntryIds(self, urlTarget, urlFallbackTarget, dirPath, useCache=True):
|
|
294
|
+
tD = {}
|
|
295
|
+
idD = {}
|
|
296
|
+
fU = FileUtil()
|
|
297
|
+
fn = fU.getFileName(urlTarget)
|
|
298
|
+
if self.__repoType == "pdb_ihm":
|
|
299
|
+
fn = fn.replace(".json", "_ihm.json") # must do this to prevent overlapping filenames with PDB
|
|
300
|
+
fp = os.path.join(dirPath, fn)
|
|
301
|
+
self.__mU.mkdir(dirPath)
|
|
302
|
+
#
|
|
303
|
+
if self.__storeCache and useCache and self.__mU.exists(fp):
|
|
304
|
+
tD = self.__mU.doImport(fp, fmt="json")
|
|
305
|
+
logger.info("Reading cached IDs list (%d)", len(tD))
|
|
306
|
+
else:
|
|
307
|
+
tD = self.__mU.doImport(urlTarget, fmt="json")
|
|
308
|
+
logger.info("Loaded ID list from %s (%r)", urlTarget, len(tD))
|
|
309
|
+
if len(tD) == 0:
|
|
310
|
+
tD = self.__mU.doImport(urlFallbackTarget, fmt="json")
|
|
311
|
+
logger.info("Loaded fallback ID list from %s (%r)", urlFallbackTarget, len(tD))
|
|
312
|
+
#
|
|
313
|
+
if self.__storeCache:
|
|
314
|
+
logger.info("Fetch ID list from %s", urlTarget)
|
|
315
|
+
ok = fU.get(urlTarget, fp)
|
|
316
|
+
if not ok:
|
|
317
|
+
ok = fU.get(urlFallbackTarget, fp)
|
|
318
|
+
#
|
|
319
|
+
for k, v in tD.items():
|
|
320
|
+
try:
|
|
321
|
+
idD[k] = datetime.datetime.fromisoformat(v)
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.error("Date processing failing for %r %r with %s", k, v, str(e))
|
|
324
|
+
#
|
|
325
|
+
sTupL = sorted(idD.items(), key=lambda item: item[1])
|
|
326
|
+
return {k: v for k, v in sTupL}
|
|
327
|
+
|
|
328
|
+
def __reloadRefdataIds(self, urlTarget, urlFallbackTarget, dirPath, useCache=True):
|
|
329
|
+
tD = {}
|
|
330
|
+
idD = {}
|
|
331
|
+
fU = FileUtil()
|
|
332
|
+
fn = fU.getFileName(urlTarget)
|
|
333
|
+
fp = os.path.join(dirPath, fn)
|
|
334
|
+
self.__mU.mkdir(dirPath)
|
|
335
|
+
#
|
|
336
|
+
if self.__storeCache and useCache and self.__mU.exists(fp):
|
|
337
|
+
tD = self.__mU.doImport(fp, fmt="json")
|
|
338
|
+
logger.info("Reading cached IDs list (%d)", len(tD))
|
|
339
|
+
else:
|
|
340
|
+
tD = self.__mU.doImport(urlTarget, fmt="json")
|
|
341
|
+
logger.info("Loaded ID list from %s (%r)", urlTarget, len(tD))
|
|
342
|
+
if len(tD) == 0:
|
|
343
|
+
tD = self.__mU.doImport(urlFallbackTarget, fmt="json")
|
|
344
|
+
logger.info("Loaded fallback ID list from %s (%r)", urlFallbackTarget, len(tD))
|
|
345
|
+
#
|
|
346
|
+
if self.__storeCache:
|
|
347
|
+
logger.info("Fetch ID list from %s", urlTarget)
|
|
348
|
+
ok = fU.get(urlTarget, fp)
|
|
349
|
+
if not ok:
|
|
350
|
+
ok = fU.get(urlFallbackTarget, fp)
|
|
351
|
+
#
|
|
352
|
+
for k, v in tD.items():
|
|
353
|
+
try:
|
|
354
|
+
idD[k] = (v["content_type"], datetime.datetime.fromisoformat(v["last_modified_date"]))
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.error("Date processing failing for %r %r with %s", k, v, str(e))
|
|
357
|
+
return idD
|