diff --git a/modules/io/pymod/CMakeLists.txt b/modules/io/pymod/CMakeLists.txt
index ead77d54b9d4cc0285845a166a52287774d73f67..02b392754d6b44ec9948b621ef86903ebf3ce060 100644
--- a/modules/io/pymod/CMakeLists.txt
+++ b/modules/io/pymod/CMakeLists.txt
@@ -4,6 +4,7 @@ set(OST_IO_PYMOD_SOURCES
   export_mmcif_io.cc
   export_omf_io.cc
   export_map_io.cc
+  export_sdf_io.cc
 )
 
 set(OST_IO_PYMOD_MODULES
diff --git a/modules/io/pymod/__init__.py b/modules/io/pymod/__init__.py
index ea3be23a85b688a37946a55d298681aa5472979d..dca962c0bcd74611721a5e439e28d6eb1511f2d0 100644
--- a/modules/io/pymod/__init__.py
+++ b/modules/io/pymod/__init__.py
@@ -556,3 +556,46 @@ def _PDBize(biounit, asu, seqres=None, min_polymer_size=None,
   return pdb_bu
 
 MMCifInfoBioUnit.PDBize = _PDBize
+
+
+def LoadSDF(filename, fault_tolerant=None, profile='DEFAULT'):
+  """
+  Load SDF file from disk and return an entity.
+
+  :param filename: File to be loaded
+  :type filename: :class:`str`
+
+  :param fault_tolerant: Enable/disable fault-tolerant import. If set, overrides
+                         the value of :attr:`IOProfile.fault_tolerant`.
+  :type fault_tolerant: :class:`bool`
+
+  :param profile: Aggregation of flags and algorithms to control import and
+                  processing of molecular structures. Can either be a
+                  :class:`str` specifying one of the default profiles
+                  ['DEFAULT', 'SLOPPY', 'CHARMM', 'STRICT'] or an actual object
+                  of type :class:`ost.io.IOProfile`.
+                  See :doc:`profile` for more info.
+  :type profile: :class:`str`/:class:`ost.io.IOProfile`
+
+  :raises: :exc:`~ost.io.IOException` if the import fails due to an erroneous or
+      inexistent file
+  """
+  def _override(val1, val2):
+    if val2 != None:
+      return val2
+    else:
+      return val1
+
+  if isinstance(profile, str):
+    prof = profiles.Get(profile)
+  elif isinstance(profile, IOProfile):
+    prof = profile.Copy()
+  else:
+    raise TypeError('profile must be of type string or IOProfile, ' + \
+                    'instead of %s' % type(profile))
+  prof.fault_tolerant = _override(prof.fault_tolerant, fault_tolerant)
+
+  reader = SDFReader(filename, prof)
+  ent = mol.CreateEntity()
+  reader.Import(ent)
+  return ent
diff --git a/modules/io/pymod/export_sdf_io.cc b/modules/io/pymod/export_sdf_io.cc
new file mode 100644
index 0000000000000000000000000000000000000000..08de790e2850229a568920c790767992491c075c
--- /dev/null
+++ b/modules/io/pymod/export_sdf_io.cc
@@ -0,0 +1,64 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2020 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+#include <boost/python.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
+using namespace boost::python;
+
+#include <ost/export_helper/pair_to_tuple_conv.hh>
+#include <ost/io/mol/entity_io_sdf_handler.hh>
+#include <ost/io/mol/io_profile.hh>
+#include <ost/io/mol/sdf_reader.hh>
+#include <ost/io/mol/sdf_writer.hh>
+#include <ost/io/sdf_str.hh>
+using namespace ost;
+using namespace ost::io;
+using namespace ost::mol;
+
+String (*sdf_str_a)(const mol::EntityHandle&)=&EntityToSDFString;
+String (*sdf_str_b)(const mol::EntityView&)=&EntityToSDFString;
+
+void (*save_sdf_handle)(const mol::EntityHandle& entity, const String& filename)=&SaveSDF;
+void (*save_sdf_view)(const mol::EntityView& entity, const String& filename)=&SaveSDF;
+
+void (SDFWriter::*write_handle)(const mol::EntityHandle&)=&SDFWriter::Write;
+void (SDFWriter::*write_view)(const mol::EntityView&)=&SDFWriter::Write;
+
+void export_sdf_io()
+{
+  class_<SDFReader, boost::noncopyable>("SDFReader", init<String, const IOProfile&>())
+    .def("Import", &SDFReader::Import)
+  ;
+  
+  class_<SDFWriter, boost::noncopyable>("SDFWriter", init<String>())
+    .def("Write", write_handle)
+    .def("Write", write_view)
+  ;
+
+  // def("LoadSDF", &LoadSDF);
+  def("SaveSDF", save_sdf_view);
+  def("SaveSDF", save_sdf_handle);
+
+  def("EntityToSDFStr", sdf_str_a);
+  def("EntityToSDFStr", sdf_str_b);
+
+  def("SDFStrToEntity", &SDFStringToEntity, (arg("SDF_string"),
+                                             arg("profile")=IOProfile()));
+}
diff --git a/modules/io/pymod/wrap_io.cc b/modules/io/pymod/wrap_io.cc
index bae73b1e0fd9168c7673fc39da52ce678d6878d9..b7cade00c33e39eefd2ecc982b7e2c4e2d5a2a6f 100644
--- a/modules/io/pymod/wrap_io.cc
+++ b/modules/io/pymod/wrap_io.cc
@@ -30,9 +30,7 @@ using namespace boost::python;
 #include <ost/io/mol/entity_io_crd_handler.hh>
 #include <ost/io/mol/entity_io_pqr_handler.hh>
 #include <ost/io/mol/entity_io_mae_handler.hh>
-#include <ost/io/mol/entity_io_sdf_handler.hh>
 #include <ost/io/mol/pdb_reader.hh>
-#include <ost/io/mol/sdf_str.hh>
 #include <ost/io/mol/dcd_io.hh>
 #include <ost/io/stereochemical_params_reader.hh>
 using namespace ost;
@@ -66,18 +64,13 @@ BOOST_PYTHON_FUNCTION_OVERLOADS(save_entity_view_ov,
 ost::mol::alg::StereoChemicalProps (*read_props_a)(String filename, bool check) = &ReadStereoChemicalPropsFile;
 ost::mol::alg::StereoChemicalProps (*read_props_b)(bool check) = &ReadStereoChemicalPropsFile;
 
-String (*sdf_str_a)(const mol::EntityHandle&)=&EntityToSDFString;
-String (*sdf_str_b)(const mol::EntityView&)=&EntityToSDFString;
-
-void (*save_sdf_handle)(const mol::EntityHandle& entity, const String& filename)=&SaveSDF;
-void (*save_sdf_view)(const mol::EntityView& entity, const String& filename)=&SaveSDF;
-
 }
 
 void export_pdb_io();
 void export_mmcif_io();
 void export_omf_io();
 void export_map_io();
+void export_sdf_io();
 BOOST_PYTHON_MODULE(_ost_io)
 {
   class_<IOManager, boost::noncopyable>("IOManager", no_init)
@@ -122,14 +115,6 @@ BOOST_PYTHON_MODULE(_ost_io)
       (arg("seq_list"), arg("filename"), arg("format")="auto"));
   def("SaveSequence", &SaveSequence,
       (arg("sequence"), arg("filename"), arg("format")="auto"));
-  def("LoadSDF", &LoadSDF);
-  def("SaveSDF", save_sdf_view);
-  def("SaveSDF", save_sdf_handle);
-
-  def("EntityToSDFStr", sdf_str_a);
-  def("EntityToSDFStr", sdf_str_b);
-
-  def("SDFStrToEntity", &SDFStringToEntity);
 
   def("LoadCRD", &LoadCRD);
   def("LoadCHARMMTraj_", &LoadCHARMMTraj, (arg("ent"), arg("trj_filename"), 
@@ -149,6 +134,7 @@ BOOST_PYTHON_MODULE(_ost_io)
   export_mmcif_io();
   export_omf_io();
   export_map_io();
+  export_sdf_io();
   def("SaveCHARMMTraj", &SaveCHARMMTraj, 
       (arg("traj"), arg("pdb_filename"), arg("dcd_filename"), arg("stride")=1, 
        arg("profile")=IOProfile()));
diff --git a/modules/io/src/mol/entity_io_sdf_handler.cc b/modules/io/src/mol/entity_io_sdf_handler.cc
index 129ce2c86be7a7ec56e1cbee52488a231411067a..cf1ff10c6bd1ec12fb1138e58686c2d839103b59 100644
--- a/modules/io/src/mol/entity_io_sdf_handler.cc
+++ b/modules/io/src/mol/entity_io_sdf_handler.cc
@@ -21,6 +21,7 @@
  */
 
 #include <ost/log.hh>
+#include <ost/profile.hh>
 #include <ost/io/mol/sdf_writer.hh>
 #include <ost/io/mol/sdf_reader.hh>
 
@@ -37,14 +38,14 @@ bool EntityIOSDFHandler::RequiresProcessor() const
 void EntityIOSDFHandler::Import(mol::EntityHandle& ent,
                                 std::istream& instream)
 {
-  SDFReader reader(instream);
+  SDFReader reader(instream, IOProfileRegistry::Instance().GetDefault());
   reader.Import(ent);
 }
 
 void EntityIOSDFHandler::Import(mol::EntityHandle& ent,
                                 const boost::filesystem::path& loc)
 {
-  SDFReader reader(loc);
+  SDFReader reader(loc, IOProfileRegistry::Instance().GetDefault());
   reader.Import(ent);
 }
 
diff --git a/modules/io/src/mol/sdf_reader.cc b/modules/io/src/mol/sdf_reader.cc
index 70ee382b1671388ee9a22aab0ca43020364a76f6..097e4e4af0a35e3fa9d99291823a10e9c0d13d8e 100644
--- a/modules/io/src/mol/sdf_reader.cc
+++ b/modules/io/src/mol/sdf_reader.cc
@@ -37,20 +37,20 @@ namespace ost { namespace io {
 
 using boost::format;
 
-SDFReader::SDFReader(const String& filename)
-  : infile_(filename), instream_(infile_)
+SDFReader::SDFReader(const String& filename, const IOProfile& profile)
+  : infile_(filename), instream_(infile_), profile_(profile)
 {
   this->ClearState(boost::filesystem::path(filename));
 }
 
-SDFReader::SDFReader(const boost::filesystem::path& loc)
-  : infile_(loc), instream_(infile_)
+SDFReader::SDFReader(const boost::filesystem::path& loc, const IOProfile& profile)
+  : infile_(loc), instream_(infile_), profile_(profile)
 {
   this->ClearState(loc);
 }
 
-SDFReader::SDFReader(std::istream& instream)
-  : infile_(), instream_(instream)
+SDFReader::SDFReader(std::istream& instream, const IOProfile& profile)
+  : infile_(), instream_(instream), profile_(profile)
 {
   this->ClearState(boost::filesystem::path(""));
 }
@@ -355,15 +355,40 @@ void SDFReader::AddBond(const bond_data& bond_tuple, int line_num, mol::EntityHa
 
   try {
     type=boost::lexical_cast<int>(boost::trim_copy(s_type));
-    if (type<1 || type>8) {
-      String msg="Bad bond line %d: Bond type number"
-                      " '%s' not within accepted range (1-8).";
-      throw IOException(str(format(msg) % line_num % s_type));
+    // From SDF spec:
+    // bond type      1 = Single, 2 = Double,       [Q] Values 4 through 8 are
+    //                3 = Triple, 4 = Aromatic,     for SSS queries oniy.
+    //                5 = Single or Double,
+    //                6 = Single or Aromatic,
+    //                7 = Double or Aromatic,
+    //                8 = Any
+    if (type < 1 || type > 8) {
+      std::stringstream ss;
+      ss << "Bad bond line " << line_num << ": Bond type number "
+         << std::to_string(type) << " not within accepted range (1-8).";
+      if (profile_.fault_tolerant) {
+        LOG_ERROR(ss.str());
+      } else {
+        throw IOException(ss.str());
+      }
+    } else if (type > 3) {
+      std::stringstream ss;
+      ss << "Bad bond line " << line_num << ": Bond type number "
+         << std::to_string(type) << ": values 4-8 are reserved for queries, "
+         << "should not appear in an SDF file.";
+        LOG_WARNING(ss.str());
     }
   } catch(boost::bad_lexical_cast&) {
-    String msg="Bad bond line %d: Can't convert bond type number"
-                " '%s' to integral constant.";
-    throw IOException(str(format(msg) % line_num % s_type));
+    std::stringstream ss;
+    ss << "Bad bond line " << line_num << ": Can't convert bond type number '"
+       << s_type << "' to integral constant.";
+    if (profile_.fault_tolerant) {
+      ss << " Assuming single bond in fault tolerant mode.";
+      LOG_ERROR(ss.str());
+      type = 1;
+    } else {
+      throw IOException(ss.str());
+    }
   }
 
   mol::AtomHandle first,second;
diff --git a/modules/io/src/mol/sdf_reader.hh b/modules/io/src/mol/sdf_reader.hh
index 59e733a937a3ed0dcb7171deec9ff9a32dd808ae..d524a8bf1561323dcbb2b91c10c70097673b352b 100644
--- a/modules/io/src/mol/sdf_reader.hh
+++ b/modules/io/src/mol/sdf_reader.hh
@@ -28,6 +28,7 @@
 #include <ost/mol/chain_handle.hh>
 #include <ost/mol/residue_handle.hh>
 #include <ost/io/module_config.hh>
+#include <ost/io/mol/io_profile.hh>
 
 namespace ost { namespace io {
 
@@ -36,11 +37,9 @@ namespace ost { namespace io {
 
 class DLLEXPORT_OST_IO SDFReader {
 public:
-  SDFReader(const String& filename);
-  SDFReader(const boost::filesystem::path& loc);
-  SDFReader(std::istream& instream);
-
-  bool HasNext();
+  SDFReader(const String& filename, const IOProfile& profile);
+  SDFReader(const boost::filesystem::path& loc, const IOProfile& profile);
+  SDFReader(std::istream& instream, const IOProfile& profile);
 
   void Import(mol::EntityHandle& ent);
 
@@ -97,6 +96,7 @@ private:
   boost::filesystem::ifstream infile_;
   std::istream& instream_;
   boost::iostreams::filtering_stream<boost::iostreams::input>  in_;
+  IOProfile profile_;
   String version_;
   bool v3000_atom_block_;
   bool v3000_bond_block_;
diff --git a/modules/io/src/mol/sdf_str.cc b/modules/io/src/mol/sdf_str.cc
index a2977c432bf3ec90647de1b05dc8eb5a2afb3f74..0441b672f0edbc3604888cec50abc35f75e0e54b 100644
--- a/modules/io/src/mol/sdf_str.cc
+++ b/modules/io/src/mol/sdf_str.cc
@@ -37,9 +37,9 @@ String EntityToSDFString(const mol::EntityView& ent) {
   return stream.str();
 }
 
-mol::EntityHandle SDFStringToEntity(const String& sdf) {
+mol::EntityHandle SDFStringToEntity(const String& sdf, const IOProfile& profile) {
   std::stringstream stream(sdf);
-  SDFReader reader(stream);
+  SDFReader reader(stream, profile);
   mol::EntityHandle ent = mol::CreateEntity();
   reader.Import(ent);
   return ent;
diff --git a/modules/io/src/mol/sdf_str.hh b/modules/io/src/mol/sdf_str.hh
index 87987679ed7ad28a6e3023f536ad6c6fb716f7f7..8cc6974593a202be792cc8fcf788fae6f9b780df 100644
--- a/modules/io/src/mol/sdf_str.hh
+++ b/modules/io/src/mol/sdf_str.hh
@@ -22,6 +22,7 @@
 #include <ost/io/module_config.hh>
 #include <ost/mol/entity_view.hh>
 #include <ost/mol/entity_handle.hh>
+#include <ost/io/mol/io_profile.hh>
 
 namespace ost { namespace io {
 
@@ -33,7 +34,7 @@ String DLLEXPORT_OST_IO
 EntityToSDFString(const mol::EntityView& ent);
 
 mol::EntityHandle DLLEXPORT_OST_IO
-SDFStringToEntity(const String& pdb);
+SDFStringToEntity(const String& pdb, const IOProfile& profile);
 
 }}
 
diff --git a/modules/io/tests/test_io_sdf.py b/modules/io/tests/test_io_sdf.py
index 7277a399d5a58ca696f50fb19e0775345999313a..40cee207926556fea2916d0a420fcf7f421dee24 100644
--- a/modules/io/tests/test_io_sdf.py
+++ b/modules/io/tests/test_io_sdf.py
@@ -47,7 +47,41 @@ class TestSDF(unittest.TestCase):
     # Charge from atom line is ignored
     o_at = ent.FindAtom("00001_Simple Ligand", 1, "3")
     self.assertEqual(o_at.charge, 0)
-    
+
+  def test_fault_tolerant(self):
+    """This file has a "dative" bond (type = 9).
+    This is a non-standard extension from RDKit which should go through only
+    in fault tolerant mode"""
+
+    with self.assertRaises(Exception):
+      ent = io.LoadSDF('testfiles/sdf/dative_bond.sdf')
+
+    # Directly with fault_tolerant
+    PushVerbosityLevel(-1)  # Expect message at Error level
+    ent = io.LoadSDF('testfiles/sdf/dative_bond.sdf', fault_tolerant=True)
+    PopVerbosityLevel()
+    self.assertEqual(ent.FindAtom("00001_Simple Ligand", 1, "5").bonds[0].bond_order, 9)
+
+    # Sloppy profile
+    PushVerbosityLevel(-1)  # Expect message at Error level
+    ent = io.LoadSDF('testfiles/sdf/dative_bond.sdf', profile="SLOPPY")
+    PopVerbosityLevel()
+    self.assertEqual(ent.FindAtom("00001_Simple Ligand", 1, "5").bonds[0].bond_order, 9)
+
+    # Sloppy profile set as default
+    old_profile = io.profiles['DEFAULT'].Copy()
+    io.profiles['DEFAULT'] = "SLOPPY"
+    PushVerbosityLevel(-1)  # Expect message at Error level
+    ent = io.LoadSDF('testfiles/sdf/dative_bond.sdf')
+    PopVerbosityLevel()
+    self.assertEqual(ent.FindAtom("00001_Simple Ligand", 1, "5").bonds[0].bond_order, 9)
+
+    # Test that a restored default profile has fault_tolerant again
+    io.profiles['DEFAULT'] = old_profile
+    with self.assertRaises(Exception):
+      ent = io.LoadSDF('testfiles/sdf/dative_bond.sdf')
+
+
 if __name__== '__main__':
   from ost import testutils
   testutils.RunTests()
diff --git a/modules/io/tests/testfiles/sdf/dative_bond.sdf b/modules/io/tests/testfiles/sdf/dative_bond.sdf
new file mode 100644
index 0000000000000000000000000000000000000000..30ac15cde8ddd239f13f7c47b33f584df0533591
--- /dev/null
+++ b/modules/io/tests/testfiles/sdf/dative_bond.sdf
@@ -0,0 +1,18 @@
+Simple Ligand
+
+ Teststructure
+  6  6  0  0  1  0            999 V2000
+    0.0000    0.0000    0.0000 N   0  3  0  0  0  0
+    1.0000    0.0000    0.0000 C   0  0  0  0  0  0
+    0.0000    1.0000    0.0000 O   0  0  0  0  0  0
+    1.0000    1.0000    0.0000 S   0  0  0  0  0  0
+    2.0000    2.0000    0.0000 C   0  0  0  0  0  0
+   -1.0000   -1.0000    0.0000 Cl  0  0  0  0  0  0
+  1  2  2  0  0  0
+  1  3  1  0  0  0
+  1  6  1  0  0  0
+  2  4  1  0  0  0
+  3  4  1  0  0  0
+  4  5  9  0  0  0
+M  END
+$$$$