pyjess 0.3.3__cp312-cp312-macosx_10_13_x86_64.whl → 0.4.1__cp312-cp312-macosx_10_13_x86_64.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 pyjess might be problematic. Click here for more details.

Binary file
pyjess/_jess.pyi CHANGED
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  import typing
3
- from typing import Any, Generic, Union, Optional, Sequence, Iterator, Iterable, List, TextIO, Sized, TypeVar
3
+ from typing import Any, Generic, Union, Optional, Sequence, Iterator, Iterable, List, TextIO, Sized, TypeVar, Tuple
4
4
 
5
5
  try:
6
6
  from typing import Literal
@@ -13,6 +13,8 @@ MATCH_MODE = Literal[
13
13
 
14
14
  _SELF = TypeVar("_SELF")
15
15
 
16
+ __version__: str
17
+
16
18
  class Molecule(Sequence[Atom]):
17
19
  @classmethod
18
20
  def load(cls, file: Union[TextIO, str, os.PathLike[str]], id: Optional[str] = None, ignore_endmdl: bool = False) -> Molecule: ...
@@ -26,9 +28,14 @@ class Molecule(Sequence[Atom]):
26
28
  def __getitem__(self: _SELF, index: slice) -> _SELF: ...
27
29
  @typing.overload
28
30
  def __getitem__(self: _SELF, index: Union[int, slice]) -> Union[Atom, _SELF]: ...
31
+ def __copy__(self) -> Molecule: ...
32
+ def __eq__(self, other: object) -> bool: ...
33
+ def __hash__(self) -> int: ...
34
+ def __reduce__(self) -> Tuple[Any, ...]: ...
29
35
  @property
30
36
  def id(self) -> Optional[str]: ...
31
37
  def conserved(self: _SELF, cutoff: float = 0.0) -> _SELF: ...
38
+ def copy(self) -> Molecule: ...
32
39
 
33
40
  class Atom:
34
41
  @classmethod
@@ -54,7 +61,11 @@ class Atom:
54
61
  element: str = "",
55
62
  charge: int = 0,
56
63
  ): ...
64
+ def __eq__(self, other: object) -> bool: ...
65
+ def __hash__(self) -> int: ...
66
+ def __reduce__(self) -> Tuple[Any, ...]: ...
57
67
  def __repr__(self) -> str: ...
68
+ def __copy__(self) -> Atom: ...
58
69
  @property
59
70
  def serial(self) -> int: ...
60
71
  @property
@@ -85,6 +96,7 @@ class Atom:
85
96
  def y(self) -> float: ...
86
97
  @property
87
98
  def z(self) -> float: ...
99
+ def copy(self) -> Atom: ...
88
100
 
89
101
  class TemplateAtom:
90
102
  @classmethod
@@ -105,6 +117,10 @@ class TemplateAtom:
105
117
  match_mode: MATCH_MODE = 0,
106
118
  ): ...
107
119
  def __repr__(self) -> str: ...
120
+ def __eq__(self, other: object) -> bool: ...
121
+ def __hash__(self) -> int: ...
122
+ def __reduce__(self) -> Tuple[Any, ...]: ...
123
+ def __copy__(self: _SELF) -> _SELF: ...
108
124
  @property
109
125
  def match_mode(self) -> MATCH_MODE: ...
110
126
  @property
@@ -118,11 +134,12 @@ class TemplateAtom:
118
134
  @property
119
135
  def z(self) -> float: ...
120
136
  @property
121
- def atom_names(self) -> List[str]: ...
137
+ def atom_names(self) -> Tuple[str, ...]: ...
122
138
  @property
123
- def residue_names(self) -> List[str]: ...
139
+ def residue_names(self) -> Tuple[str, ...]: ...
124
140
  @property
125
141
  def distance_weight(self) -> float: ...
142
+ def copy(self: _SELF) -> _SELF: ...
126
143
 
127
144
  class Template(Sequence[TemplateAtom]):
128
145
  @classmethod
@@ -137,10 +154,15 @@ class Template(Sequence[TemplateAtom]):
137
154
  def __getitem__(self: _SELF, index: slice) -> _SELF: ...
138
155
  @typing.overload
139
156
  def __getitem__(self: _SELF, index: Union[int, slice]) -> Union[TemplateAtom, _SELF]: ...
157
+ def __eq__(self, other: object) -> bool: ...
158
+ def __hash__(self) -> int: ...
159
+ def __reduce__(self) -> Tuple[Any, ...]: ...
160
+ def __copy__(self) -> Template: ...
140
161
  @property
141
162
  def id(self) -> str: ...
142
163
  @property
143
164
  def dimension(self) -> int: ...
165
+ def copy(self) -> Template: ...
144
166
 
145
167
  _T = TypeVar("_T", bound=Template)
146
168
 
@@ -184,6 +206,10 @@ class Jess(Generic[_T], Sequence[_T]):
184
206
  def __getitem__(self, index: slice) -> Jess[_T]: ...
185
207
  @typing.overload
186
208
  def __getitem__(self, index: Union[int, slice]) -> Union[_T, Jess[_T]]: ...
