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 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