RNApolis 0.3.17__tar.gz → 0.4.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. {rnapolis-0.3.17/src/RNApolis.egg-info → rnapolis-0.4.0}/PKG-INFO +2 -1
  2. {rnapolis-0.3.17 → rnapolis-0.4.0}/setup.py +2 -1
  3. {rnapolis-0.3.17 → rnapolis-0.4.0/src/RNApolis.egg-info}/PKG-INFO +2 -1
  4. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/RNApolis.egg-info/requires.txt +1 -0
  5. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/annotator.py +3 -4
  6. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/clashfinder.py +1 -2
  7. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/common.py +0 -1
  8. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/metareader.py +0 -1
  9. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/molecule_filter.py +16 -4
  10. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/parser.py +82 -33
  11. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/rfam_folder.py +0 -1
  12. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/tertiary.py +28 -5
  13. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_annotator.py +1 -3
  14. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_bugfixes.py +13 -2
  15. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_common.py +1 -2
  16. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_rfam_folder.py +0 -1
  17. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_tertiary.py +4 -4
  18. {rnapolis-0.3.17 → rnapolis-0.4.0}/LICENSE +0 -0
  19. {rnapolis-0.3.17 → rnapolis-0.4.0}/README.md +0 -0
  20. {rnapolis-0.3.17 → rnapolis-0.4.0}/pyproject.toml +0 -0
  21. {rnapolis-0.3.17 → rnapolis-0.4.0}/setup.cfg +0 -0
  22. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/RNApolis.egg-info/SOURCES.txt +0 -0
  23. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/RNApolis.egg-info/dependency_links.txt +0 -0
  24. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/RNApolis.egg-info/entry_points.txt +0 -0
  25. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/RNApolis.egg-info/top_level.txt +0 -0
  26. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/motif_extractor.py +0 -0
  27. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/transformer.py +0 -0
  28. {rnapolis-0.3.17 → rnapolis-0.4.0}/src/rnapolis/util.py +0 -0
  29. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_metareader.py +0 -0
  30. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_parser.py +0 -0
  31. {rnapolis-0.3.17 → rnapolis-0.4.0}/tests/test_quadruplexes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RNApolis
3
- Version: 0.3.17
3
+ Version: 0.4.0
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
@@ -21,6 +21,7 @@ Requires-Dist: mmcif
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: ordered-set
23
23
  Requires-Dist: orjson
24
+ Requires-Dist: pandas
24
25
  Requires-Dist: pulp
25
26
  Requires-Dist: requests
26
27
  Requires-Dist: scipy
@@ -5,7 +5,7 @@ with open("README.md") as f:
5
5
 