209
+ def __eq__(self, other: object) -> bool: ...
210
+ def __hash__(self) -> int: ...
211
+ def __copy__(self: _SELF) -> _SELF: ...
212
+ def __reduce__(self) -> Tuple[Any, ...]: ...
187
213
  def query(
188
214
  self,
189
215
  molecule: Molecule,
pyjess/_jess.pyx CHANGED
@@ -43,7 +43,9 @@ from jess.tess_atom cimport TessAtom as _TessAtom
43
43
  # --- Python imports ---------------------------------------------------------
44
44
 
45
45
  import contextlib
46
+ import functools
46
47
  import io
48
+ import itertools
47
49
  import os
48
50
  import warnings
49
51
 
@@ -53,10 +55,11 @@ __version__ = PROJECT_VERSION
53
55
 
54
56
  cdef inline void copy_token(char* dst, const char* src, size_t n) noexcept nogil:
55
57
  cdef size_t i
56
- strncpy(dst, src, n)
57
58
  for i in range(n):
58
- if dst[i] == ord(' ') or dst[i] == 0:
59
+ if src[i] == ord(' ') or src[i] == 0:
59
60
  dst[i] = ord('_')
61
+ else:
62
+ dst[i] = src[i]
60
63
  dst[n] = 0
61
64
 
62
65
  @contextlib.contextmanager
@@ -68,9 +71,12 @@ def nullcontext(return_value=None):
68
71
  cdef class Molecule:
69
72
  """A molecule structure, as a sequence of `Atom` objects.
70
73
 
71
- .. versionchanged:: 0.2.2
74
+ .. versionadded:: 0.2.2
72
75
  Support identifiers of arbitrary length.
73
76
 
77
+ .. versionadded:: 0.4.0
78
+ Equality, hashing and pickle protocol support.
79
+
74
80
  """
75
81
  cdef _Molecule* _mol
76
82
  cdef str _id
@@ -204,6 +210,26 @@ cdef class Molecule:
204
210
  atom._atom = <_Atom*> jess.molecule.Molecule_atom(self._mol, index_)
205
211
  return atom
206
212
 
213
+ def __copy__(self):
214
+ return self.copy()
215
+
216
+ def __eq__(self, object other):
217
+ cdef Molecule other_
218
+ if not isinstance(other, Molecule):
219
+ return NotImplemented
220
+ other_ = other
221
+ if self._id != other_._id:
222
+ return False
223
+ if self._mol.count != other_._mol.count:
224
+ return False
225
+ return all(x == y for x,y in zip(self, other_))
226
+
227
+ def __hash__(self):
228
+ return hash((self._id, *(hash(x) for x in self)))
229
+
230
+ def __reduce__(self):
231
+ return type(self), (list(self), self.id)
232
+
207
233
  def __sizeof__(self):
208
234
  assert self._mol is not NULL
209
235
  return (
@@ -229,9 +255,44 @@ cdef class Molecule:
229
255
  ]
230
256
  )
231
257
 
258
+ cpdef Molecule copy(self):
259
+ """Create a copy of this molecule and its atoms.
260
+
261
+ Returns:
262
+ `~pyjess.Molecule`: A newly allocated molecule with the same
263
+ identifier and atoms.
264
+
265
+ .. versionadded:: 0.4.0
266
+
267
+ """
268
+ cdef Molecule copy = Molecule.__new__(Molecule)
269
+ cdef size_t size = sizeof(_Molecule) + self._mol.count * sizeof(_Atom*)
270
+
271
+ with nogil:
272
+ # allocate molecule storage
273
+ copy._mol = <_Molecule*> malloc(size)
274
+ if copy._mol is NULL:
275
+ raise MemoryError("Failed to allocate molecule")
276
+ # copy molecule attributes
277
+ copy._mol.count = self._mol.count
278
+ memset(copy._mol.id, b' ', 5)
279
+ # copy molecule atoms
280
+ for i in range(self._mol.count):
281
+ copy._mol.atom[i] = <_Atom*> malloc(sizeof(_Atom))
282
+ if copy._mol.atom[i] is NULL:
283
+ raise MemoryError("Failed to allocate atom")
284
+ memcpy(copy._mol.atom[i], self._mol.atom[i], sizeof(_Atom))
285
+
286
+ copy._id = self._id
287
+ return copy
288
+
232
289
 
233
290
  cdef class Atom:
234
291
  """A single atom in a molecule.
292
+
293
+ .. versionadded:: 0.4.0
294
+ Equality, hashing and pickle protocol support.
295
+
235
296
  """
236
297
  cdef object owner
237
298
  cdef bint owned
@@ -297,11 +358,11 @@ cdef class Atom:
297
358
  str chain_id,
298
359
  int residue_number,
299
360
  str insertion_code,
300
- float x,
301
- float y,
302
- float z,
303
- float occupancy = 0.0,
304
- float temperature_factor = 0.0,
361
+ double x,
362
+ double y,
363
+ double z,
364
+ double occupancy = 0.0,
365
+ double temperature_factor = 0.0,
305
366
  str segment = '',
306
367
  str element = '',
307
368
  int charge = 0,
@@ -336,7 +397,7 @@ cdef class Atom:
336
397
  self._atom.serial = serial
337
398
  self._atom.altLoc = ord(altloc)
338
399
  self._atom.chainID1 = ord(chain_id[0]) if len(chain_id) > 0 else 0
339
- self._atom.chainID2 = ord(chain_id[1]) if len(chain_id) > 1 else ord('0')
400
+ self._atom.chainID2 = ord(chain_id[1]) if len(chain_id) > 1 else ord(' ')
340
401
  self._atom.resSeq = residue_number
341
402
  self._atom.iCode = ord(insertion_code)
342
403
  self._atom.x[0] = x
@@ -354,29 +415,38 @@ cdef class Atom:
354
415
  _name.insert(0, ord('_'))
355
416
  copy_token(self._atom.name, _name.ljust(4, b'\0'), 4)
356
417
 
418
+ def __copy__(self):
419
+ return self.copy()
420
+
421
+ cdef dict _state(self):
422
+ return {
423
+ "serial": self.serial,
424
+ "name": self.name,
425
+ "altloc": self.altloc,
426
+ "residue_name": self.residue_name,
427
+ "chain_id": self.chain_id,
428
+ "residue_number": self.residue_number,
429
+ "insertion_code": self.insertion_code,
430
+ "x": self.x,
431
+ "y": self.y,
432
+ "z": self.z,
433
+ "temperature_factor": self.temperature_factor,
434
+ "occupancy": self.occupancy,
435
+ "segment": self.segment,
436
+ "element": self.element,
437
+ "charge": self.charge,
438
+ }
439
+
440
+ def __reduce__(self):
441
+ cdef dict state = self._state()
442
+ return functools.partial(type(self), **state), ()
443
+
357
444
  def __repr__(self):
358
445
  cdef str ty = type(self).__name__
