aldepyde 0.0.0a2__py3-none-any.whl → 0.0.0a32__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.

Potentially problematic release.


This version of aldepyde might be problematic. Click here for more details.

Files changed (44) hide show
  1. aldepyde/Parsers/_mmcif_parser.py +0 -0
  2. aldepyde/Parsers/_pdb_parser.py +0 -0
  3. aldepyde/__init__.py +46 -2
  4. aldepyde/_config.py +98 -36
  5. aldepyde/biomolecule/Residue.py +9 -0
  6. aldepyde/biomolecule/_Atom.py +95 -0
  7. aldepyde/biomolecule/_AtomFactory.py +71 -0
  8. aldepyde/biomolecule/__init__.py +15 -0
  9. aldepyde/biomolecule/_amino_acid.py +6 -0
  10. aldepyde/biomolecule/_dna.py +6 -0
  11. aldepyde/biomolecule/_pdb.py +455 -0
  12. aldepyde/biomolecule/_rna.py +6 -0
  13. aldepyde/biomolecule/utils.py +60 -0
  14. aldepyde/cache/__init__.py +2 -0
  15. aldepyde/cache/_cache.py +257 -0
  16. aldepyde/cache/cachemanager.py +212 -0
  17. aldepyde/cache/downloader.py +13 -0
  18. aldepyde/cache/utils.py +32 -0
  19. aldepyde/configurable.py +7 -0
  20. aldepyde/data/RemoteFileHandler.py +32 -0
  21. aldepyde/data/__init__.py +1 -0
  22. aldepyde/data.py +148 -0
  23. aldepyde/databases/PDB.py +0 -0
  24. aldepyde/databases/RemoteFileHandler.py +43 -0
  25. aldepyde/databases/UniRef.py +75 -0
  26. aldepyde/databases/__init__.py +0 -0
  27. aldepyde/databases/_database.py +38 -0
  28. aldepyde/env.py +43 -0
  29. aldepyde/fetcher/__init__.py +0 -0
  30. aldepyde/fetcher/test.py +2 -0
  31. aldepyde/json/CHG.json +25 -0
  32. aldepyde/json/Swiss_Prot.json +25 -0
  33. aldepyde/json/chemistry.json +4622 -0
  34. aldepyde/rand/RandomProtein.py +402 -0
  35. aldepyde/rand/__init__.py +3 -0
  36. aldepyde/stats/ProteinStats.py +89 -0
  37. aldepyde/stats/__init__.py +0 -0
  38. aldepyde/utils.py +275 -0
  39. {aldepyde-0.0.0a2.dist-info → aldepyde-0.0.0a32.dist-info}/METADATA +4 -3
  40. aldepyde-0.0.0a32.dist-info/RECORD +43 -0
  41. {aldepyde-0.0.0a2.dist-info → aldepyde-0.0.0a32.dist-info}/WHEEL +1 -1
  42. aldepyde-0.0.0a2.dist-info/RECORD +0 -7
  43. {aldepyde-0.0.0a2.dist-info → aldepyde-0.0.0a32.dist-info/licenses}/LICENSE +0 -0
  44. {aldepyde-0.0.0a2.dist-info → aldepyde-0.0.0a32.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,455 @@
1
+ # Submodule for reading, writing, and fetching PDB/mmcif files
2
+ from aldepyde.configurable import Configurable
3
+ from aldepyde.data import RemoteFileHandler
4
+ from ._AtomFactory import *
5
+ from ._Atom import Atom
6
+ from aldepyde import cache
7
+ from enum import Enum
8
+ import urllib.request
9
+ import requests
10
+ import gzip
11
+ from io import BytesIO
12
+ import os
13
+ from pathlib import Path
14
+
15
+ __all__ = ['PDB']
16
+
17
+
18
+ #TODO Basically rewrite this whole thing
19
+ class PDB(Configurable):
20
+ class Mode(Enum):
21
+ AUTO = 0
22
+ PDB = 1
23
+ CIF = 2
24
+
25
+ def __init__(self):
26
+ self._current = BytesIO()
27
+ self._mode = self.Mode.AUTO
28
+
29
+
30
+ def SetCurrent(self, stream: BytesIO) -> None:
31
+ self._current = stream
32
+
33
+ def Get(self) -> BytesIO:
34
+ return self._current
35
+
36
+ def ToString(self) -> str:
37
+ return self._current.read().decode()
38
+
39
+ def clear(self) -> 'PDB':
40
+ return self.Clear()
41
+
42
+ def Clear(self) -> 'PDB':
43
+ self._current = BytesIO()
44
+ return self
45
+
46
+ def auto(self):
47
+ self._mode = self.Mode.AUTO
48
+
49
+ def pdb(self):
50
+ self._mode = self.Mode.PDB
51
+
52
+ def cif(self):
53
+ self._mode = self.Mode.CIF
54
+
55
+
56
+ def UpdateSettings(self):
57
+ pass
58
+
59
+ # Enabling bad behavior
60
+ def fetch(self, code: str, extension: str="mmCIF") -> 'PDB':
61
+ return self.Fetch(code, extension=extension)
62
+
63
+ # Fetches a file directly from the PDB
64
+ def Fetch(self, code: str, extension: str="mmCIF") -> 'PDB':
65
+ code = code.lower().strip()
66
+ if "pdb" in extension.lower():
67
+ filepath = f"pdb/{code[1:3]}/pdb{code}.ent.gz"
68
+ name = f"pdb{code}.ent.gz"
69
+ else:
70
+ filepath = f"mmCIF/{code[1:3]}/{code}.cif.gz"
71
+ name = f"{code}.cif.gz"
72
+ fetch_url = f"https://files.rcsb.org/pub/pdb/data/structures/divided/{filepath}"
73
+ # stream = cache.grab_url(fetch_url, name)
74
+ stream = RemoteFileHandler.fetch_file_from_pdb(fetch_url, name) # TODO Why is this only a tar file on first download?
75
+ if RemoteFileHandler.is_gzip(stream):
76
+ stream = RemoteFileHandler.unpack_tar_gz_bio(stream)
77
+ # print(stream.getvalue())
78
+ self.SetCurrent(stream)
79
+ return self
80
+
81
+ def Load(self, file: str) -> 'PDB':
82
+ if file.endswith(".gz"):
83
+ with gzip.open(file, "r") as gz:
84
+ stream = BytesIO(gz.read())
85
+ else:
86
+ with open(file, "rb") as fp:
87
+ stream = BytesIO()
88
+ stream.write(fp.read())
89
+ self.SetCurrent(stream)
90
+ return self
91
+
92
+
93
+ #Currently only works for PDB files
94
+ # Fetches a file from the PDB or cache and saves it to a location
95
+ def SaveFetch(self, code: str, path: str, extension: str="mmCIF") -> bool:
96
+ stream = self.Fetch(code, extension, save=False).Get()
97
+ try:
98
+ with open(path, "wb") as fp:
99
+ fp.write(stream.read())
100
+ except:
101
+ return False
102
+
103
+ def Construct(self, data: str|None = None):
104
+ if os.path.exists(data):
105
+ pass
106
+
107
+
108
+
109
+ # TODO Everything below here :)
110
+
111
+
112
+ # This function does too much.
113
+ def Read(self, file: str|BytesIO):
114
+ # Whatever we have, convert it to a list of string-lines
115
+ if isinstance(file, str) and len(file) == 4: # We have a code
116
+ lines = self.Fetch(file, extension='pdb', as_string=True).split('\n')
117
+ elif isinstance(file, str): # We have a stringline
118
+ pass
119
+ elif isinstance(file, str) and os.path.exists(file): # Path to a file
120
+ # Get file type
121
+ # Read accordingly
122
+ pass
123
+ elif isinstance(file, BytesIO): # File stream
124
+ pass
125
+ else:
126
+ raise ValueError("Data to read must be a PDB 4-letter code, a file as a string, a path to a file,"
127
+ "or a fetched file.")
128
+ for line in lines:
129
+ if line[0:6] == "ATOM " or line[0:6] == "HETATM":
130
+ print(line)
131
+ atom = self._parse_atom_pdb(line)
132
+ print(atom)
133
+ # print(lines)
134
+
135
+ def ParsePDB(self, lines):
136
+ for line in lines:
137
+ if line[0:6] == "ATOM " or line[0:6] == "HETATM":
138
+ print(line)
139
+ atom = self._parse_atom_pdb(line)
140
+ print(atom)
141
+
142
+ def ParseCIF(self):
143
+ pass
144
+
145
+ # I guess people may want this?
146
+ def ParseXML(self):
147
+ pass
148
+
149
+ def _prepare_file(self, file: str|BytesIO) -> list:
150
+ if isinstance(file, str):
151
+ file = file.strip()
152
+ if isinstance(file, str) and len(file) == 4: # Try reading as a pdb code
153
+ lines = self.Fetch(file, extension='pdb', as_string=True).split('\n')
154
+ elif isinstance(file, str) and os.path.exists(file): # Path to a file
155
+ lines = self.Load(file).split('\n')
156
+ elif isinstance(file, str): # We have a stringline
157
+ lines = file.split('\n')
158
+ elif isinstance(file, BytesIO): # File stream
159
+ lines = file.read().decode().split('\n')
160
+ else:
161
+ raise ValueError("Unable to process file.")
162
+ return lines
163
+
164
+ def _parse_atom_cif(self, line: str) -> Atom:
165
+ pass
166
+
167
+ def _parse_atom_pdb(self, line: str) -> Atom:
168
+ record_name = line[0:6]
169
+ serial = line[6:11]
170
+ name = line[12:16]
171
+ altLoc = line[16]
172
+ resName = line[17:20]
173
+ chainID = line[21]
174
+ resSeq = line[22:26]
175
+ iCode = line[26]
176
+ x = line[30:38]
177
+ y = line[38:46]
178
+ z = line[46:54]
179
+ occupancy = line[54:60]
180
+ tempFactor = line[60:66]
181
+ element = line[76:78]
182
+ charge = line[78:80]
183
+ return AtomFactory.EnforcedAtom(
184
+ record_name=record_name,
185
+ serial=serial,
186
+ name=name,
187
+ altLoc=altLoc,
188
+ resName=resName,
189
+ chainID=chainID,
190
+ resSeq=resSeq,
191
+ iCode=iCode,
192
+ x=x,
193
+ y=y,
194
+ z=z,
195
+ occupancy=occupancy,
196
+ tempFactor=tempFactor,
197
+ element=element,
198
+ charge=charge
199
+ )
200
+
201
+ # Surprisingly hard to design this because of how irregular PDB files are
202
+ # @classmethod
203
+ # def DetectFiletype(cls, file: str|BytesIO, trust_extension: bool = True) -> list:
204
+ # if isinstance(file, str) and os.path.exists(file):
205
+ # if trust_extension and len(Path(file).suffixes) != 0:
206
+ # return Path(file).suffixes
207
+ # extensions = []
208
+ # lines = cls._prepare_file(file)
209
+ #
210
+
211
+
212
+ # @classmethod
213
+ # def IsPDB(cls, file: str|BytesIO):
214
+ # pass
215
+ # Extrapolate file type
216
+ # Go line by line
217
+ # Grab symmetry information
218
+ # Parse atom information Organize residues
219
+
220
+
221
+ # urllib.request.urlretrieve(fetch_url, name)
222
+ # response = requests.get(fetch_url)
223
+ # response.raise_for_status()
224
+ # with gzip.GzipFile(fileobj=BytesIO(response.content)) as gz:
225
+ # with TextIOWrapper(gz) as fp:
226
+ # for line in fp.readlines():
227
+ # print(line.strip())
228
+ # break
229
+
230
+ # print("Fetching ", prot)
231
+ # import urllib.request
232
+ # url = r'https://files.rcsb.org/download/' + prot.strip() + '.pdb'
233
+ # try:
234
+ # with urllib.request.urlopen(url) as f:
235
+ # self.biomolecule.SetFetch(f.read().decode('utf-8'))
236
+ # self._Parse(hold_pdb)
237
+ # return True
238
+ # except urllib.error.URLError:
239
+ # sys.stderr.write("The requested pdb code could not be retrieved or does not exist\n")
240
+ # if crash:
241
+ # exit()
242
+ # return False
243
+
244
+ # def Fetch(self, code: str, extension: str="pdb"):
245
+ #
246
+ # fetch_url = r"https://files.rcsb.org/pub/pdb/data/structures/divided/pdb/b8/"
247
+ #
248
+ # url = r"https://www.rcsb.org/fasta/entry/" + prot.strip().upper() + r"/display"
249
+ # try:
250
+ # with urllib.request.urlopen(url) as f:
251
+ # return f.read().decode('utf-8')
252
+ # except urllib.error.URLError:
253
+ # sys.stderr.write("The requested pdb code could not be retrieved or does not exist\n")
254
+ # return False
255
+
256
+
257
+ # class PDB:
258
+ # def __init__(self):
259
+ # self.biomolecule = biomolecule()
260
+ # # print(apalib.j_data.GetJson())
261
+ #
262
+ # def Current(self):
263
+ # return self.biomolecule
264
+ #
265
+ # def FetchFASTA(self, prot):
266
+ # import urllib.request
267
+ # url = r"https://www.rcsb.org/fasta/entry/" + prot.strip().upper() + r"/display"
268
+ # try:
269
+ # with urllib.request.urlopen(url) as f:
270
+ # return f.read().decode('utf-8')
271
+ # except urllib.error.URLError:
272
+ # sys.stderr.write("The requested pdb code could not be retrieved or does not exist\n")
273
+ # return False
274
+ #
275
+ # # def FetchAsFile(self, prot):
276
+ # # import urllib.request
277
+ # # url = r'https://files.rcsb.org/download/' + prot.strip() + '.pdb'
278
+ # # try:
279
+ # # with urllib.request.urlopen(url) as f:
280
+ #
281
+ # #Enabling bad behavior
282
+ # def fetch(self, prot, crash=True, hold_pdb=False):
283
+ # return self.Fetch(prot, crash, hold_pdb)
284
+ # def Fetch(self, prot, crash = True, hold_pdb=False):
285
+ # # print("Fetching ", prot)
286
+ # import urllib.request
287
+ # url = r'https://files.rcsb.org/download/' + prot.strip() + '.pdb'
288
+ # try:
289
+ # with urllib.request.urlopen(url) as f:
290
+ # self.biomolecule.SetFetch(f.read().decode('utf-8'))
291
+ # self._Parse(hold_pdb)
292
+ # return True
293
+ # except urllib.error.URLError:
294
+ # sys.stderr.write("The requested pdb code could not be retrieved or does not exist\n")
295
+ # if crash:
296
+ # exit()
297
+ # return False
298
+ #
299
+ # def Read(self, path, hold_pdb=False):
300
+ # with open(path, 'r') as fp:
301
+ # self.biomolecule.SetFetch(fp.read())
302
+ # self._Parse(hold_pdb)
303
+ #
304
+ # # Wrapper for the ParsePDB file to allow functionality with a fetched protein
305
+ # def _Parse(self, hold_pdb=False):
306
+ # try:
307
+ # if self.biomolecule.GetFetch() is None:
308
+ # raise apaExcept.NoFetchError
309
+ # return self._ParsePDB(self.biomolecule.GetFetch(), hold_pdb)
310
+ # # return self._ParsePDB(self.container.GetFetch().splitlines())
311
+ # except apaExcept.NoFetchError as e:
312
+ # sys.stderr.write(e.message)
313
+ #
314
+ #
315
+ # #PDB standard described here: https://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html
316
+ # def _ParsePDB(self, raw_pdb, hold_pdb=False):
317
+ # self.biomolecule.ClearAll()
318
+ # remark350 = ""
319
+ # if hold_pdb:
320
+ # self.biomolecule.SetFetch(raw_pdb)
321
+ # for line in raw_pdb.splitlines():
322
+ # # print(line)
323
+ # if line[0:6] == 'ATOM ' or line[0:6] == 'HETATM':
324
+ # self._ExtractAtomAndResidue(line)
325
+ # if line.find("REMARK 350") != -1:
326
+ # remark350 += line + "\n"
327
+ #
328
+ # symmetry_groups = self._ParseRemark350(remark350)
329
+ # self.biomolecule._AddSymmetry(symmetry_groups)
330
+ # self.biomolecule._PostParseEvaluations()
331
+ # def _ParseRemark350(self, remark350):
332
+ # lines = remark350.splitlines()
333
+ # lines.append("END")
334
+ # symFlag = False
335
+ # biomolecules = []
336
+ # for line in lines:
337
+ # if 'REMARK 350 APPLY' in line and ":" in line and symFlag is False:
338
+ # symFlag = True
339
+ # symLines = []
340
+ # chains = line[line.find(":")+1:].replace(",", " ").split()
341
+ # elif 'REMARK 350 APPLY' in line and ":" in line and symFlag is True:
342
+ # chains += line[line.find(":")+1:].replace(",", " ").split() #This feels dangerous
343
+ # elif 'AND CHAINS:' in line and symFlag is True:
344
+ # chains += line[line.find(":") + 1:].replace(",", " ").split() #This also feels dangerous
345
+ # elif 'BIOMT' in line and symFlag:
346
+ # BIOMT = line[13:19].strip()
347
+ # id = line[19:23].strip()
348
+ # x = line[23:33].strip()
349
+ # y = line[33:43].strip()
350
+ # z = line[43:53].strip()
351
+ # m = line[53:].strip()
352
+ # symLines.append([BIOMT, int(id), float(x), float(y), float(z) ,float(m)])
353
+ # elif symFlag:
354
+ # symFlag = False
355
+ # biomolecule = {}
356
+ # biomolecule['chains'] = chains
357
+ # for sl in symLines:
358
+ # if sl[1] not in biomolecule.keys():
359
+ # biomolecule[sl[1]] = []
360
+ # biomolecule[sl[1]].append([sl[0]] + sl[2:])
361
+ # biomolecules.append(biomolecule)
362
+ # #I hate PDB file format
363
+ # for i in range(len(biomolecules)):
364
+ # biomolecule = biomolecules[i]
365
+ # for key in biomolecule.keys():
366
+ # biomolecule[key].sort(key=lambda x:x[0])
367
+ # for i in range(len(biomolecule[key])):
368
+ # if key == "chains":
369
+ # continue
370
+ # biomolecule[key][i].pop(0)
371
+ # return biomolecules
372
+ #
373
+ # def _ExtractAtomAndResidue(self, line):
374
+ # serial = line[6:11].strip()
375
+ # name = line[12:16].strip()
376
+ # altLoc = line[16].strip()
377
+ # resName = line[17:20].strip()
378
+ # chainID = line[21].strip()
379
+ # resSeq = line[22:26].strip()
380
+ # iCode = line[26].strip()
381
+ # x = line[30:38].strip()
382
+ # y = line[38:46].strip()
383
+ # z = line[46:54].strip()
384
+ # occupancy = line[54:60].strip()
385
+ # tempFactor = line[60:66].strip()
386
+ # element = line[76:78].strip()
387
+ # charge = line[78:80].strip()
388
+ # atom = Atom.Atom(serial=serial, name=name, altLoc=altLoc, resName=resName, chainID=chainID, resSeq=resSeq,
389
+ # iCode=iCode, x=float(x), y=float(y), z=float(z), occupancy=occupancy, tempFactor=tempFactor, element=element,
390
+ # charge=charge)
391
+ # if "HETATM" in line:
392
+ # resType = "HETATM"
393
+ # else:
394
+ # resType = self.DetermineResType(resName)
395
+ # residue = self.biomolecule.AddResidue(resType, resSeq, resName, chainID)
396
+ # residue.InsertAtom(atom)
397
+ #
398
+ # def DetermineResType(self, res_code):
399
+ # if data.ValidateRNA(res_code):
400
+ # return 'RNA'
401
+ # elif data.ValidateDNA(res_code):
402
+ # return 'DNA'
403
+ # elif data.ValidateAA(res_code):
404
+ # return "AA"
405
+ # else:
406
+ # return "HETATM"
407
+ #
408
+ # #Remove all of the waters from the current fetch. Probably make this more general for any HETATM group. Make a wrapper?
409
+ # def RemoveWater(self):
410
+ # h_chains = self.biomolecule.GetHETATMChains()
411
+ # for chain in h_chains.keys():
412
+ # h_chains[chain] = {key: value for (key, value) in h_chains[chain].items() if value.GetResName().upper() != 'HOH'}
413
+ #
414
+ # # def Validate(self, **kwargs):
415
+ # # for key in kwargs:
416
+ # # if key != 'pdb' or (key == 'pdb' and not isinstance(kwargs['pdb'], str)):
417
+ # # raise apalib.apalibExceptions.BadKwarg('pdb=<pdb_to_validate>')
418
+ #
419
+ # #Write contents to a PDB file
420
+ #
421
+ # def AddChain(self, collection, name=None):
422
+ # if name is None:
423
+ # chains = list(self.biomolecule.Chains.keys())
424
+ # for i in range(26):
425
+ # if chr(ord('z') - i) not in chains:
426
+ # name = chr(ord('z') - i)
427
+ # break
428
+ # else:
429
+ # if len(name) > 1 or not name.isalnum():
430
+ # raise BadNameException("A chain must be a single-character alphanumeric input")
431
+ # #Rechain all of the atoms and residues with new name
432
+ # for residue in collection:
433
+ # residue.SetChainID(name)
434
+ # for atom in residue.GetAtoms():
435
+ # atom.SetChainID(name)
436
+ # self.biomolecule.AddChain(name)
437
+ # self.biomolecule.Chains[name] = collection
438
+ #
439
+ # def WritePDB(self, fp):
440
+ # s = sorted(self.biomolecule.DumpResidues(), key=lambda x: x.seqNum)
441
+ # with open(fp, "w") as f:
442
+ # for res in s:
443
+ # f.write(res.WriteForPDB())
444
+ #
445
+ # #Write contents to FASTA
446
+ # def ToFASTA(self):
447
+ # ls = self.biomolecule.AsList(ordered=True)
448
+ # retStr = ""
449
+ # for r in ls:
450
+ # if data.ValidateAA(r.resName):
451
+ # name = data.Map("Amino Acids", r.resName)
452
+ # retStr += data.GetJson()["Amino Acids"][name]["1code"]
453
+ # elif r.resName.upper() != "HOH":
454
+ # retStr += "X"
455
+ # return retStr
@@ -0,0 +1,6 @@
1
+ from aldepyde.biomolecule.Residue import Residue
2
+
3
+ __all__ = ['rna']
4
+
5
+ class rna(Residue):
6
+ pass
@@ -0,0 +1,60 @@
1
+ from ._dna import dna
2
+ from ._rna import rna
3
+ from ._amino_acid import amino_acid
4
+ from .Residue import Residue
5
+
6
+ # These three could probably be condensed
7
+ def CheckDNA(self, value: str, *args) -> bool:
8
+ if args == ():
9
+ args = self._dna_map[1]
10
+ map = self.load_values(*args, store_as=None)
11
+ if value in map['dna'].keys():
12
+ return True
13
+ return False
14
+
15
+
16
+ def CheckRNA(self, value: str, *args) -> bool:
17
+ if args == ():
18
+ args = self._map[1]
19
+ map = self.load_values(*args, store_as=None)
20
+ if value in map['dna'].keys():
21
+ return True
22
+ return False
23
+
24
+
25
+ def CheckAA(self, value: str, *args) -> bool:
26
+ if args == ():
27
+ args = self._map[1]
28
+ map = self.load_values(*args, store_as=None)
29
+ if value in map['dna'].keys():
30
+ return True
31
+ return False
32
+
33
+
34
+ def CheckResidue(self, value: str, *args) -> bool:
35
+ if args == ():
36
+ args = self._map[1]
37
+ if self.CheckAA(value, *args):
38
+ return True
39
+ if self.CheckDNA(value, *args):
40
+ return True
41
+ if self.CheckRNA(value, *args):
42
+ return True
43
+ return False
44
+
45
+
46
+ # This method determines if something is DNA, RNA, or an amino acid.
47
+ # Don't be cheeky with this. If you aren't following the IUPAC naming schemes,
48
+ # you're gonna have a bad time.
49
+ #
50
+ # RNA has exclusively 1-letter codes: A, C, T, G, etc.
51
+ # DNA has exclusively 2-letter codes: DA, DC, DT, DG, etc.
52
+ # Amino acids have exclusively 3-letter codes
53
+ def ExtrapolateResidueType(self, value: str) -> object:
54
+ if self.CheckRNA(value):
55
+ return rna
56
+ if self.CheckDNA(value):
57
+ return dna
58
+ if self.CheckAA(value):
59
+ return amino_acid
60
+ return Residue
@@ -0,0 +1,2 @@
1
+ # from ._cache import _cache_handler
2
+ from .cachemanager import CacheManager