RNApolis 0.10.5__py3-none-any.whl → 0.10.7__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.
rnapolis/adapter.py CHANGED
@@ -8,7 +8,7 @@ from collections import defaultdict
8
8
  from dataclasses import dataclass
9
9
  from enum import Enum
10
10
  from tempfile import NamedTemporaryFile
11
- from typing import DefaultDict, Dict, List, Optional, Set, Tuple
11
+ from typing import Any, DefaultDict, Dict, List, Optional, Set, Tuple, Union
12
12
 
13
13
  import orjson
14
14
 
@@ -49,6 +49,7 @@ class ExternalTool(Enum):
49
49
  BPNET = "bpnet"
50
50
  MAXIT = "maxit"
51
51
  BARNABA = "barnaba"
52
+ MCANNOTATE = "mc-annotate"
52
53
 
53
54
 
54
55
  logging.basicConfig(level=os.getenv("LOGLEVEL", "INFO").upper())
@@ -68,25 +69,30 @@ def auto_detect_tool(external_files: List[str]) -> ExternalTool:
68
69
  return ExternalTool.MAXIT
69
70
 
70
71
  for file_path in external_files:
72
+ basename = os.path.basename(file_path)
73
+
71
74
  # Check for FR3D pattern
72
- if file_path.endswith("basepair_detail.txt"):
75
+ if basename.endswith("basepair_detail.txt"):
73
76
  return ExternalTool.FR3D
74
77
 
75
78
  # Check for RNAView pattern
76
- if file_path.endswith(".out"):
79
+ if basename.endswith(".out"):
77
80
  return ExternalTool.RNAVIEW
78
81
 
79
82
  # Check for BPNet pattern
80
- if file_path.endswith("basepair.json"):
83
+ if basename.endswith("basepair.json"):
81
84
  return ExternalTool.BPNET
82
85
 
86
+ # Check for MC-Annotate pattern
87
+ if basename.endswith("stdout.txt"):
88
+ return ExternalTool.MCANNOTATE
89
+
83
90
  # Check for Barnaba pattern
84
- basename = os.path.basename(file_path)
85
91
  if "pairing" in basename or "stacking" in basename:
86
92
  return ExternalTool.BARNABA
87
93
 
88
94
  # Check for JSON files (DSSR)
89
- if file_path.endswith(".json"):
95
+ if basename.endswith(".json"):
90
96
  return ExternalTool.DSSR
91
97
 
92
98
  # Default to MAXIT if no patterns match
@@ -1157,6 +1163,346 @@ def parse_barnaba_output(
1157
1163
  )
1158
1164
 
1159
1165
 
1166
+ class MCAnnotateAdapter:
1167
+ # Represents state of parsing MC-Annotate result
1168
+ # Luckily every important part of file
1169
+ # begins with a unique sentence
1170
+ class ParseState(str, Enum):
1171
+ RESIDUES_INFORMATION = "Residue conformations"
1172
+ ADJACENT_STACKINGS = "Adjacent stackings"
1173
+ NON_ADJACENT_STACKINGS = "Non-Adjacent stackings"
1174
+ BASE_PAIRS_SECTION = "Base-pairs"
1175
+ SUMMARY_SECTION = "Number of"
1176
+
1177
+ # This dictionary maps our model edges
1178
+ # to edge representation used by MC-Annotate
1179
+ EDGES: Dict[str, Tuple[str, ...]] = {
1180
+ "H": ("Hh", "Hw", "Bh", "C8"),
1181
+ "W": ("Wh", "Ww", "Ws"),
1182
+ "S": ("Ss", "Sw", "Bs"),
1183
+ }
1184
+
1185
+ # Contains flatten EDGES values (in one touple)
1186
+ ALL_EDGES = sum(EDGES.values(), ())
1187
+
1188
+ # Based on these tokens
1189
+ # BaseRiboseInteractions and BasePhosphateInteractions are created
1190
+ RIBOSE_ATOM = "O2'"
1191
+ PHOSPHATE_ATOM = "O2P"
1192
+
1193
+ # Single hydrogen bond - for us it's OtherInteraction
1194
+ ONE_HBOND = "one_hbond"
1195
+
1196
+ # Cis/trans tokens used by MC-Annotate
1197
+ CIS = "cis"
1198
+ TRANS = "trans"
1199
+
1200
+ # Tokens used in PDB files
1201
+ ATOM = "ATOM"
1202
+ HETATM = "HETATM"
1203
+
1204
+ # This regex is used to capture 6 groups of residues information:
1205
+ # (1) (2) (3) (4) (5) (6)
1206
+ # 1, 4 - chain IDs
1207
+ # 2, 5 - numbers
1208
+ # 3, 6 - icodes (or empty string if no icode)
1209
+ # Example - match and groups:
1210
+ # A-100.X-B200
1211
+ # ('A'), ('-100'), ('X'), ('B'), ('200'), ('')
1212
+ RESIDUE_REGEX = re.compile(
1213
+ r"'?(.)'?(-?[0-9]+)\.?([a-zA-Z]?)-'?(.)'?(-?[0-9]+)\.?([a-zA-Z]?)"
1214
+ )
1215
+
1216
+ # Roman numerals used by Saenger
1217
+ # both in our model and MC-Annotate
1218
+ ROMAN_NUMERALS = ("I", "V", "X")
1219
+
1220
+ # Positions of residues info in PDB files
1221
+ CHAIN_INDEX = 21
1222
+ NUMBER_INDEX = slice(22, 26)
1223
+ ICODE_INDEX = 26
1224
+ NAME_INDEX = slice(17, 20)
1225
+
1226
+ def __init__(self) -> None:
1227
+ # Since names are not present in adjacent and non-adjacent stackings
1228
+ # we need save these values eariler
1229
+ self.names: Dict[str, str] = {}
1230
+ self.base_pairs: List[BasePair] = []
1231
+ self.stackings: List[Stacking] = []
1232
+ self.base_ribose_interactions: List[BaseRibose] = []
1233
+ self.base_phosphate_interactions: List[BasePhosphate] = []
1234
+ self.other_interactions: List[OtherInteraction] = []
1235
+
1236
+ def classify_edge(self, edge_type: str) -> Optional[str]:
1237
+ for edge, edges in self.EDGES.items():
1238
+ if edge_type in edges:
1239
+ return edge
1240
+ logging.warning('Edge type "{type}" unknown')
1241
+ return None
1242
+
1243
+ def get_residue(self, residue_info_list: Tuple[Union[str, Any], ...]) -> Residue:
1244
+ chain = residue_info_list[0]
1245
+ number = int(residue_info_list[1])
1246
+
1247
+ if residue_info_list[2] == "":
1248
+ icode = None
1249
+ residue_info = f"{chain}{number}"
1250
+ else:
1251
+ icode = residue_info_list[2]
1252
+ residue_info = f"{chain}{number}.{icode}"
1253
+
1254
+ return Residue(
1255
+ None, ResidueAuth(chain, number, icode, self.names[residue_info])
1256
+ )
1257
+
1258
+ def get_residues(
1259
+ self, residues_info: str
1260
+ ) -> Tuple[Optional[Residue], Optional[Residue]]:
1261
+ regex_result = re.search(self.RESIDUE_REGEX, residues_info)
1262
+ if regex_result is None:
1263
+ logging.error("MC-Annotate regex failed: {residues_info}")
1264
+ return None, None
1265
+ residues_info_list = regex_result.groups()
1266
+ # Expects (chain1, number1, icode1, chain2, number2, icode2)
1267
+ if len(residues_info_list) != 6:
1268
+ logging.error(f"MC-Annotate regex failed for {residues_info}")
1269
+ return None, None
1270
+ residue_left = self.get_residue(residues_info_list[:3])
1271
+ residue_right = self.get_residue(residues_info_list[3:])
1272
+ return residue_left, residue_right
1273
+
1274
+ def append_stacking(self, line: str, topology_position: int) -> None:
1275
+ splitted_line = line.split()
1276
+ topology_info = splitted_line[topology_position]
1277
+ residue_left, residue_right = self.get_residues(splitted_line[0])
1278
+ if residue_left is None or residue_right is None:
1279
+ logging.warning(f"Could not parse residues in line: {line}")
1280
+ return
1281
+ stacking = Stacking(
1282
+ residue_left, residue_right, StackingTopology[topology_info]
1283
+ )
1284
+ self.stackings.append(stacking)
1285
+
1286
+ def get_ribose_interaction(
1287
+ self, residues: Tuple[Residue, Residue], token: str
1288
+ ) -> BaseRibose:
1289
+ # BasePair is preffered first so swap if necessary
1290
+ if token.split("/", 1)[0] == self.RIBOSE_ATOM:
1291
+ residue_left, residue_right = residues[1], residues[0]
1292
+ else:
1293
+ residue_left, residue_right = residues[0], residues[1]
1294
+ return BaseRibose(residue_left, residue_right, None)
1295
+
1296
+ def get_phosphate_interaction(
1297
+ self, residues: Tuple[Residue, Residue], token: str
1298
+ ) -> BasePhosphate:
1299
+ # BasePair is preffered first so swap if necessary
1300
+ if token.split("/", 1)[0] == self.PHOSPHATE_ATOM:
1301
+ residue_left, residue_right = residues[1], residues[0]
1302
+ else:
1303
+ residue_left, residue_right = residues[0], residues[1]
1304
+ return BasePhosphate(residue_left, residue_right, None)
1305
+
1306
+ def get_base_interaction(
1307
+ self,
1308
+ residues: Tuple[Residue, Residue],
1309
+ token: str,
1310
+ tokens: List[str],
1311
+ ) -> Optional[BasePair]:
1312
+ if self.CIS in tokens:
1313
+ cis_trans = "c"
1314
+ elif self.TRANS in tokens:
1315
+ cis_trans = "t"
1316
+ else:
1317
+ logging.warning(f"Cis/trans expected, but not present in {tokens}")
1318
+ return None
1319
+
1320
+ # example saenger: XIX or XII,XIII (?)
1321
+ for potential_saenger_token in tokens:
1322
+ potential_saenger_without_comma = potential_saenger_token.split(",")[0]
1323
+ if all(
1324
+ char in self.ROMAN_NUMERALS for char in potential_saenger_without_comma
1325
+ ):
1326
+ saenger = Saenger[potential_saenger_without_comma]
1327
+ break
1328
+ else:
1329
+ saenger = None
1330
+
1331
+ left_edge, right_edge = token.split("/", 1)
1332
+ leontis_westhof_left = self.classify_edge(left_edge)
1333
+ leontis_westohf_right = self.classify_edge(right_edge)
1334
+
1335
+ if leontis_westhof_left is None or leontis_westohf_right is None:
1336
+ return None
1337
+
1338
+ leontis_westhof = LeontisWesthof[
1339
+ f"{cis_trans}{leontis_westhof_left}{leontis_westohf_right}"
1340
+ ]
1341
+ residue_left, residue_right = residues
1342
+ return BasePair(residue_left, residue_right, leontis_westhof, saenger)
1343
+
1344
+ def get_other_interaction(
1345
+ self, residues: Tuple[Residue, Residue]
1346
+ ) -> OtherInteraction:
1347
+ return OtherInteraction(residues[0], residues[1])
1348
+
1349
+ def append_interactions(self, line: str) -> None:
1350
+ splitted_line = line.split()
1351
+ residues = self.get_residues(splitted_line[0])
1352
+ if residues[0] is None or residues[1] is None:
1353
+ logging.warning(f"Could not parse residues in line: {line}")
1354
+ return
1355
+ # Assumes that one pair can belong to every interaction type
1356
+ # no more than once!
1357
+ base_added, ribose_added, phosphate_added = False, False, False
1358
+ # example tokens: Ww/Ww pairing antiparallel cis XX
1359
+ tokens: List[str] = splitted_line[3:]
1360
+
1361
+ # Special case
1362
+ # IF single hydrogen bond and base pairs only THEN
1363
+ # append to OtherIneraction list
1364
+ if self.ONE_HBOND in tokens:
1365
+ for token in tokens:
1366
+ if self.RIBOSE_ATOM in token or self.PHOSPHATE_ATOM in token:
1367
+ break
1368
+ else:
1369
+ other_interaction = self.get_other_interaction(residues)
1370
+ self.other_interactions.append(other_interaction)
1371
+ return
1372
+
1373
+ for token in tokens:
1374
+ if self.RIBOSE_ATOM in token and not ribose_added:
1375
+ # example token: Ss/O2'
1376
+ ribose_interaction = self.get_ribose_interaction(residues, token)
1377
+ self.base_ribose_interactions.append(ribose_interaction)
1378
+ ribose_added = True
1379
+
1380
+ elif self.PHOSPHATE_ATOM in token and not phosphate_added:
1381
+ # example token: O2P/Bh
1382
+ phosphate_interaction = self.get_phosphate_interaction(residues, token)
1383
+ self.base_phosphate_interactions.append(phosphate_interaction)
1384
+ phosphate_added = True
1385
+
1386
+ elif len(token.split("/", 1)) > 1:
1387
+ token_left, token_right = token.split("/", 1)
1388
+ tokens_in_edges = (
1389
+ token_left in self.ALL_EDGES and token_right in self.ALL_EDGES
1390
+ )
1391
+ if tokens_in_edges and not base_added:
1392
+ # example token_left: Ww | example token_right: Ws
1393
+ base_pair_interaction = self.get_base_interaction(
1394
+ residues, token, tokens
1395
+ )
1396
+ if base_pair_interaction is not None:
1397
+ self.base_pairs.append(base_pair_interaction)
1398
+ base_added = True
1399
+
1400
+ def append_names(self, file_content: str) -> None:
1401
+ for line in file_content.splitlines():
1402
+ if line.startswith(self.ATOM) or line.startswith(self.HETATM):
1403
+ chain = line[self.CHAIN_INDEX].strip()
1404
+ number = line[self.NUMBER_INDEX].strip()
1405
+ icode = line[self.ICODE_INDEX].strip()
1406
+ name = line[self.NAME_INDEX].strip()
1407
+ residue_info = (
1408
+ f"{chain}{number}" if icode == "" else f"{chain}{number}.{icode}"
1409
+ )
1410
+ self.names[residue_info] = name
1411
+
1412
+ def analyze_by_mc_annotate(
1413
+ self, pdb_content: str, mc_result: str, **_: Dict[str, Any]
1414
+ ) -> BaseInteractions:
1415
+ self.append_names(pdb_content)
1416
+ current_state = None
1417
+
1418
+ for line in mc_result.splitlines():
1419
+ for state in self.ParseState:
1420
+ if line.startswith(state.value):
1421
+ current_state = state
1422
+ break
1423
+ # Loop ended without break - parse file
1424
+ else:
1425
+ if current_state == self.ParseState.RESIDUES_INFORMATION:
1426
+ # example line: X7.H : G C3p_endo anti
1427
+ # Skip residues information - meaningless information
1428
+ pass
1429
+ elif current_state == self.ParseState.ADJACENT_STACKINGS:
1430
+ # example line: X4.E-X5.F : adjacent_5p upward
1431
+ self.append_stacking(line, 3)
1432
+ elif current_state == self.ParseState.NON_ADJACENT_STACKINGS:
1433
+ # example line: Y40.M-Y67.N : inward pairing
1434
+ self.append_stacking(line, 2)
1435
+ elif current_state == self.ParseState.BASE_PAIRS_SECTION:
1436
+ # example line: Y38.K-Y51.X : A-U Ww/Ww pairing antiparallel cis XX
1437
+ self.append_interactions(line)
1438
+ elif current_state == self.ParseState.SUMMARY_SECTION:
1439
+ # example line: Number of non adjacent stackings = 26
1440
+ # Skip summary section - meaningless information
1441
+ pass
1442
+
1443
+ return (
1444
+ self.base_pairs,
1445
+ self.stackings,
1446
+ self.base_ribose_interactions,
1447
+ self.base_phosphate_interactions,
1448
+ self.other_interactions,
1449
+ )
1450
+
1451
+
1452
+ def parse_mcannotate_output(
1453
+ file_paths: List[str], structure3d: Structure3D
1454
+ ) -> BaseInteractions:
1455
+ """
1456
+ Parse mc-annotate output and convert to BaseInteractions.
1457
+ This function expects a file with mc-annotate stdout and a PDB file.
1458
+ """
1459
+ stdout_file = None
1460
+ structure_file = None
1461
+ for file_path in file_paths:
1462
+ if os.path.basename(file_path).endswith("stdout.txt"):
1463
+ stdout_file = file_path
1464
+ elif file_path.endswith(".pdb"):
1465
+ structure_file = file_path
1466
+
1467
+ if not stdout_file:
1468
+ logging.warning("No stdout.txt file found for mc-annotate.")
1469
+ return BaseInteractions([], [], [], [], [])
1470
+
1471
+ if not structure_file:
1472
+ logging.warning("No PDB file found for mc-annotate.")
1473
+ return BaseInteractions([], [], [], [], [])
1474
+
1475
+ logging.info(f"Processing mc-annotate stdout file: {stdout_file}")
1476
+ logging.info(f"Using structure file for residue names: {structure_file}")
1477
+
1478
+ try:
1479
+ with open(stdout_file, "r") as f:
1480
+ mc_result = f.read()
1481
+ with open(structure_file, "r") as f:
1482
+ pdb_content = f.read()
1483
+ except Exception as e:
1484
+ logging.warning(f"Could not read input files for mc-annotate: {e}")
1485
+ return BaseInteractions([], [], [], [], [])
1486
+
1487
+ adapter = MCAnnotateAdapter()
1488
+ (
1489
+ base_pairs,
1490
+ stackings,
1491
+ base_ribose_interactions,
1492
+ base_phosphate_interactions,
1493
+ other_interactions,
1494
+ ) = adapter.analyze_by_mc_annotate(pdb_content, mc_result)
1495
+
1496
+ return BaseInteractions.from_structure3d(
1497
+ structure3d,
1498
+ base_pairs,
1499
+ stackings,
1500
+ base_ribose_interactions,
1501
+ base_phosphate_interactions,
1502
+ other_interactions,
1503
+ )
1504
+
1505
+
1160
1506
  def parse_external_output(
1161
1507
  file_paths: List[str], tool: ExternalTool, structure3d: Structure3D
1162
1508
  ) -> BaseInteractions:
@@ -1183,6 +1529,8 @@ def parse_external_output(
1183
1529
  return parse_rnaview_output(file_paths, structure3d)
1184
1530
  elif tool == ExternalTool.BARNABA:
1185
1531
  return parse_barnaba_output(file_paths, structure3d)
1532
+ elif tool == ExternalTool.MCANNOTATE:
1533
+ return parse_mcannotate_output(file_paths, structure3d)
1186
1534
  else:
1187
1535
  raise ValueError(f"Unsupported external tool: {tool}")
1188
1536
 
@@ -1259,6 +1607,9 @@ def process_external_tool_output(
1259
1607
  if not external_file_paths:
1260
1608
  # For MAXIT or when no external files are provided, use the input file
1261
1609
  file_paths_to_process = [input_file_path]
1610
+ elif tool == ExternalTool.MCANNOTATE:
1611
+ # MC-Annotate requires both the stdout and the PDB file
1612
+ file_paths_to_process = external_file_paths + [input_file_path]
1262
1613
  else:
1263
1614
  # Process all external files
1264
1615
  file_paths_to_process = external_file_paths
rnapolis/common.py CHANGED
@@ -1084,33 +1084,41 @@ class BaseInteractions:
1084
1084
  base_phosphate_interactions: List[BasePhosphate],
1085
1085
  other_interactions: List[OtherInteraction],
1086
1086
  ) -> "BaseInteractions":
1087
- auth2residue3d = {}
1088
- auth2label = {}
1089
- label2auth = {}
1087
+ cni2residue = {}
1088
+ cni2label = {}
1089
+ cni2auth = {}
1090
1090
 
1091
1091
  for residue3d in structure3d.residues:
1092
- auth2residue3d[residue3d.auth] = residue3d
1093
- auth2label[residue3d.auth] = residue3d.label
1094
- label2auth[residue3d.label] = residue3d.auth
1092
+ cni = (residue3d.chain, residue3d.number, residue3d.icode or None)
1093
+ cni2auth[cni] = residue3d.auth
1094
+ cni2label[cni] = residue3d.label
1095
+ cni2residue[cni] = residue3d
1095
1096
 
1096
1097
  def unify_nt(nt: Residue) -> Residue:
1097
1098
  if nt.auth is not None and nt.label is not None:
1098
1099
  return nt
1100
+ cni = (nt.chain, nt.number, nt.icode or None)
1099
1101
  if nt.auth is not None:
1100
- return Residue(label=auth2label.get(nt.auth, None), auth=nt.auth)
1102
+ return Residue(label=cni2label.get(cni, None), auth=nt.auth)
1101
1103
  if nt.label is not None:
1102
- return Residue(label=nt.label, auth=label2auth.get(nt.label, None))
1104
+ return Residue(label=nt.label, auth=cni2auth.get(cni, None))
1103
1105
  return nt
1104
1106
 
1105
1107
  base_pairs_new = []
1106
1108
  for base_pair in base_pairs:
1107
1109
  nt1 = unify_nt(base_pair.nt1)
1108
1110
  nt2 = unify_nt(base_pair.nt2)
1109
- saenger = base_pair.saenger or Saenger.from_leontis_westhof(
1110
- auth2residue3d[nt1.auth].one_letter_name,
1111
- auth2residue3d[nt2.auth].one_letter_name,
1112
- base_pair.lw,
1113
- )
1111
+
1112
+ cni1 = (nt1.chain, nt1.number, nt1.icode or None)
1113
+ cni2 = (nt2.chain, nt2.number, nt2.icode or None)
1114
+ if cni1 not in cni2residue or cni2 not in cni2residue:
1115
+ saenger = base_pair.saenger
1116
+ else:
1117
+ saenger = base_pair.saenger or Saenger.from_leontis_westhof(
1118
+ cni2residue[cni1].one_letter_name,
1119
+ cni2residue[cni2].one_letter_name,
1120
+ base_pair.lw,
1121
+ )
1114
1122
  if (
1115
1123
  nt1 != base_pair.nt1
1116
1124
  or nt2 != base_pair.nt2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RNApolis
3
- Version: 0.10.5
3
+ Version: 0.10.7
4
4
  Summary: A Python library containing RNA-related bioinformatics functions and classes
5
5
  Home-page: https://github.com/tzok/rnapolis-py
6
6
  Author: Tomasz Zok
@@ -1,8 +1,8 @@
1
- rnapolis/adapter.py,sha256=0Awt6owSeGcWq8kgLtkDv19suhrmcJgQip_Hi4Y3tK4,48513
1
+ rnapolis/adapter.py,sha256=6hJTweIqUXH8CEGvi8oupFzk5etkIt8Q2bqRvgsqako,62169
2
2
  rnapolis/aligner.py,sha256=o7rQyjAZ3n4VXcnSPY3HVB8nLNRkVbl552O3NVh0mfg,3429
3
3
  rnapolis/annotator.py,sha256=HA2hfEUXdmBElObqRlASAB1FgkysjiHgwMTjEhsDiDE,30277
4
4
  rnapolis/clashfinder.py,sha256=AC9_tIx7QIk57sELq_aKfU1u3UMOXbgcccQeGHhMR6c,8517
5
- rnapolis/common.py,sha256=hamlW892ZF5A0dSWsl7cOCZqOpbVQMgXjVPYDFzk3pE,36347
5
+ rnapolis/common.py,sha256=qifqTIiq43jeR1xKK3301PbRMo7vaZgjQauG-G7asSc,36686
6
6
  rnapolis/component_A.csv,sha256=koirS-AwUZwoYGItT8yn3wS6Idvmh2FANfTQcOS_xh8,2897
7
7
  rnapolis/component_C.csv,sha256=NtvsAu_YrUgTjzZm3j4poW4IZ99x3dPARB09XVIiMCc,2803
8
8
  rnapolis/component_G.csv,sha256=Z5wl8OnHRyx4XhTyBiWgRZiEvmZXhoxtVRH8bn6Vxf0,2898
@@ -22,9 +22,9 @@ rnapolis/tertiary_v2.py,sha256=SgijTv0bPqMJwsMqyQk0O8QAnS2Ozk45vk8igxt9hRs,38001
22
22
  rnapolis/transformer.py,sha256=aC0nBmHHJf5TyLvBIV57Jj3tlwpvHbPo347opfAOlQA,3844
23
23
  rnapolis/unifier.py,sha256=2ge7IB9FdRgzSAiVD39U_ciwtdDJ2fGzf8mUIudbrqY,5820
24
24
  rnapolis/util.py,sha256=IdquFO3PV1_KDqodjupzm0Rqvgy0CeSzxGHaGEHYXVU,543
25
- rnapolis-0.10.5.dist-info/licenses/LICENSE,sha256=ZGRu12MzCgbYA-Lt8MyBlmjvPZh7xfiD5u5wBx0enq4,1066
26
- rnapolis-0.10.5.dist-info/METADATA,sha256=LbY2r44uwiIoVASkHv1_TAwwKIOCi8Az33PDuTs7gdg,54611
27
- rnapolis-0.10.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- rnapolis-0.10.5.dist-info/entry_points.txt,sha256=MZMWnYBUYnis-zWDmFfuA5yXtU3W5YdQrm5HA5LrkeM,474
29
- rnapolis-0.10.5.dist-info/top_level.txt,sha256=LcO18koxZcWoJ21KDRRRo_tyIbmXL5z61dPitZpy8yc,9
30
- rnapolis-0.10.5.dist-info/RECORD,,
25
+ rnapolis-0.10.7.dist-info/licenses/LICENSE,sha256=ZGRu12MzCgbYA-Lt8MyBlmjvPZh7xfiD5u5wBx0enq4,1066
26
+ rnapolis-0.10.7.dist-info/METADATA,sha256=QPuGPZ96VIjvQPiLkk4bS4vstWqO6cok6e4vID33vg0,54611
27
+ rnapolis-0.10.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ rnapolis-0.10.7.dist-info/entry_points.txt,sha256=MZMWnYBUYnis-zWDmFfuA5yXtU3W5YdQrm5HA5LrkeM,474
29
+ rnapolis-0.10.7.dist-info/top_level.txt,sha256=LcO18koxZcWoJ21KDRRRo_tyIbmXL5z61dPitZpy8yc,9
30
+ rnapolis-0.10.7.dist-info/RECORD,,