359
- cdef list args = [
360
- f"serial={self.serial!r}",
361
- f"name={self.name!r}",
362
- f"altloc={self.altloc!r}",
363
- f"residue_name={self.residue_name!r}",
364
- f"chain_id={self.chain_id!r}",
365
- f"residue_number={self.residue_number!r}",
366
- f"x={self.x!r}",
367
- f"y={self.y!r}",
368
- f"z={self.z!r}",
369
- f"segment={self.segment!r}",
370
- f"insertion_code={self.insertion_code!r}",
371
- ]
372
- if self.occupancy:
373
- args.append(f"occupancy={self.occupancy!r}")
374
- if self.temperature_factor:
375
- args.append(f"temperature_factor={self.temperature_factor!r}")
376
- if self.element:
377
- args.append(f"element={self.element!r}")
378
- if self.charge:
379
- args.append(f"charge={self.charge!r}")
446
+ cdef list args = []
447
+ for k,v in self._state().items():
448
+ if v is not None:
449
+ args.append(f"{k}={v!r}")
380
450
  return f"{ty}({', '.join(args)})"
381
451
 
382
452
  def __sizeof__(self):
@@ -385,6 +455,17 @@ cdef class Atom:
385
455
  size += sizeof(_Atom)
386
456
  return size
387
457
 
458
+ def __eq__(self, object other):
459
+ cdef Atom other_
460
+ if not isinstance(other, Atom):
461
+ return NotImplemented
462
+ other_ = other
463
+ # FIXME: it should be possible to do a memcmp here.
464
+ return self._state() == other_._state()
465
+
466
+ def __hash__(self):
467
+ return hash(tuple(self._state().values()))
468
+
388
469
  @property
389
470
  def serial(self):
390
471
  """`int`: The atom serial number.
@@ -484,9 +565,29 @@ cdef class Atom:
484
565
  assert self._atom is not NULL
485
566
  return self._atom.x[2]
486
567
 
568
+ cpdef Atom copy(self):
569
+ """Create a copy of this atom.
570
+
571
+ Returns:
572
+ `~pyjess.Atom`: A newly allocated atom with identical attributes.
573
+
574
+ .. versionadded:: 0.4.0
575
+
576
+ """
577
+ cdef Atom copy = Atom.__new__(Atom)
578
+ copy._atom = <_Atom*> malloc(sizeof(_Atom))
579
+ if copy._atom is NULL:
580
+ raise MemoryError("Failed to allocate atom")
581
+ memcpy(copy._atom, self._atom, sizeof(_Atom))
582
+ return copy
583
+
487
584
 
488
585
  cdef class TemplateAtom:
489
586
  """A single template atom.
587
+
588
+ .. versionadded:: 0.4.0
589
+ Equality, hashing and pickle protocol support.
590
+
490
591
  """
491
592
  cdef object owner
492
593
  cdef bint owned
@@ -630,23 +731,42 @@ cdef class TemplateAtom:
630
731
  raise ValueError(f"Invalid residue name: {name!r}")
631
732
  copy_token(self._atom.resName[m], _name.ljust(3, b'\0'), 3)
632
733
 
734
+ cdef dict _state(self):
735
+ return {
736
+ "chain_id": self.chain_id,
737
+ "residue_number": self.residue_number,
738
+ "x": self.x,
739
+ "y": self.y,
740
+ "z": self.z,
741
+ "residue_names": self.residue_names,
742
+ "atom_names": self.atom_names,
743
+ "distance_weight": self.distance_weight,
744
+ "match_mode": self.match_mode,
745
+ }
746
+
633
747
  def __repr__(self):
634
748
  cdef str ty = type(self).__name__
635
- cdef list args = [
636
- f"chain_id={self.chain_id!r}",
637
- f"residue_number={self.residue_number!r}",
638
- f"x={self.x!r}",
639
- f"y={self.y!r}",
640
- f"z={self.z!r}",
641
- f"residue_names={self.residue_names!r}",
642
- f"atom_names={self.atom_names!r}",
643
- ]
644
- if self.distance_weight:
645
- args.append(f"distance_weight={self.distance_weight!r}")
646
- if self.match_mode:
647
- args.append(f"match_mode={self.match_mode!r}")
749
+ cdef list args = []
750
+ for k, v in self._state().items():
751
+ args.append(f"{k}={v!r}")
648
752
  return f"{ty}({', '.join(args)})"
649
753
 
754
+ def __copy__(self):
755
+ return self.copy()
756
+
757
+ def __eq__(self, object other):
758
+ cdef TemplateAtom other_
759
+ if not isinstance(other, TemplateAtom):
760
+ return NotImplemented
761
+ other_ = other
762
+ return self._state() == other_._state()
763
+
764
+ def __hash__(self):
765
+ return hash(tuple(self._state().values()))
766
+
767
+ def __reduce__(self):
768
+ return functools.partial(type(self), **self._state()), ()
769
+
650
770
  def __sizeof__(self):
651
771
  assert self._atom is not NULL
652
772
 
@@ -708,7 +828,11 @@ cdef class TemplateAtom:
708
828
 
709
829
  @property
710
830
  def atom_names(self):
711
- """`list` of `str`: The different atom names for this atom.
831
+ """`tuple` of `str`: The different atom names for this atom.
832
+
833
+ .. versionchanged:: 0.4.1
834
+ Property now returns a `tuple` rather than a `list`.
835
+
712
836
  """
713
837
  assert self._atom is not NULL
714
838
 
@@ -717,11 +841,15 @@ cdef class TemplateAtom:
717
841
 
718
842
  for i in range(self._atom.nameCount):
719
843
  l.append(self._atom.name[i].replace(b'_', b'').decode())
720
- return l
844
+ return tuple(l)
721
845
 
722
846
  @property
723
847
  def residue_names(self):