6
6
  setup(
7
7
  name="RNApolis",
8
- version="0.3.17",
8
+ version="0.4.0",
9
9
  packages=["rnapolis"],
10
10
  package_dir={"": "src"},
11
11
  author="Tomasz Zok",
@@ -42,6 +42,7 @@ setup(
42
42
  "numpy",
43
43
  "ordered-set",
44
44
  "orjson",
45
+ "pandas",
45
46
  "pulp",
46
47
  "requests",
47
48
  "scipy",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RNApolis
3
- Version: 0.3.17
3
+ Version: 0.4.0
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
@@ -21,6 +21,7 @@ Requires-Dist: mmcif
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: ordered-set
23
23
  Requires-Dist: orjson
24
+ Requires-Dist: pandas
24
25
  Requires-Dist: pulp
25
26
  Requires-Dist: requests
26
27
  Requires-Dist: scipy
@@ -4,6 +4,7 @@ mmcif
4
4
  numpy
5
5
  ordered-set
6
6
  orjson
7
+ pandas
7
8
  pulp
8
9
  requests
9
10
  scipy
@@ -10,8 +10,6 @@ from typing import Dict, List, Optional, Set, Tuple
10
10
  import numpy
11
11
  import numpy.typing
12
12
  from ordered_set import OrderedSet
13
- from scipy.spatial import KDTree
14
-
15
13
  from rnapolis.common import (
16
14
  BR,
17
15
  BaseInteractions,
@@ -43,6 +41,7 @@ from rnapolis.tertiary import (
43
41
  torsion_angle,
44
42
  )
45
43
  from rnapolis.util import handle_input_file
44
+ from scipy.spatial import KDTree
46
45
 
47
46
  HYDROGEN_BOND_MAX_DISTANCE = 4.0
48
47
  HYDROGEN_BOND_ANGLE_RANGE = (50.0, 130.0) # 90 degrees is ideal, so allow +- 40 degrees
@@ -149,7 +148,7 @@ def detect_bph_br_classification(
149
148
 
150
149
 
151
150
  def merge_and_clean_bph_br(
152
- pairs: List[Tuple[Residue3D, Residue3D, int]]
151
+ pairs: List[Tuple[Residue3D, Residue3D, int]],
153
152
  ) -> Dict[Tuple[Residue3D, Residue3D], OrderedSet[int]]:
154
153
  bph_br_map: Dict[Tuple[Residue3D, Residue3D], OrderedSet[int]] = defaultdict(
155
154
  OrderedSet
@@ -588,7 +587,7 @@ def main():
588
587
  "-a",
589
588
  "--all-dot-brackets",
590
589
  action="store_true",
591
- help=")optional) print all dot-brackets, not only optimal one (exclusive with -e/--extended)",
590
+ help="(optional) print all dot-brackets, not only optimal one (exclusive with -e/--extended)",
592
591
  )
593
592
  parser.add_argument("-b", "--bpseq", help="(optional) path to output BPSEQ file")
594
593
  parser.add_argument("-c", "--csv", help="(optional) path to output CSV file")
@@ -8,11 +8,10 @@ from functools import cached_property
8
8
  from typing import List, Optional
9
9
 
10
10
  import numpy as np
11
- from scipy.spatial import KDTree
12
-
13
11
  from rnapolis.metareader import read_metadata
14
12
  from rnapolis.parser import read_3d_structure
15
13
  from rnapolis.tertiary import Atom, Residue3D
14
+ from scipy.spatial import KDTree
16
15
 
17
16
  CARBON_RADIUS = 0.6
18
17
  NITROGEN_RADIUS = 0.54
@@ -8,7 +8,6 @@ from collections.abc import Sequence
8
8
  from dataclasses import dataclass
9
9
  from enum import Enum
10
10
  from functools import cache, cached_property, total_ordering
11
- from itertools import permutations
12
11
  from typing import Dict, List, Optional, Tuple
13
12
 
14
13
  import graphviz
@@ -6,7 +6,6 @@ import orjson
6
6
  import pandas as pd
7
7
  from mmcif.io.IoAdapterPy import IoAdapterPy
8
8
  from mmcif.io.PdbxReader import DataContainer
9
-
10
9
  from rnapolis.util import handle_input_file
11
10
 
12
11
 
@@ -5,7 +5,6 @@ from typing import List, Set, Tuple
5
5
 
6
6
  from mmcif.io.IoAdapterPy import IoAdapterPy
7
7
  from mmcif.io.PdbxReader import DataCategory, DataContainer
8
-
9
8
  from rnapolis.util import handle_input_file
10
9
 
11
10
  # Source: https://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Items/_entity_poly.type.html
@@ -158,11 +157,17 @@ def main():
158
157
  parser = argparse.ArgumentParser()
159
158
  parser.add_argument(
160
159
  "--type",
161
- help="a type of molecule to select (default: polyribonucleotide)",
160
+ help="a type of molecule to select, you can provide this argument multiple times (default: polyribonucleotide)",
162
161
  action="append",
163
162
  default=["polyribonucleotide"],
164
163
  choices=ENTITY_POLY_TYPES,
165
164
  )
165
+ parser.add_argument(
166
+ "--chain",
167
+ help="a chain ID (label_asym_id) to select, you can provide this argument multiple times (if provided, it overrides the --type argument)",
168
+ action="append",
169
+ default=[],
170
+ )
166
171
  parser.add_argument("path", help="path to a PDBx/mmCIF file")
167
172
  args = parser.parse_args()
168
173
 
@@ -171,8 +176,15 @@ def main():
171
176
  data = adapter.readFile(file.name)
172
177
  output = DataContainer("rnapolis")
173
178
 
174
- entity_ids = select_ids(data, "entity_poly", "type", "entity_id", set(args.type))
175
- asym_ids = select_ids(data, "struct_asym", "entity_id", "id", entity_ids)
179
+ if args.chain:
180
+ entity_ids = select_ids(data, "struct_asym", "id", "entity_id", set(args.chain))
181
+ asym_ids = set(args.chain)
182
+ else:
183
+ entity_ids = select_ids(
184
+ data, "entity_poly", "type", "entity_id", set(args.type)
185
+ )
186
+ asym_ids = select_ids(data, "struct_asym", "entity_id", "id", entity_ids)
187
+
176
188
  auth_asym_ids = select_ids(
177
189
  data, "atom_site", "label_asym_id", "auth_asym_id", asym_ids
178
190
  )
@@ -2,7 +2,6 @@ import logging
2
2
  from typing import IO, Dict, List, Optional, Tuple, Union
3
3
 
4
4
  from mmcif.io.IoAdapterPy import IoAdapterPy
5
-
6
5
  from rnapolis.common import ResidueAuth, ResidueLabel
7
6
  from rnapolis.tertiary import BASE_ATOMS, Atom, Residue3D, Structure3D
8
7
 
@@ -13,7 +12,7 @@ logger = logging.getLogger(__name__)
13
12
  def read_3d_structure(
14
13
  cif_or_pdb: IO[str], model: Optional[int] = None, nucleic_acid_only: bool = False
15
14
  ) -> Structure3D:
16
- atoms, modified, sequence = (
15
+ atoms, modified, sequence_by_entity, is_nucleic_acid_by_entity = (
17
16
  parse_cif(cif_or_pdb) if is_cif(cif_or_pdb) else parse_pdb(cif_or_pdb)
18
17
  )
19
18
  available_models = {atom.model: None for atom in atoms}
@@ -25,7 +24,13 @@ def read_3d_structure(
25
24
  atoms = atoms_by_model[model]
26
25
  else:
27
26
  atoms = atoms_by_model[list(available_models.keys())[0]]
28
- return group_atoms(atoms, modified, sequence, nucleic_acid_only)
27
+ return group_atoms(
28
+ atoms,
29
+ modified,
30
+ sequence_by_entity,
31
+ is_nucleic_acid_by_entity,
32
+ nucleic_acid_only,
33
+ )
29
34
 
30
35
 
31
36
  def is_cif(cif_or_pdb: IO[str]) -> bool:
@@ -41,7 +46,8 @@ def parse_cif(
41
46
  ) -> Tuple[
42
47
  List[Atom],
43
48
  Dict[Union[ResidueLabel, ResidueAuth], str],
44
- Dict[Tuple[str, int], str],
49
+ Dict[str, str],
50
+ Dict[str, bool],
45
51
  ]:
46
52
  cif.seek(0)
47
53
 
@@ -49,7 +55,8 @@ def parse_cif(
49
55
  data = io_adapter.readFile(cif.name)
50
56
  atoms: List[Atom] = []
51
57
  modified: Dict[Union[ResidueLabel, ResidueAuth], str] = {}
52
- sequence = {}
58
+ sequence_by_entity = {}
59
+ is_nucleic_acid_by_entity = {}
53
60
 
54
61
  if data:
55
62
  atom_site = data[0].getObj("atom_site")
@@ -60,6 +67,7 @@ def parse_cif(
60
67
  for row in atom_site.getRowList():
61
68
  row_dict = dict(zip(atom_site.getAttributeList(), row))
62
69
 
70
+ label_entity_id = row_dict.get("label_entity_id", None)
63
71
  label_chain_name = row_dict.get("label_asym_id", None)
64
72
  label_residue_number = try_parse_int(row_dict.get("label_seq_id", None))
65
73
  label_residue_name = row_dict.get("label_comp_id", None)
@@ -128,7 +136,19 @@ def parse_cif(
128
136
  else None
129
137
  )
130
138
 
131
- atoms.append(Atom(label, auth, model, atom_name, x, y, z, occupancy))
139
+ atoms.append(
140
+ Atom(
141
+ label_entity_id,
142
+ label,
143
+ auth,
144
+ model,
145
+ atom_name,
146
+ x,
147
+ y,
148
+ z,
149
+ occupancy,
150
+ )
151
+ )
132
152
 
133
153
  if mod_residue:
134
154
  for row in mod_residue.getRowList():
@@ -179,17 +199,24 @@ def parse_cif(
179
199
  for row in entity_poly.getRowList():
180
200
  row_dict = dict(zip(entity_poly.getAttributeList(), row))
181
201
 
182
- pdbx_strand_id = row_dict.get("pdbx_strand_id", None)
202
+ entity_id = row_dict.get("entity_id", None)
203
+ type_ = row_dict.get("type", None)
183
204
  pdbx_seq_one_letter_code_can = row_dict.get(
184
205
  "pdbx_seq_one_letter_code_can", None
185
206
  )
186
207
 
187
- if pdbx_strand_id and pdbx_seq_one_letter_code_can:
188
- for strand in pdbx_strand_id.split(","):
189
- for i, letter in enumerate(pdbx_seq_one_letter_code_can):
190
- sequence[(strand, i + 1)] = letter
208
+ if entity_id and type_:
209
+ is_nucleic_acid_by_entity[entity_id] = type_ in (
210
+ "peptide nucleic acid",
211
+ "polydeoxyribonucleotide",
212
+ "polydeoxyribonucleotide/polyribonucleotide hybrid",
213
+ "polyribonucleotide",
214
+ )
215
+
216
+ if entity_id and pdbx_seq_one_letter_code_can:
217
+ sequence_by_entity[entity_id] = pdbx_seq_one_letter_code_can
191
218
 
192
- return atoms, modified, sequence
219
+ return atoms, modified, sequence_by_entity, is_nucleic_acid_by_entity
193
220
 
194
221
 
195
222
  def parse_pdb(
@@ -197,7 +224,8 @@ def parse_pdb(
197
224
  ) -> Tuple[
198
225
  List[Atom],
199
226
  Dict[Union[ResidueLabel, ResidueAuth], str],
200
- Dict[Tuple[str, int], str],
227
+ Dict[str, str],
228
+ Dict[str, bool],
201
229
  ]:
202
230
  pdb.seek(0)
203
231
  atoms: List[Atom] = []
@@ -223,7 +251,7 @@ def parse_pdb(
223
251
  auth = ResidueAuth(
224
252
  chain_identifier, residue_number, insertion_code, residue_name
225
253
  )
226
- atoms.append(Atom(None, auth, model, atom_name, x, y, z, occupancy))
254
+ atoms.append(Atom(None, None, auth, model, atom_name, x, y, z, occupancy))
227
255
  elif line.startswith("MODRES"):
228
256
  original_name = line[12:15]
229
257
  chain_identifier = line[16]
@@ -235,13 +263,14 @@ def parse_pdb(
235
263
  )
236
264
  modified[auth] = standard_residue_name
237
265
 
238
- return atoms, modified, {}
266
+ return atoms, modified, {}, {}
239
267
 
240
268
 
241
269
  def group_atoms(
242
270
  atoms: List[Atom],
243
271
  modified: Dict[Union[ResidueLabel, ResidueAuth], str],
244
- sequence: Dict[Tuple[str, int], str],
272
+ sequence_by_entity: Dict[str, str],
273
+ is_nucleic_acid_by_entity: Dict[str, bool],
245
274
  nucleic_acid_only: bool,
246
275
  ) -> Structure3D:
247
276
  if not atoms:
@@ -259,28 +288,45 @@ def group_atoms(
259
288
  label = key_previous[0]
260
289
  auth = key_previous[1]
261
290
  model = key_previous[2]
291
+ entity_id = residue_atoms[-1].entity_id
262
292
  name = get_residue_name(auth, label, modified)
263
- one_letter_name = get_one_letter_name(label, sequence, name)
264
- if one_letter_name not in "ACGUT":
293
+ one_letter_name = get_one_letter_name(
294
+ entity_id, label, sequence_by_entity, name
295
+ )
296
+
297
+ if one_letter_name not in "ACGUTN":
265
298
  one_letter_name = detect_one_letter_name(residue_atoms)
266
- residue = Residue3D(
267
- label, auth, model, one_letter_name, tuple(residue_atoms)
299
+
300
+ residues.append(
301
+ Residue3D(label, auth, model, one_letter_name, tuple(residue_atoms))
268
302
  )
269
- if not nucleic_acid_only or (nucleic_acid_only and residue.is_nucleotide):
270
- residues.append(residue)
303
+
271
304
  key_previous = key
272
305
  residue_atoms = [atom]
273
306
 
274
307
  label = key_previous[0]
275
308
  auth = key_previous[1]
276
309
  model = key_previous[2]
310
+ entity_id = residue_atoms[-1].entity_id
277
311
  name = get_residue_name(auth, label, modified)
278
- one_letter_name = get_one_letter_name(label, sequence, name)
279
- if one_letter_name not in "ACGUT":
312
+ one_letter_name = get_one_letter_name(entity_id, label, sequence_by_entity, name)
313
+
314
+ if one_letter_name not in "ACGUTN":
280
315
  one_letter_name = detect_one_letter_name(residue_atoms)
281
- residue = Residue3D(label, auth, model, one_letter_name, tuple(residue_atoms))
282
- if not nucleic_acid_only or (nucleic_acid_only and residue.is_nucleotide):
283
- residues.append(residue)
316
+
317
+ residues.append(
318
+ Residue3D(label, auth, model, one_letter_name, tuple(residue_atoms))
319
+ )
320
+
321
+ if nucleic_acid_only:
322
+ if is_nucleic_acid_by_entity:
323
+ residues = [
324
+ residue
325
+ for residue in residues
326
+ if is_nucleic_acid_by_entity[residue.atoms[0].entity_id]
327
+ ]
328
+ else:
329
+ residues = [residue for residue in residues if residue.is_nucleotide]
284
330
 
285
331
  return Structure3D(residues)
286
332
 
@@ -305,13 +351,14 @@ def get_residue_name(
305
351
 
306
352
 
307
353
  def get_one_letter_name(
308
- label: Optional[ResidueLabel], sequence: Dict[Tuple[str, int], str], name: str
354
+ entity_id: Optional[str],
355
+ label: Optional[ResidueLabel],
356
+ sequence_by_entity: Dict[str, str],
357
+ name: str,
309
358
  ) -> str:
310
359
  # try getting the value from _entity_poly first
311
- if label is not None:
312
- key = (label.chain, label.number)
313
- if key in sequence:
314
- return sequence[key]
360
+ if entity_id is not None and label is not None and entity_id in sequence_by_entity:
361
+ return sequence_by_entity[entity_id][label.number - 1]
315
362
  # RNA
316
363
  if len(name) == 1:
317
364
  return name
@@ -335,11 +382,13 @@ def detect_one_letter_name(atoms: List[Atom]) -> str:
335
382
  ) / len(atom_names_expected)
336
383
  score[candidate] = count
337
384
  items = sorted(score.items(), key=lambda kv: kv[1], reverse=True)
385
+ if items[0][1] == 0:
386
+ return "?"
338
387
  return items[0][0]
339
388
 
340
389
 
341
390
  def try_parse_int(s: str) -> Optional[int]:
342
391
  try:
343
392
  return int(s)
344
- except:
393
+ except ValueError:
345
394
  return None
@@ -14,7 +14,6 @@ from typing import List
14
14
  import appdirs
15
15
  import requests
16
16
  import RNA
17
-
18
17
  from rnapolis.common import BpSeq, DotBracket
19
18
 
20
19
  COMBINED_CM = "https://ftp.ebi.ac.uk/pub/databases/Rfam/CURRENT/Rfam.cm.gz"
@@ -7,7 +7,6 @@ from typing import Dict, List, Optional, Set, Tuple, Union
7
7
 
8
8
  import numpy
9
9
  import numpy.typing
10
-
11
10
  from rnapolis.common import (
12
11
  BasePair,
13
12
  BpSeq,
@@ -97,6 +96,7 @@ AVERAGE_OXYGEN_PHOSPHORUS_DISTANCE_COVALENT = 1.6
97
96
 
98
97
  @dataclass(frozen=True, order=True)
99
98
  class Atom:
99
+ entity_id: Optional[str]
100
100
  label: Optional[ResidueLabel]
101
101
  auth: Optional[ResidueAuth]
102
102
  model: int
@@ -129,6 +129,29 @@ class Residue3D(Residue):
129
129
  "C": set(["N1", "C2", "O2", "N3", "C4", "N4", "C5", "C6"]),
130
130
  "U": set(["N1", "C2", "O2", "N3", "C4", "O4", "C5", "C6"]),
131
131
  }
132
+ # Heavy atoms in nucleotide
133
+ nucleotide_heavy_atoms = (
134
+ set(
135
+ [
136
+ "P",
137
+ "OP1",
138
+ "OP2",
139
+ "O5'",
140
+ "C5'",
141
+ "C4'",
142
+ "O4'",
143
+ "C3'",
144
+ "O3'",
145
+ "C2'",
146
+ "O2'",
147
+ "C1'",
148
+ ]
149
+ )
150
+ .union(nucleobase_heavy_atoms["A"])
151
+ .union(nucleobase_heavy_atoms["G"])
152
+ .union(nucleobase_heavy_atoms["C"])
153
+ .union(nucleobase_heavy_atoms["U"])
154
+ )
132
155
 
133
156
  def __lt__(self, other):
134
157
  return (self.model, self.chain, self.number, self.icode or " ") < (
@@ -177,8 +200,8 @@ class Residue3D(Residue):
177
200
 
178
201
  @cached_property
179
202
  def is_nucleotide(self) -> bool:
180
- return len(self.atoms) > 1 and any(
181
- [atom for atom in self.atoms if atom.name == "C1'"]
203
+ return self.nucleotide_heavy_atoms.intersection(
204
+ set([atom.name for atom in self.atoms])
182
205
  )
183
206
 
184
207
  @cached_property
@@ -269,7 +292,7 @@ class Residue3D(Residue):
269
292
  logging.error(
270
293
  f"Failed to determine the outermost atom for nucleotide {self}, so an arbitrary atom will be used"
271
294
  )
272
- yield Atom(self.label, self.auth, self.model, "UNK", 0.0, 0.0, 0.0, None)
295
+ yield Atom(None, self.label, self.auth, self.model, "UNK", 0.0, 0.0, 0.0, None)
273
296
 
274
297
  def __inner_generator(self):
275
298
  # try to find expected atom name
@@ -297,7 +320,7 @@ class Residue3D(Residue):
297
320
  logging.error(
298
321
  f"Failed to determine the innermost atom for nucleotide {self}, so an arbitrary atom will be used"
299
322
  )
300
- yield Atom(self.label, self.auth, self.model, "UNK", 0.0, 0.0, 0.0, None)
323
+ yield Atom(None, self.label, self.auth, self.model, "UNK", 0.0, 0.0, 0.0, None)
301
324
 
302
325
 
303
326
  @dataclass(frozen=True, order=True)
@@ -36,9 +36,7 @@ def test_1ehz():
36
36
  for bp in interactions[i]
37
37
  if (bp.nt1.full_name, bp.nt2.full_name) == element
38
38
  ]
39
- assert (
40
- False
41
- ), f"Interaction {element} occurs {count} times among {labels[i]} type: {duplicates}"
39
+ assert False, f"Interaction {element} occurs {count} times among {labels[i]} type: {duplicates}"
42
40
 
43
41
 
44
42
  def test_8btk():
@@ -34,7 +34,7 @@ def test_1DFU():
34
34
  assert b1u not in mapping.base_pair_graph[b2g]
35
35
 
36
36
 
37
- # in 4WTI the first residue has only O3' atom and so is not considered a nucleotide
37
+ # in 4WTI the first residue has only O3' atom, but is stil considered a nucleotide
38
38
  def test_4WTI():
39
39
  with open("tests/4WTI_1_T-P.cif") as f:
40
40
  structure3d = read_3d_structure(f, 1)
@@ -42,7 +42,7 @@ def test_4WTI():
42
42
  mapping = Mapping2D3D(
43
43
  structure3d, base_interactions.basePairs, base_interactions.stackings, True
44
44
  )
45
- assert mapping.dot_bracket == ">strand_T\nCGG\n.((\n>strand_P\nCC\n))"
45
+ assert mapping.dot_bracket == ">strand_T\nACGG\n..((\n>strand_P\nCC\n))"
46
46
 
47
47
 
48
48
  # in 1HMH the bases are oriented in 45 degrees and it caused the program to identify invalid base pair
@@ -64,3 +64,14 @@ def test_6INQ():
64
64
  assert structure3d.find_residue(None, ResidueAuth("T", 0, None, "DC")) is not None
65
65
  assert structure3d.find_residue(ResidueLabel("O", 126, "DG"), None) is not None
66
66
  assert structure3d.find_residue(None, ResidueAuth("N", 0, None, "DG")) is not None
67
+
68
+
69
+ # in 6g90 from rna3db, the sequence contains Ns which were ignored incorrectly
70
+ def test_6g90():
71
+ with open("tests/6g90_1.cif") as f:
72
+ structure3d = read_3d_structure(f, nucleic_acid_only=True)
73
+ sequence = "".join([residue.one_letter_name for residue in structure3d.residues])
74
+ assert (
75
+ sequence
76
+ == "AUACUUACCUUAAGAUAUCAGAGGAGAUCAAGAAGUCCUACUGAUCAAACAUGCGCUUCCAAGAAGGACGUUAAGCAUUUAUCAUUGAACGUUCAUUGAACAUUGAUGCAAACUCCUUGGUCACACACACGCGGAAGGCGUGUUUGCUGACGUCCCUUGUUUCAAUCAUUGGUUAACUGAUUUUUGGGGCCCUUUGUUCUUCUGAGAAGUGACACCAAUUGGUGUUAGGGGAGCUGGGGCCUUUCAAAANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNUUUUGGAAGGUCUUGGUCGGGUGGAUCUUAUAAUUUUUGAUUUA"
77
+ )
@@ -2,7 +2,6 @@ from collections import Counter
2
2
 
3
3
  from hypothesis import given, settings
4
4
  from hypothesis import strategies as st
5
-
6
5
  from rnapolis.common import (
7
6
  BaseInteractions,
8
7
  BasePair,
@@ -93,7 +92,7 @@ def test_rnapdbee_adapters_api_compliance_structure2d(obj):
93
92
 
94
93
  def test_bpseq_from_dotbracket():
95
94
  expected = BpSeq.from_file("tests/1ET4-A.bpseq")
96
- actual = BpSeq.from_dotbracket(DotBracket.from_file(f"tests/1ET4-A.dbn"))
95
+ actual = BpSeq.from_dotbracket(DotBracket.from_file("tests/1ET4-A.dbn"))
97
96
  assert expected == actual
98
97
 
99
98
 
@@ -1,7 +1,6 @@
1
1
  import os
2
2
 
3
3
  import pytest
4
-
5
4
  from rnapolis.rfam_folder import generate_consensus_secondary_structure, parse_fasta
6
5
 
7
6
  IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true"
@@ -5,10 +5,10 @@ from rnapolis.tertiary import Atom, torsion_angle
5
5
 
6
6
 
7
7
  def test_torsion_angle():
8
- a1 = Atom(None, None, 1, "P", 50.63, 49.73, 50.57, None)
9
- a2 = Atom(None, None, 1, "O5'", 50.16, 49.14, 52.02, None)
10
- a3 = Atom(None, None, 1, "C5'", 50.22, 49.95, 53.21, None)
11
- a4 = Atom(None, None, 1, "C4'", 50.97, 49.23, 54.31, None)
8
+ a1 = Atom(None, None, None, 1, "P", 50.63, 49.73, 50.57, None)
9
+ a2 = Atom(None, None, None, 1, "O5'", 50.16, 49.14, 52.02, None)
10
+ a3 = Atom(None, None, None, 1, "C5'", 50.22, 49.95, 53.21, None)
11
+ a4 = Atom(None, None, None, 1, "C4'", 50.97, 49.23, 54.31, None)
12
12
  assert math.isclose(
13
13
  math.degrees(torsion_angle(a1, a2, a3, a4)), -127.83976634524326
14
14
  )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes