diff --git a/modules/conop/src/CMakeLists.txt b/modules/conop/src/CMakeLists.txt
index c2f1f4c6d0148c62461be5852bd33872ad39f903..b45b11272b79a090210c3a3d7134676d83fcbc05 100644
--- a/modules/conop/src/CMakeLists.txt
+++ b/modules/conop/src/CMakeLists.txt
@@ -50,7 +50,7 @@ if (COMPOUND_LIB)
     install(FILES ${TO} DESTINATION "share/openstructure")
   else()
     message(FATAL_ERROR "${COMPOUND_LIB} does not exist. \n"
-            "Pass -DCOMPOUND_LIB=NONE if you would like to build "
+            "Pass -DCOMPOUND_LIB=OFF if you would like to build "
             "OpenStructure without a compound library")
   endif()
 
diff --git a/modules/geom/pymod/export_vec3.cc b/modules/geom/pymod/export_vec3.cc
index 7691c59ac0090a17a0aa79394ac24aada3686eb8..445b8aef781d463d31b162af60d7811334f1f4fa 100644
--- a/modules/geom/pymod/export_vec3.cc
+++ b/modules/geom/pymod/export_vec3.cc
@@ -127,5 +127,7 @@ void export_Vec3()
     .def("GetGDTHA", &Vec3List::GetGDTHA, (arg("other"), arg("norm")=true))
     .def("GetGDTTS", &Vec3List::GetGDTTS, (arg("other"), arg("norm")=true))
     .def("GetGDT", &Vec3List::GetGDT, (arg("other"), arg("thresh"), arg("norm")=true))
+    .def("GetMinDist", &Vec3List::GetMinDist, (arg("other")))
+    .def("IsWithin", &Vec3List::IsWithin, (arg("other"), arg("dist")))
   ;
 }
diff --git a/modules/geom/src/vec3.cc b/modules/geom/src/vec3.cc
index 0adbc1a65d2ab414234ca9769374fe066cf9bbd2..bb3a0617e6cf59a3226bc2da6a686a52078457ca 100644
--- a/modules/geom/src/vec3.cc
+++ b/modules/geom/src/vec3.cc
@@ -198,6 +198,28 @@ Real Vec3List::GetGDT(const Vec3List& other, Real thresh, bool norm) const
   return norm && !this->empty() ? static_cast<Real>(n)/(this->size()) : n;
 }
 
+Real Vec3List::GetMinDist(const Vec3List& other) const {
+  Real min = std::numeric_limits<Real>::max();
+  for(size_t i = 0; i < this->size(); ++i) {
+    for(size_t j = 0; j < other.size(); ++j) {
+      min = std::min(min, geom::Length2((*this)[i] - other[j]));
+    }
+  }
+  return std::sqrt(min);
+}
+
+bool Vec3List::IsWithin(const Vec3List& other, Real dist) const {
+  Real squared_dist = dist*dist;
+  for(size_t i = 0; i < this->size(); ++i) {
+    for(size_t j = 0; j < other.size(); ++j) {
+      if(geom::Length2((*this)[i] - other[j]) <= squared_dist) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 std::pair<Line3, Real> Vec3List::FitCylinder(const Vec3& initial_direction) const
   { 
     Vec3 center=this->GetCenter();
diff --git a/modules/geom/src/vec3.hh b/modules/geom/src/vec3.hh
index 8302729061277c0bc5f9fa0b3d20caca36aaaa66..2c0e2bbb2c601f1238f7a5a7948ebfafaba41277 100644
--- a/modules/geom/src/vec3.hh
+++ b/modules/geom/src/vec3.hh
@@ -343,6 +343,8 @@ public:
   Real GetGDTHA(const Vec3List& other, bool norm=true) const;
   Real GetGDTTS(const Vec3List& other, bool norm=true) const;
   Real GetGDT(const Vec3List& other, Real thresh, bool norm=true) const;
+  Real GetMinDist(const Vec3List& other) const;
+  bool IsWithin(const Vec3List& other, Real dist) const;
 
   //This function fits a cylinder to the positions in Vec3List
   //It takes as argument an initial guess for the direction.
diff --git a/modules/mol/alg/doc/molalg.rst b/modules/mol/alg/doc/molalg.rst
index 06c4ef78276b5ce0e29eed88a0b781f95dcbf7fe..5548d1cd951f4e5d11a9f3adba543d0f587ee024 100644
--- a/modules/mol/alg/doc/molalg.rst
+++ b/modules/mol/alg/doc/molalg.rst
@@ -155,6 +155,14 @@ Local Distance Test scores (lDDT, DRMSD)
 .. currentmodule:: ost.mol.alg
 
 
+:mod:`DockQ <ost.mol.alg.dockq>` -- DockQ implementation
+--------------------------------------------------------------------------------
+
+.. autofunction:: ost.mol.alg.dockq.DockQ
+
+.. currentmodule:: ost.mol.alg
+
+
 .. _steric-clashes:
 
 Steric Clashes
diff --git a/modules/mol/alg/pymod/CMakeLists.txt b/modules/mol/alg/pymod/CMakeLists.txt
index e50232556749336687c37a16804da393240066ce..9c65a6b87cf2b330b53868278135f62e473d036b 100644
--- a/modules/mol/alg/pymod/CMakeLists.txt
+++ b/modules/mol/alg/pymod/CMakeLists.txt
@@ -28,6 +28,7 @@ set(OST_MOL_ALG_PYMOD_MODULES
   chain_mapping.py
   stereochemistry.py
   ligand_scoring.py
+  dockq.py
 )
 
 if (NOT ENABLE_STATIC)
diff --git a/modules/mol/alg/pymod/dockq.py b/modules/mol/alg/pymod/dockq.py
new file mode 100644
index 0000000000000000000000000000000000000000..c22ca78d21746de90900b2a1f539d931c8cc65a8
--- /dev/null
+++ b/modules/mol/alg/pymod/dockq.py
@@ -0,0 +1,234 @@
+from ost import geom
+from ost import mol
+
+def _PreprocessStructures(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2,
+                          ch1_aln = None, ch2_aln = None):
+    """ Preprocesses *mdl* and *ref*
+
+    Returns two entity views with the exact same number of residues. I.e. the
+    residues correspond to a one-to-one mapping. Additionally, each residue gets
+    the int property "dockq_map" assigned, which corresponds to the residue
+    index in the respective chain of the processed structures.
+    """
+    mdl_residues_1 = list()
+    mdl_residues_2 = list()
+    ref_residues_1 = list()
+    ref_residues_2 = list()
+
+    if ch1_aln is None and ch2_aln is None:
+        # go by residue numbers
+        for mdl_r in mdl.Select(f"cname={mdl_ch1}").residues:
+            ref_r = ref.FindResidue(ref_ch1, mdl_r.GetNumber())
+            if ref_r.IsValid():
+                mdl_residues_1.append(mdl_r)
+                ref_residues_1.append(ref_r)
+        for mdl_r in mdl.Select(f"cname={mdl_ch2}").residues:
+            ref_r = ref.FindResidue(ref_ch2, mdl_r.GetNumber())
+            if ref_r.IsValid():
+                mdl_residues_2.append(mdl_r)
+                ref_residues_2.append(ref_r)
+    else:
+        raise NotImplementedError("No aln mapping implemented yet")
+
+    new_mdl = mdl.handle.CreateEmptyView()
+    new_ref = ref.handle.CreateEmptyView()
+    for r in mdl_residues_1:
+        new_mdl.AddResidue(r.handle, mol.INCLUDE_ALL)
+    for r in mdl_residues_2:
+        new_mdl.AddResidue(r.handle, mol.INCLUDE_ALL)
+    for r in ref_residues_1:
+        new_ref.AddResidue(r.handle, mol.INCLUDE_ALL)
+    for r in ref_residues_2:
+        new_ref.AddResidue(r.handle, mol.INCLUDE_ALL)
+
+    # set dockq_map property
+    ch = new_mdl.FindChain(mdl_ch1)
+    for r_idx, r in enumerate(ch.residues):
+        r.SetIntProp("dockq_map", r_idx)
+    ch = new_mdl.FindChain(mdl_ch2)
+    for r_idx, r in enumerate(ch.residues):
+        r.SetIntProp("dockq_map", r_idx)
+    ch = new_ref.FindChain(ref_ch1)
+    for r_idx, r in enumerate(ch.residues):
+        r.SetIntProp("dockq_map", r_idx)
+    ch = new_ref.FindChain(ref_ch2)
+    for r_idx, r in enumerate(ch.residues):
+        r.SetIntProp("dockq_map", r_idx)
+
+    return (new_mdl, new_ref)
+
+def _GetContacts(ent, ch1, ch2, dist_thresh):
+    int1 = ent.Select(f"cname={ch1} and {dist_thresh} <> [cname={ch2}]")
+    int2 = ent.Select(f"cname={ch2} and {dist_thresh} <> [cname={ch1}]")
+    contacts = set()
+    int1_p = [geom.Vec3List([a.pos for a in r.atoms]) for r in int1.residues]
+    int2_p = [geom.Vec3List([a.pos for a in r.atoms]) for r in int2.residues]
+    for r1, p1 in zip(int1.residues, int1_p):
+        for r2, p2 in zip(int2.residues, int2_p):
+            if p1.IsWithin(p2, dist_thresh):
+                contacts.add((r1.GetIntProp("dockq_map"), r2.GetIntProp("dockq_map")))
+    return contacts
+
+def _ContactScores(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2, dist_thresh=5.0):
+    ref_contacts = _GetContacts(ref, ref_ch1, ref_ch2, dist_thresh)
+    mdl_contacts = _GetContacts(mdl, mdl_ch1, mdl_ch2, dist_thresh)
+
+    nnat = len(ref_contacts)
+    nmdl = len(mdl_contacts)
+
+    fnat = len(ref_contacts.intersection(mdl_contacts))
+    if nnat > 0:
+        fnat /= nnat
+
+    fnonnat = len(mdl_contacts.difference(ref_contacts))
+    if len(mdl_contacts) > 0:
+        fnonnat /= len(mdl_contacts)
+
+    return (nnat, nmdl, fnat, fnonnat)
+
+def _RMSDScores(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2, dist_thresh=10.0):
+
+    mdl_ch1_residues = mdl.FindChain(mdl_ch1).residues
+    mdl_ch2_residues = mdl.FindChain(mdl_ch2).residues
+    ref_ch1_residues = ref.FindChain(ref_ch1).residues
+    ref_ch2_residues = ref.FindChain(ref_ch2).residues
+
+    # iRMSD
+    #######
+    int1 = ref.Select(f"cname={ref_ch1} and {dist_thresh} <> [cname={ref_ch2}]")
+    int2 = ref.Select(f"cname={ref_ch2} and {dist_thresh} <> [cname={ref_ch1}]")
+    int1_indices = [r.GetIntProp("dockq_map") for r in int1.residues]
+    int2_indices = [r.GetIntProp("dockq_map") for r in int2.residues]
+    ref_pos = geom.Vec3List()
+    mdl_pos = geom.Vec3List()
+    atom_names = ['CA','C','N','O']
+    for idx in int1_indices:
+        ref_r = ref_ch1_residues[idx]
+        mdl_r = mdl_ch1_residues[idx]
+        for aname in atom_names:
+            ref_a = ref_r.FindAtom(aname)
+            mdl_a = mdl_r.FindAtom(aname)
+            if ref_a.IsValid() and mdl_a.IsValid():
+                ref_pos.append(ref_a.pos)
+                mdl_pos.append(mdl_a.pos)
+
+    for idx in int2_indices:
+        ref_r = ref_ch2_residues[idx]
+        mdl_r = mdl_ch2_residues[idx]
+        for aname in atom_names:
+            ref_a = ref_r.FindAtom(aname)
+            mdl_a = mdl_r.FindAtom(aname)
+            if ref_a.IsValid() and mdl_a.IsValid():
+                ref_pos.append(ref_a.pos)
+                mdl_pos.append(mdl_a.pos)
+
+    if len(mdl_pos) >= 3:
+        sup_result = mol.alg.SuperposeSVD(mdl_pos, ref_pos)
+        irmsd = sup_result.rmsd
+    else:
+        irmsd = 0.0
+
+    # lRMSD
+    #######
+    # receptor is by definition the larger chain
+    if len(ref_ch1_residues) > len(ref_ch2_residues):
+        ref_receptor_residues = ref_ch1_residues
+        ref_ligand_residues = ref_ch2_residues
+        mdl_receptor_residues = mdl_ch1_residues
+        mdl_ligand_residues = mdl_ch2_residues
+    else:
+        ref_receptor_residues = ref_ch2_residues
+        ref_ligand_residues = ref_ch1_residues
+        mdl_receptor_residues = mdl_ch2_residues
+        mdl_ligand_residues = mdl_ch1_residues
+
+    ref_receptor_positions = geom.Vec3List()
+    mdl_receptor_positions = geom.Vec3List()
+    ref_ligand_positions = geom.Vec3List()
+    mdl_ligand_positions = geom.Vec3List()
+
+    for ref_r, mdl_r in zip(ref_receptor_residues, mdl_receptor_residues):
+        for aname in atom_names:
+            ref_a = ref_r.FindAtom(aname)
+            mdl_a = mdl_r.FindAtom(aname)
+            if ref_a.IsValid() and mdl_a.IsValid():
+                ref_receptor_positions.append(ref_a.pos)
+                mdl_receptor_positions.append(mdl_a.pos)
+
+    for ref_r, mdl_r in zip(ref_ligand_residues, mdl_ligand_residues):
+        for aname in atom_names:
+            ref_a = ref_r.FindAtom(aname)
+            mdl_a = mdl_r.FindAtom(aname)
+            if ref_a.IsValid() and mdl_a.IsValid():
+                ref_ligand_positions.append(ref_a.pos)
+                mdl_ligand_positions.append(mdl_a.pos)
+
+    if len(mdl_receptor_positions) >= 3:
+        sup_result = mol.alg.SuperposeSVD(mdl_receptor_positions,
+                                          ref_receptor_positions)
+        mdl_ligand_positions.ApplyTransform(sup_result.transformation)
+        lrmsd = mdl_ligand_positions.GetRMSD(ref_ligand_positions)
+    else:
+        lrmsd = 0.0
+
+    return (irmsd, lrmsd)
+
+def _ScaleRMSD(rmsd, d):
+    return 1.0/(1+(rmsd/d)**2)
+
+def _DockQ(fnat, lrmsd, irmsd, d1, d2):
+    """ The final number chrunching as described in the DockQ manuscript
+    """
+    return (fnat + _ScaleRMSD(lrmsd, d1) + _ScaleRMSD(irmsd, d2))/3
+
+def DockQ(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2,
+          ch1_aln=None, ch2_aln=None):
+    """ Computes DockQ for specified interface
+
+    DockQ is described in: Sankar Basu and Bjoern Wallner (2016), "DockQ: A
+    Quality Measure for Protein-Protein Docking Models", PLOS one 
+
+    Residues are mapped based on residue numbers by default. If you provide
+    *ch1_aln* and *ch2_aln* you can enforce an arbitrary mapping.
+
+    :param mdl: Model structure
+    :type mdl: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle`
+    :param ref: Reference structure, i.e. native structure
+    :type ref: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle`
+    :param mdl_ch1: Specifies chain in model constituting first part of
+                    interface
+    :type mdl_ch1: :class:`str`
+    :param mdl_ch2: Specifies chain in model constituting second part of
+                    interface
+    :type mdl_ch2: :class:`str`
+    :param ref_ch1: ref equivalent of mdl_ch1
+    :type ref_ch1: :class:`str`
+    :param ref_ch2: ref equivalent of mdl_ch2
+    :type ref_ch2: :class:`str`
+    :param ch1_aln: Alignment with two sequences to map *ref_ch1* and *mdl_ch1*.
+                    The first sequence must match the sequence in *ref_ch1* and
+                    the second to *mdl_ch1*.
+    :type ch1_aln: :class:`ost.seq.AlignmentHandle`
+    :param ch2_aln: Alignment with two sequences to map *ref_ch2* and *mdl_ch2*.
+                    The first sequence must match the sequence in *ref_ch2* and
+                    the second to *mdl_ch2*.
+    :type ch2_aln: :class:`ost.seq.AlignmentHandle`
+    :returns: :class:`dict` with keys nnat, nmdl, fnat, fnonnat, irmsd, lrmsd,
+              DockQ which corresponds to the equivalent values in the original
+              DockQ implementation.
+    """
+    mapped_model, mapped_ref = _PreprocessStructures(mdl, ref, mdl_ch1, mdl_ch2,
+                                                     ref_ch1, ref_ch2,
+                                                     ch1_aln = ch1_aln,
+                                                     ch2_aln = ch2_aln)
+    nnat, nmdl, fnat, fnonnat = _ContactScores(mapped_model, mapped_ref,
+                                         mdl_ch1, mdl_ch2, ref_ch1, ref_ch2)
+    irmsd, lrmsd = _RMSDScores(mapped_model, mapped_ref,
+                               mdl_ch1, mdl_ch2, ref_ch1, ref_ch2)
+    return {"nnat": nnat,
+            "nmdl": nmdl,
+            "fnat": fnat,
+            "fnonnat": fnonnat,
+            "irmsd": round(irmsd, 3),
+            "lrmsd": round(lrmsd, 3),
+            "DockQ": round(_DockQ(fnat, lrmsd, irmsd, 8.5, 1.5), 3)}
diff --git a/modules/mol/alg/pymod/scoring.py b/modules/mol/alg/pymod/scoring.py
index 359aa0e67b9dbe6f131d074610219a434e162f15..1cb97530eadd1f90f30a31b25df4405c798cf7b2 100644
--- a/modules/mol/alg/pymod/scoring.py
+++ b/modules/mol/alg/pymod/scoring.py
@@ -9,10 +9,10 @@ from ost.mol.alg import lddt
 from ost.mol.alg import qsscore
 from ost.mol.alg import chain_mapping
 from ost.mol.alg import stereochemistry
+from ost.mol.alg import dockq
 from ost.mol.alg.lddt import lDDTScorer
 from ost.mol.alg.qsscore import QSScorer
 from ost.mol.alg import Molck, MolckSettings
-from ost.bindings import dockq
 from ost.bindings import cadscore
 import numpy as np
 
@@ -114,11 +114,6 @@ class Scorer:
                                        to optimize for QS-score. Everything
                                        above is treated with a heuristic.
     :type naive_chain_mapping_thresh: :class:`int` 
-    :param dockq_exec: Explicit path to DockQ.py script from DockQ installation
-                       from https://github.com/bjornwallner/DockQ. If not given,
-                       DockQ.py must be in PATH if any of the DockQ related
-                       attributes is requested.
-    :type dockq_exec: :class:`str`
     :param cad_score_exec: Explicit path to voronota-cadscore executable from
                            voronota installation from 
                            https://github.com/kliment-olechnovic/voronota. If
@@ -128,7 +123,7 @@ class Scorer:
     """
     def __init__(self, model, target, resnum_alignments=False,
                  molck_settings = None, naive_chain_mapping_thresh=12,
-                 dockq_exec = None, cad_score_exec = None):
+                 cad_score_exec = None):
 
         if isinstance(model, mol.EntityView):
             self._model = mol.CreateEntityFromView(model, False)
@@ -183,7 +178,6 @@ class Scorer:
         Molck(self._target, conop.GetDefaultLib(), molck_settings)
         self.resnum_alignments = resnum_alignments
         self.naive_chain_mapping_thresh = naive_chain_mapping_thresh
-        self.dockq_exec = dockq_exec
         self.cad_score_exec = cad_score_exec
 
         # lazily evaluated attributes
@@ -868,12 +862,6 @@ class Scorer:
                                "that are consistent between target and model "
                                "chains, i.e. only work if resnum_alignments "
                                "is True at Scorer construction.")
-        try:
-            dockq_exec = settings.Locate("DockQ.py",
-                                         explicit_file_name=self.dockq_exec)
-        except Exception as e:
-            raise RuntimeError("DockQ.py must be in PATH for DockQ "
-                               "scoring") from e
 
         flat_mapping = self.mapping.GetFlatMapping()
         # list of [trg_ch1, trg_ch2, mdl_ch1, mdl_ch2]
@@ -896,38 +884,22 @@ class Scorer:
             if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping:
                 mdl_ch1 = flat_mapping[trg_ch1]
                 mdl_ch2 = flat_mapping[trg_ch2]
-                try:
-                    res = dockq.DockQ(dockq_exec, self.model, self.target,
-                                      mdl_ch1, mdl_ch2, trg_ch1, trg_ch2)
-                except Exception as e:
-                    if "AssertionError: length of native is zero" in str(e):
-                        # happens if target interface has no native contacts
-                        continue
-                    else:
-                        raise
-
-                if res.native_contacts > 0:
+                res = dockq.DockQ(self.model, self.target, mdl_ch1, mdl_ch2,
+                                  trg_ch1, trg_ch2)
+                if res["nnat"] > 0:
                     self._dockq_interfaces.append((trg_ch1, trg_ch2,
                                                    mdl_ch1, mdl_ch2))
-                    self._dockq_native_contacts.append(res.native_contacts)
-                    self._dockq_scores.append(res.DockQ)
+                    self._dockq_native_contacts.append(res["nnat"])
+                    self._dockq_scores.append(res["DockQ"])
             else:
                 # interface which is not covered by mdl... let's run DockQ with
                 # trg as trg/mdl in order to get the native contacts out
-                try:
-                    res = dockq.DockQ(dockq_exec, self.target, self.target,
-                                      trg_ch1, trg_ch2, trg_ch1, trg_ch2)
-                    counts = res.native_contacts
-                    if counts > 0:
-                        self._dockq_nonmapped_interfaces.append((trg_ch1,
-                                                                 trg_ch2))
-                        self._dockq_nonmapped_interfaces_counts.append(counts)
-                except Exception as e:
-                    if "AssertionError: length of native is zero" in str(e):
-                        # happens if target interface has no native contacts
-                        continue
-                    else:
-                        raise
+                res = dockq.DockQ(self.target, self.target,
+                                  trg_ch1, trg_ch2, trg_ch1, trg_ch2)
+                if res["nnat"] > 0:
+                    self._dockq_nonmapped_interfaces.append((trg_ch1,
+                                                             trg_ch2))
+                    self._dockq_nonmapped_interfaces_counts.append(res["nnat"])
 
         # there are 4 types of combined scores
         # - simple average
@@ -1268,27 +1240,12 @@ class Scorer:
         :type trg_patch_two: :class:`ost.mol.EntityView`
         :returns: DockQ score
         """
-        if not self.resnum_alignments:
-            raise RuntimeError("DockQ computations rely on residue numbers "
-                               "that are consistent between target and model "
-                               "chains, i.e. only work if resnum_alignments "
-                               "is True at Scorer construction.")
-        try:
-            dockq_exec = settings.Locate("DockQ.py",
-                                         explicit_file_name=self.dockq_exec)
-        except Exception as e:
-            raise RuntimeError("DockQ.py must be in PATH for DockQ "
-                               "scoring") from e
         m = self._qs_ent_from_patches(mdl_patch_one, mdl_patch_two)
         t = self._qs_ent_from_patches(trg_patch_one, trg_patch_two)
-        try:
-            dockq_result = dockq.DockQ(dockq_exec, t, m, "A", "B", "A", "B")
-        except Exception as e:
-            if "AssertionError: length of native is zero" in str(e):
-                return 0.0
-            else:
-                raise
-        return dockq_result.DockQ
+        dockq_result = dockq.DockQ(t, m, "A", "B", "A", "B")
+        if dockq_result["nnat"] > 0:
+            return dockq_result["DockQ"]
+        return 0.0
 
     def _qs_ent_from_patches(self, patch_one, patch_two):
         """ Constructs Entity with two chains named "A" and "B""
diff --git a/modules/mol/base/doc/entity.rst b/modules/mol/base/doc/entity.rst
index 5f56ccec19790ab02cd68b242e11b11782244a69..a0946b7f4bab7248565cac5dd8980dc1ab54b431 100644
--- a/modules/mol/base/doc/entity.rst
+++ b/modules/mol/base/doc/entity.rst
@@ -999,7 +999,7 @@ The Handle Classes
     
     See :attr:`bonds`
     
-    :rtype: :class:`BondHandleList`
+    :rtype: :class:`BondHandleList` (list of :class:`BondHandle`)
 
   .. method:: GetBondPartners()
     
@@ -1097,7 +1097,106 @@ The Handle Classes
   
     See :attr:`valid`
 
-  
+
+.. class:: BondHandle
+
+  Represents a chemical bond between two atoms (first and second).
+
+  .. attribute:: first
+  .. attribute:: second
+
+    Atoms involved in the bond. No assumptions about the order should be made.
+    With the internal coordinate system enabled, first and second may even be
+    swapped when rebuilding the internal connectivity tree. Also available as
+    :meth:`GetFirst` and :meth:`GetSecond`.
+
+    :type: :class:`AtomHandle`
+
+  .. attribute:: pos
+
+    Midpoint between the two atoms (transformed coordinates). Also available as
+    :meth:`GetPos`.
+
+    :type: :class:`~ost.geom.Vec3`
+
+  .. attribute:: length
+
+    Length of the bond. Also available as :meth:`GetLength`.
+
+    :type: float
+
+  .. attribute:: bond_order
+
+    The bond order. Possible values:
+
+      * ``1`` - single bond
+      * ``2`` - double bond
+      * ``3`` - triple bond
+      * ``4`` - aromatic bond
+
+    Also available as :meth:`GetBondOrder`.
+
+    :type: int
+
+  .. attribute:: hash_code
+
+    A unique identifier for this bond handle.  Also available as :meth:`GetHashCode`.
+
+    :type: int
+
+  .. attribute:: valid
+
+    Validity of handle.  Also available as :meth:`IsValid`.
+
+    :type: bool
+
+  .. method:: GetFirst()
+
+    See :attr:`first`
+
+  .. method:: GetSecond()
+
+    See :attr:`second`
+
+  .. method:: GetPos()
+
+    See :attr:`pos`
+
+  .. method:: GetLength()
+
+    See :attr:`length`
+
+  .. method:: GetBondOrder()
+
+    See :attr:`bond_order`
+
+  .. method:: GetOther(other_atom)
+
+    Get the other atom. Returns the one of the two atoms that does not match
+    the given one.
+
+    :param other_atom: The other atom
+    :type  other_atom: :class:`AtomHandle`
+    :rtype: :class:`AtomHandle`
+
+  .. method:: SetBondOrder(order)
+
+    Set the bond order. See :meth:`GetBondOrder`.
+
+    :param order: The bond order
+    :type  order: :class:`int`
+
+    See :attr:`bond_order`
+
+  .. method:: IsValid()
+
+    See :attr:`valid`
+
+  .. method:: GetHashCode()
+
+    See :attr:`hash_code`
+
+
 The View Classes
 --------------------------------------------------------------------------------
 
@@ -1411,7 +1510,7 @@ The View Classes
 
     See :attr:`bonds`
     
-    :rtype: :class:`BondHandleList`
+    :rtype: :class:`BondHandleList` (list of :class:`BondHandle`)
 
   .. method:: GetHandle()
   
diff --git a/modules/mol/base/pymod/export_bond.cc b/modules/mol/base/pymod/export_bond.cc
index 5dc6e6503fdc9ce9038e595fd3a78384c1e37d56..a972ce175dde361423a6ecbc5b59403749c84d6a 100644
--- a/modules/mol/base/pymod/export_bond.cc
+++ b/modules/mol/base/pymod/export_bond.cc
@@ -44,17 +44,20 @@ void export_Bond()
     .add_property("bond_order",
                   &BondHandle::GetBondOrder,
                   &BondHandle::SetBondOrder)
+    .add_property("valid", &BondHandle::IsValid)
     .def("GetFirst", &BondHandle::GetFirst)    
     .def("GetSecond",&BondHandle::GetSecond)
-    .def("GetOther",&BondHandle::GetOther)
+    .def("GetOther",&BondHandle::GetOther, arg("other_atom"))
     .def("GetLength",&BondHandle::GetLength)
     .def("GetBondOrder",&BondHandle::GetBondOrder)
-    .def("SetBondOrder",&BondHandle::SetBondOrder)
+    .def("SetBondOrder",&BondHandle::SetBondOrder, arg("order"))
     .def("IsValid", &BondHandle::IsValid)       
     .def("GetHashCode", &BondHandle::GetHashCode)
     .def(self == self)
     .def(self != self)
     .def(self_ns::str(self))
+    .def("__hash__", &BondHandle::GetHashCode)
+    .add_property("hash_code", &BondHandle::GetHashCode)
   ;    
   generic_prop_def<BondHandle>(bond_handle);
   class_<BondHandleList>("BondHandleList", no_init)
diff --git a/modules/mol/base/src/bond_handle.hh b/modules/mol/base/src/bond_handle.hh
index 76acfff537e1a6cbf09eea762cfd69fd78209dc2..26a9df83c87e9213371ffb72f939bca7e49be1f2 100644
--- a/modules/mol/base/src/bond_handle.hh
+++ b/modules/mol/base/src/bond_handle.hh
@@ -76,7 +76,6 @@ public:
 
   /// \brief get other atom
   /// Returns one of the two atoms that does not match the given one.
-  /// In python also available as the property other
   AtomHandle GetOther(const AtomHandle& a) const;
   //@}