724
- """`list` of `str`: The different residue names for this atom.
848
+ """`tuple` of `str`: The different residue names for this atom.
849
+
850
+ .. versionchanged:: 0.4.1
851
+ Property now returns a `tuple` rather than a `list`.
852
+
725
853
  """
726
854
  assert self._atom is not NULL
727
855
 
@@ -730,7 +858,7 @@ cdef class TemplateAtom:
730
858
 
731
859
  for i in range(self._atom.resNameCount):
732
860
  l.append(self._atom.resName[i].replace(b'_', b'').decode())
733
- return l
861
+ return tuple(l)
734
862
 
735
863
  @property
736
864
  def distance_weight(self):
@@ -739,9 +867,25 @@ cdef class TemplateAtom:
739
867
  assert self._atom is not NULL
740
868
  return self._atom.distWeight
741
869
 
870
+ cpdef TemplateAtom copy(self):
871
+ """Create a copy of this template atom.
872
+
873
+ Returns:
874
+ `~pyjess.TemplateAtom`: A new template atom object with
875
+ identical attributes.
876
+
877
+ .. versionadded:: 0.4.0
878
+
879
+ """
880
+ return type(self)(**self._state())
881
+
742
882
 
743
883
  cdef class Template:
744
884
  """A template, as a sequence of `TemplateAtom` objects.
885
+
886
+ .. versionadded:: 0.4.0
887
+ Equality, hashing and pickle protocol support.
888
+
745
889
  """
746
890
  cdef object owner
747
891
  cdef bint owned
@@ -858,6 +1002,9 @@ cdef class Template:
858
1002
  residues = { self._tess.atom[i].resSeq for i in range(count) }
859
1003
  self._tess.dim = len(residues)
860
1004
 
1005
+ def __copy__(self):
1006
+ return self.copy()
1007
+
861
1008
  def __len__(self):
862
1009
  assert self._tpl is not NULL
863
1010
  return self._tess.count
@@ -884,6 +1031,28 @@ cdef class Template:
884
1031
  atom._atom = self._tess.atom[index_]
885
1032
  return atom
886
1033
 
1034
+ def __eq__(self, object other):
1035
+ cdef Template other_
1036
+ if not isinstance(other, Template):
1037
+ return NotImplemented
1038
+ other_ = other
1039
+ if self.id != other_.id:
1040
+ return False
1041
+ if self.dimension != other_.dimension:
1042
+ return False
1043
+ if len(self) != len(other_):
1044
+ return False
1045
+ return all(x == y for x,y in zip(self, other_))
1046
+
1047
+ def __hash__(self):
1048
+ return hash((
1049
+ self.id,
1050
+ *(hash(x) for x in self)
1051
+ ))
1052
+
1053
+ def __reduce__(self):
1054
+ return type(self), (list(self), self.id)
1055
+
887
1056
  def __sizeof__(self):
888
1057
  assert self._tess is not NULL
889
1058
 
@@ -927,6 +1096,12 @@ cdef class Template:
927
1096
  assert self._tess is not NULL
928
1097
  return self._tess.dim
929
1098
 
1099
+ cpdef Template copy(self):
1100
+ return Template(
1101
+ self,
1102
+ self.id
1103
+ )
1104
+
930
1105
 
931
1106
  cdef class Query:
932
1107
  """A query over templates with a given molecule.
@@ -1177,10 +1352,14 @@ cdef class Hit:
1177
1352
 
1178
1353
  cdef class Jess:
1179
1354
  """A handle to run Jess over a list of templates.
1355
+
1356
+ .. versionadded:: 0.4.0
1357
+ Equality, hashing and pickle protocol support.
1358
+
1180
1359
  """
1181
1360
  cdef _Jess* _jess
1182
1361
  cdef dict _indices
1183
- cdef list _templates
1362
+ cdef tuple _templates
1184
1363
  cdef size_t length
1185
1364
 
1186
1365
  def __cinit__(self):
@@ -1208,10 +1387,10 @@ cdef class Jess:
1208
1387
  """
1209
1388
  cdef Template template
1210
1389
  cdef _Template* tpl
1390
+ cdef list _templates = []
1211
1391
 
1212
1392
  self._jess = jess.jess.Jess_create()
1213
1393
  self._indices = {}
1214
- self._templates = []
1215
1394
 
1216
1395
  for template in templates:
1217
1396
  # NOTE: the Jess storage owns the data, so we make a copy of the
@@ -1219,9 +1398,27 @@ cdef class Jess:
1219
1398
  tpl = template._tpl.copy(template._tpl)
1220
1399
  jess.jess.Jess_addTemplate(self._jess, tpl)
1221
1400
  self._indices[<size_t> tpl] = self.length
1222
- self._templates.append(template)
1401
+ _templates.append(template)
1223
1402
  self.length += 1
1224
1403
 
1404
+ self._templates = tuple(_templates)
1405
+
1406
+ def __copy__(self):
1407
+ return self.copy()
1408
+
1409
+ def __reduce__(self):
1410
+ return type(self), (self._templates,)
1411
+
1412
+ def __eq__(self, object other):
1413
+ cdef Jess other_
1414
+ if not isinstance(other, Jess):
1415
+ return NotImplemented
1416
+ other_ = other
1417
+ return self._templates == other_._templates
1418
+
1419
+ def __hash__(self):
1420
+ return hash((Jess, self._templates))
1421
+
1225
1422
  def __len__(self):
1226
1423
  return self.length
1227
1424
 
@@ -1239,6 +1436,17 @@ cdef class Jess:
1239
1436
  raise IndexError(index)
1240
1437
  return self._templates[index_]
1241
1438
 
1439
+ cpdef Jess copy(self):
1440
+ """Create a copy of the `Jess` object.
1441
+
1442
+ Returns:
1443
+ `~pyjess.Jess`: A `Jess` object containing the same templates.
1444
+
1445
+ .. versionadded:: 0.4.0
1446
+
1447
+ """
1448
+ return type(self)(self._templates)
1449
+
1242
1450
  def query(
1243
1451
  self,
1244
1452
  Molecule molecule,
pyjess/tests/test_atom.py CHANGED
@@ -1,4 +1,6 @@
1
+ import ctypes
1
2
  import unittest
3
+ import pickle
2
4
 
3
5
  from .._jess import Atom
4
6
 
@@ -25,6 +27,25 @@ class TestAtom(unittest.TestCase):
25
27
  self.assertEqual(atom.segment, '')
26
28
  self.assertEqual(atom.element, 'C')
27
29
  self.assertEqual(atom.charge, 0)
30
+
31
+ def test_init_consistency(self):
32
+ loaded = Atom.loads("ATOM 39 CA PRO A 469 -14.948 2.091 10.228 1.00 27.71 C")
33
+ created = Atom(
34
+ serial=loaded.serial,
35
+ name=loaded.name,
36
+ residue_name=loaded.residue_name,
37
+ chain_id=loaded.chain_id,
38
+ altloc=loaded.altloc,
39
+ insertion_code=loaded.insertion_code,
40
+ residue_number=loaded.residue_number,
41
+ x=loaded.x,
42
+ y=loaded.y,
43
+ z=loaded.z,
44
+ occupancy=loaded.occupancy,
45
+ temperature_factor=loaded.temperature_factor,
46
+ element=loaded.element,
47
+ )
48
+ self.assertEqual(loaded, created)
28
49
 
29
50
  def test_init_invalid_chain_id(self):
30
51
  self.assertRaises(ValueError, self._create_atom, chain_id="too long")
@@ -35,6 +56,22 @@ class TestAtom(unittest.TestCase):
35
56
  def test_init_invalid_residue_name(self):
36
57
  self.assertRaises(ValueError, self._create_atom, residue_name="too long")
37
58
 
59
+ def test_hash(self):
60
+ a1 = self._create_atom()
61
+ a2 = self._create_atom()
62
+ self.assertEqual(hash(a1), hash(a2))
63
+ self.assertIsNot(a1, a2)
64
+ a3 = self._create_atom(x=1.0)
65
+ self.assertNotEqual(hash(a1), hash(a3))
66
+
67
+ def test_eq(self):
68
+ a1 = self._create_atom()
69
+ a2 = self._create_atom()
70
+ self.assertEqual(a1, a2)
71
+ self.assertIsNot(a1, a2)
72
+ a3 = self._create_atom(x=1.0)
73
+ self.assertNotEqual(a1, a3)
74
+
38
75
  def test_repr_roundtrip(self):
39
76
  atom = self._create_atom()
40
77
  copy = eval(repr(atom))
@@ -51,4 +88,24 @@ class TestAtom(unittest.TestCase):
51
88
  self.assertEqual(atom.charge, copy.charge)
52
89
  self.assertEqual(atom.x, copy.x)
53
90
  self.assertEqual(atom.y, copy.y)
54
- self.assertEqual(atom.z, copy.z)
91
+ self.assertEqual(atom.z, copy.z)
92
+ self.assertEqual(atom, copy)
93
+
94
+ def test_pickle_roundtrip(self):
95
+ atom = self._create_atom()
96
+ copy = pickle.loads(pickle.dumps(atom))
97
+ self.assertEqual(atom.serial, copy.serial)
98
+ self.assertEqual(atom.altloc, copy.altloc)
99
+ self.assertEqual(atom.name, copy.name)
100
+ self.assertEqual(atom.residue_name, copy.residue_name)
101
+ self.assertEqual(atom.residue_number, copy.residue_number)
102
+ self.assertEqual(atom.element, copy.element)
103
+ self.assertEqual(atom.insertion_code, copy.insertion_code)
104
+ self.assertEqual(atom.chain_id, copy.chain_id)
105
+ self.assertEqual(atom.occupancy, copy.occupancy)
106
+ self.assertEqual(atom.temperature_factor, copy.temperature_factor)
107
+ self.assertEqual(atom.charge, copy.charge)
108
+ self.assertEqual(atom.x, copy.x)
109
+ self.assertEqual(atom.y, copy.y)
110
+ self.assertEqual(atom.z, copy.z)
111
+ self.assertEqual(atom, copy)
pyjess/tests/test_jess.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import math
2
2
  import unittest
3
3
  import sys
4
+ import pickle
4
5
 
5
6
  from .._jess import Template, Molecule, Jess
6
7
  from .utils import files
@@ -20,6 +21,59 @@ class TestJess(unittest.TestCase):
20
21
  hits = jess.query(mol, 2, 2, 4)
21
22
  self.assertRaises(StopIteration, next, hits)
22
23
 
24
+ def test_hash_empty(self):
25
+ j1 = Jess()
26
+ j2 = Jess()
27
+ self.assertEqual(j1, j2)
28
+ self.assertEqual(hash(j1), hash(j2))
29
+ self.assertIsNot(j1, j2)
30
+
31
+ @unittest.skipUnless(files, "importlib.resources not available")
32
+ def test_copy(self):
33
+ with files(data).joinpath("template_01.qry").open() as f:
34
+ template1 = Template.load(f)
35
+ with files(data).joinpath("template_02.qry").open() as f:
36
+ template2 = Template.load(f)
37
+ jess = Jess([template1, template2])
38
+ copy = jess.copy()
39
+ self.assertEqual(jess, copy)
40
+
41
+ @unittest.skipUnless(files, "importlib.resources not available")
42
+ def test_hash(self):
43
+ with files(data).joinpath("template_01.qry").open() as f:
44
+ template1 = Template.load(f)
45
+ with files(data).joinpath("template_02.qry").open() as f:
46
+ template2 = Template.load(f)
47
+ j1 = Jess([template1, template2])
48
+ j2 = Jess([template1, template2])
49
+ self.assertEqual(hash(j1), hash(j2))
50
+ self.assertIsNot(j1, j2)
51
+ j3 = Jess([template1])
52
+ self.assertNotEqual(hash(j1), hash(j3))
53
+
54
+ @unittest.skipUnless(files, "importlib.resources not available")
55
+ def test_eq(self):
56
+ with files(data).joinpath("template_01.qry").open() as f:
57
+ template1 = Template.load(f)
58
+ with files(data).joinpath("template_02.qry").open() as f:
59
+ template2 = Template.load(f)
60
+ j1 = Jess([template1, template2])
61
+ j2 = Jess([template1, template2])
62
+ self.assertEqual(j1, j2)
63
+ self.assertIsNot(j1, j2)
64
+ j3 = Jess([template1])
65
+ self.assertNotEqual(j1, j3)
66
+
67
+ @unittest.skipUnless(files, "importlib.resources not available")
68
+ def test_pickle_roundtrip(self):
69
+ with files(data).joinpath("template_01.qry").open() as f:
70
+ template1 = Template.load(f)
71
+ with files(data).joinpath("template_02.qry").open() as f:
72
+ template2 = Template.load(f)
73
+ jess = Jess([template1, template2])
74
+ copy = pickle.loads(pickle.dumps(jess))
75
+ self.assertEqual(jess, copy)
76
+
23
77
  @unittest.skipUnless(sys.implementation.name == "cpython", "only available on CPython")
24
78
  @unittest.skipUnless(files, "importlib.resources not available")
25
79
  def test_sizeof(self):
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import pickle
2
3
  import unittest
3
4
  import tempfile
4
5
  import textwrap
@@ -83,4 +84,58 @@ class TestMolecule(unittest.TestCase):
83
84
  self.assertEqual(mol2.id, mol.id)
84
85
  self.assertEqual(len(mol2), 2)
85
86
  self.assertEqual(mol2[0].name, "CA")
86
- self.assertEqual(mol2[1].name, "C")
87
+ self.assertEqual(mol2[1].name, "C")
88
+
89
+ def test_hash(self):
90
+ atoms = [
91
+ self._create_atom(serial=1, name='N'),
92
+ self._create_atom(serial=2, name='CA'),
93
+ self._create_atom(serial=3, name='C'),
94
+ self._create_atom(serial=4, name='O'),
95
+ ]
96
+ mol1 = Molecule(atoms)
97
+ mol2 = Molecule(atoms)
98
+ self.assertEqual(hash(mol1), hash(mol2))
99
+ self.assertIsNot(mol1, mol2)
100
+ mol3 = Molecule(atoms[:-1])
101
+ self.assertNotEqual(hash(mol1), hash(mol3))
102
+
103
+ def test_eq(self):
104
+ atoms = [
105
+ self._create_atom(serial=1, name='N'),
106
+ self._create_atom(serial=2, name='CA'),
107
+ self._create_atom(serial=3, name='C'),
108
+ self._create_atom(serial=4, name='O'),
109
+ ]
110
+ mol1 = Molecule(atoms)
111
+ mol2 = Molecule(atoms)
112
+ self.assertEqual(mol1, mol2)
113
+ self.assertIsNot(mol1, mol2)
114
+ mol3 = Molecule(atoms[:-1])
115
+ self.assertNotEqual(mol1, mol3)
116
+
117
+ def test_copy(self):
118
+ atoms = [
119
+ self._create_atom(serial=1, name='N'),
120
+ self._create_atom(serial=2, name='CA'),
121
+ self._create_atom(serial=3, name='C'),
122
+ self._create_atom(serial=4, name='O'),
123
+ ]
124
+ mol1 = Molecule(atoms)
125
+ mol2 = mol1.copy()
126
+ self.assertEqual(list(mol1), list(mol2))
127
+ self.assertEqual(mol1.id, mol2.id)
128
+ self.assertEqual(mol1, mol2)
129
+
130
+ def test_pickle_roundtrip(self):
131
+ atoms = [
132
+ self._create_atom(serial=1, name='N'),
133
+ self._create_atom(serial=2, name='CA'),
134
+ self._create_atom(serial=3, name='C'),
135
+ self._create_atom(serial=4, name='O'),
136
+ ]
137
+ mol1 = Molecule(atoms)
138
+ mol2 = pickle.loads(pickle.dumps(mol1))
139
+ self.assertEqual(list(mol1), list(mol2))
140
+ self.assertEqual(mol1.id, mol2.id)
141
+ self.assertEqual(mol1, mol2)
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import pickle
2
3
  import unittest
3
4
  import tempfile
4
5
  import textwrap
@@ -29,9 +30,9 @@ class TestTemplate(unittest.TestCase):
29
30
  template = Template.loads(TEMPLATE)
30
31
  self.assertEqual(len(template), 11)
31
32
  self.assertEqual(template.dimension, 5)
32
- self.assertEqual(template[0].residue_names, ["LYS"])
33
- self.assertEqual(template[0].atom_names, ["NZ"])
34
- self.assertEqual(template[1].atom_names, ["CG"])
33
+ self.assertEqual(template[0].residue_names, ("LYS",))
34
+ self.assertEqual(template[0].atom_names, ("NZ",))
35
+ self.assertEqual(template[1].atom_names, ("CG",))
35
36
  self.assertEqual(template[2].residue_number, 1132)
36
37
  self.assertEqual(template[-1].residue_number, 1150)
37
38
 
@@ -41,9 +42,9 @@ class TestTemplate(unittest.TestCase):
41
42
  f.write(TEMPLATE)
42
43
  f.flush()
43
44
  template = Template.load(f.name)
44
- self.assertEqual(template[0].residue_names, ["LYS"])
45
- self.assertEqual(template[0].atom_names, ["NZ"])
46
- self.assertEqual(template[1].atom_names, ["CG"])
45
+ self.assertEqual(template[0].residue_names, ("LYS",))
46
+ self.assertEqual(template[0].atom_names, ("NZ",))
47
+ self.assertEqual(template[1].atom_names, ("CG",))
47
48
  self.assertEqual(template[2].residue_number, 1132)
48
49
  self.assertEqual(template[-1].residue_number, 1150)
49
50
 
@@ -54,9 +55,9 @@ class TestTemplate(unittest.TestCase):
54
55
  f.flush()
55
56
  f.seek(0)
56
57
  template = Template.load(f)
57
- self.assertEqual(template[0].residue_names, ["LYS"])
58
- self.assertEqual(template[0].atom_names, ["NZ"])
59
- self.assertEqual(template[1].atom_names, ["CG"])
58
+ self.assertEqual(template[0].residue_names, ("LYS",))
59
+ self.assertEqual(template[0].atom_names, ("NZ",))
60
+ self.assertEqual(template[1].atom_names, ("CG",))
60
61
  self.assertEqual(template[2].residue_number, 1132)
61
62
  self.assertEqual(template[-1].residue_number, 1150)
62
63
 
@@ -85,3 +86,32 @@ class TestTemplate(unittest.TestCase):
85
86
  tpl1 = Template.loads(TEMPLATE, id="tpl1")
86
87
  tpl2 = tpl1[1:4]
87
88
  self.assertEqual(len(tpl2), 3)
89
+
90
+ def test_hash(self):
91
+ tpl1 = Template.loads(TEMPLATE, id="tpl1")
92
+ tpl2 = Template.loads(TEMPLATE, id="tpl1")
93
+ self.assertEqual(tpl1, tpl2)
94
+ self.assertEqual(hash(tpl1), hash(tpl2))
95
+ self.assertIsNot(tpl1, tpl2)
96
+ tpl3 = Template.loads(TEMPLATE, id="tpl3")
97
+ self.assertNotEqual(hash(tpl1), hash(tpl3))
98
+
99
+ def test_eq(self):
100
+ tpl1 = Template.loads(TEMPLATE, id="tpl1")
101
+ tpl2 = Template.loads(TEMPLATE, id="tpl1")
102
+ self.assertEqual(tpl1, tpl2)
103
+ self.assertIsNot(tpl1, tpl2)
104
+ tpl3 = Template.loads(TEMPLATE, id="tpl3")
105
+ self.assertNotEqual(tpl1, tpl3)
106
+
107
+ def test_copy(self):
108
+ tpl1 = Template.loads(TEMPLATE, id="tpl1")
109
+ tpl2 = tpl1.copy()
110
+ self.assertEqual(len(tpl1), len(tpl2))
111
+ self.assertEqual(tpl1, tpl2)
112
+
113
+ def test_pickle_roundtrip(self):
114
+ tpl1 = Template.loads(TEMPLATE, id="tpl1")
115
+ tpl2 = pickle.loads(pickle.dumps(tpl1))
116
+ self.assertEqual(len(tpl1), len(tpl2))
117
+ self.assertEqual(tpl1, tpl2)
@@ -1,5 +1,6 @@
1
1
  import unittest
2
2
  import sys
3
+ import pickle
3
4
 
4
5
  from .._jess import TemplateAtom
5
6
 
@@ -9,8 +10,8 @@ class TestTemplateAtom(unittest.TestCase):
9
10
  def test_load(self):
10
11
  atom = TemplateAtom.loads("ATOM 1 NE ARG A1136 3.953 0.597 -1.721 K")
11
12
  self.assertEqual(atom.match_mode, 1)
12
- self.assertEqual(atom.atom_names, ["NE"])
13
- self.assertEqual(atom.residue_names, ["ARG", "LYS"])
13
+ self.assertEqual(atom.atom_names, ("NE",))
14
+ self.assertEqual(atom.residue_names, ("ARG", "LYS",))
14
15
  self.assertEqual(atom.chain_id, "A")
15
16
  self.assertEqual(atom.residue_number, 1136)
16
17
  self.assertEqual(atom.x, 3.953)
@@ -31,6 +32,22 @@ class TestTemplateAtom(unittest.TestCase):
31
32
  default.update(kwargs)
32
33
  return TemplateAtom(**default)
33
34
 
35
+ def test_hash(self):
36
+ a1 = self._create_atom()
37
+ a2 = self._create_atom()
38
+ self.assertEqual(hash(a1), hash(a2))
39
+ self.assertIsNot(a1, a2)
40
+ a3 = self._create_atom(x=1.0)
41
+ self.assertNotEqual(hash(a1), hash(a3))
42
+
43
+ def test_eq(self):
44
+ a1 = self._create_atom()
45
+ a2 = self._create_atom()
46
+ self.assertEqual(a1, a2)
47
+ self.assertIsNot(a1, a2)
48
+ a3 = self._create_atom(x=1.0)
49
+ self.assertNotEqual(a1, a3)
50
+
34
51
  @unittest.skipUnless(sys.implementation.name == "cpython", "only available on CPython")
35
52
  def test_sizeof(self):
36
53
  atom = self._create_atom()
@@ -43,8 +60,33 @@ class TestTemplateAtom(unittest.TestCase):
43
60
  def test_init_invalid_residue_name(self):
44
61
  self.assertRaises(ValueError, self._create_atom, residue_names=["something"])
45
62
 
63
+ def test_init_consistency(self):
64
+ loaded = TemplateAtom.loads("ATOM 1 NE ARG A1136 3.953 0.597 -1.721 K")
65
+ created = TemplateAtom(
66
+ chain_id=loaded.chain_id,
67
+ residue_number=loaded.residue_number,
68
+ x=loaded.x,
69
+ y=loaded.y,
70
+ z=loaded.z,
71
+ residue_names=loaded.residue_names,
72
+ atom_names=loaded.atom_names,
73
+ distance_weight=loaded.distance_weight,
74
+ match_mode=loaded.match_mode
75
+ )
76
+ for attribute in ("atom_names", "residue_names", "chain_id", "x", "y", "z", "match_mode"):
77
+ self.assertEqual(getattr(loaded, attribute), getattr(created, attribute))
78
+ self.assertEqual(loaded, created)
79
+
46
80
  def test_repr_roundtrip(self):
47
81
  atom = self._create_atom()
48
82
  copy = eval(repr(atom))
49
83
  for attribute in ("atom_names", "residue_names", "chain_id", "x", "y", "z", "match_mode"):
50
- self.assertEqual(getattr(copy, attribute), getattr(atom, attribute))
84
+ self.assertEqual(getattr(copy, attribute), getattr(atom, attribute))
85
+ self.assertEqual(atom, copy)
86
+
87
+ def test_pickle_roundtrip(self):
88
+ atom = self._create_atom()
89
+ copy = pickle.loads(pickle.dumps(atom))
90
+ for attribute in ("atom_names", "residue_names", "chain_id", "x", "y", "z", "match_mode"):
91
+ self.assertEqual(getattr(copy, attribute), getattr(atom, attribute))
92
+ self.assertEqual(atom, copy)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyjess
3
- Version: 0.3.3
3
+ Version: 0.4.1
4
4
  Summary: Cython bindings and Python interface to JESS, a 3D template matching software.
5
5
  Keywords: bioinformatics,structure,template,matching
6
6
  Author-Email: Martin Larralde <martin.larralde@embl.de>
@@ -25,7 +25,7 @@ License: MIT License
25
25
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
26
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
27
  SOFTWARE.
28
- Classifier: Development Status :: 3 - Alpha
28
+ Classifier: Development Status :: 4 - Beta
29
29
  Classifier: Intended Audience :: Developers
30
30
  Classifier: Intended Audience :: Science/Research
31
31
  Classifier: License :: OSI Approved :: MIT License
@@ -1,21 +1,21 @@
1
- pyjess-0.3.3.dist-info/RECORD,,
2
- pyjess-0.3.3.dist-info/WHEEL,sha256=Vqw9orcNesD9-KuxFaWjC_hI-wCEiyMbR5lwnn6Nrrk,116
3
- pyjess-0.3.3.dist-info/METADATA,sha256=wjCjJAgH4YolUjgoysZ9FIIPDcWeXQcNs7MY6nWp1Bc,10786
4
- pyjess-0.3.3.dist-info/licenses/COPYING,sha256=W3hXwpT6UtiSFrO8yeDddZLU5tKIAX238e0N5slPQGA,1098
5
- pyjess/_jess.cpython-312-darwin.so,sha256=p8lu0QGZh0yF48E7nHc7gL0z1GLSTMWHooH8XDkfzF4,276504
6
- pyjess/_jess.pyi,sha256=kpvEKrznUbXs-q8RDiIWZmX8y55AE_ugrGR45a4SpD0,5849
1
+ pyjess-0.4.1.dist-info/RECORD,,
2
+ pyjess-0.4.1.dist-info/WHEEL,sha256=Vqw9orcNesD9-KuxFaWjC_hI-wCEiyMbR5lwnn6Nrrk,116
3
+ pyjess-0.4.1.dist-info/METADATA,sha256=iHTTiJz29i7uRreAEeB_ow4-DArxSGb5Nz2Y9N4RblQ,10785
4
+ pyjess-0.4.1.dist-info/licenses/COPYING,sha256=W3hXwpT6UtiSFrO8yeDddZLU5tKIAX238e0N5slPQGA,1098
5
+ pyjess/_jess.cpython-312-darwin.so,sha256=sZ3A5-0GA_fOJVkkFESSJavV_k-DUKWh3XIJu0lggeA,313944
6
+ pyjess/_jess.pyi,sha256=QIQSeQgNG-w0QZTuvacHjnv3IlEfV-3AOll9To3ZdM4,6899
7
7
  pyjess/CMakeLists.txt,sha256=Oa0pniEQx9jXyFCJGyrswn9ahWSSVuW1madyeP6StoI,35
8
8
  pyjess/__init__.py,sha256=h4XXLdS4FnyVa-MBs_k3eZMG1jWxeiOJnwfBaJA9gyQ,745
9
9
  pyjess/.gitignore,sha256=uQBOufp4v50qn0aZKv6zbSo00cjfB-v9KySog7rlmIU,19
10
- pyjess/_jess.pyx,sha256=EV6aLunqq8lmqXd5o0p_04YN6LmZJx8Td76Ep44ZSus,41745
10
+ pyjess/_jess.pyx,sha256=E7D4tvgjnGo9q-YdWGdO-r472NdJanUEs8hYbB6rBaM,47303
11
11
  pyjess/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  pyjess/tests/__init__.py,sha256=MdHWtr6A8S4TBWlkoj4olFK2FXAGc5uJdbWtgFrDLpk,528
13
- pyjess/tests/test_molecule.py,sha256=i9nWe7RRw06hND9rtGUkjIHzfAz3ZVKH0GVT1PjfK04,3407
13
+ pyjess/tests/test_molecule.py,sha256=RHBE0yL_j71nQOwXJ6t_PzkZi4BaFPY5Q0VwKWM6elk,5311
14
14
  pyjess/tests/utils.py,sha256=dsaphex7qomJCvSHWnVy79iYDPGiL59xqGAtRoVAeWc,196
15
- pyjess/tests/test_atom.py,sha256=RziIlTWteOVawhbRPTmcBDGjf6FrSlInqnMQLR9UDQY,2450
16
- pyjess/tests/test_jess.py,sha256=bWPsEGcNqr3gjj6iWqdwGX8IT5jaTTkTexOKhMPHWvw,7619
17
- pyjess/tests/test_template.py,sha256=WM5DRvFxa8NEUl_U4PWppsFq2Dw6pZQskbnCu6jgZm8,3465
18
- pyjess/tests/test_template_atom.py,sha256=diQ9TCsStLeUkk4lUHkL-oQZWgMzDhW_LMnjik5MM1Y,1781
15
+ pyjess/tests/test_atom.py,sha256=omNznNbRXMDt2j1plAUlfWPGCfmtkYpj2jysEX1zQuY,4631
16
+ pyjess/tests/test_jess.py,sha256=mgHx6kDW5yzdvZiiybAVrd1J4k3Li3r-t4le6RFEE5o,9757
17
+ pyjess/tests/test_template.py,sha256=XMLELYRB4j7xavziZ-ntq15PjhNHNfJJkctUq9BkvEI,4541
18
+ pyjess/tests/test_template_atom.py,sha256=s9tJ_SAgvKeGwbVjaTWY-EtsUeQp3eu4NF5ja3oO_84,3405
19
19
  pyjess/tests/data/pdb1lnb.pdb,sha256=E9Jjy4qQ75O1UKIXcVyVJHE1XDNx1Rb7ENPVrehW6N8,270054
20
20
  pyjess/tests/data/1AMY.pdb,sha256=t2CaGLdOyPrhyeMpe1TbwZ8u7QmfxCIG1Pit8-vzvgo,319221
21
21
  pyjess/tests/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
File without changes