8f0850380b9e30de13c2c7b5c3366a90708e9b94 — Stefan Schwarzer 3 years ago fe7000e
Remove support for `M2Crypto`

M2Crypto was used to get FTP_TLS support in Python 2. Since we now
target Python 3.5+, users can and should use `ftplib.FTP_TLS` to
get FTP_TLS support.
4 files changed, 7 insertions(+), 92 deletions(-)

M ftputil/session.py
M test/test_real_ftp.py
M test/test_session.py
M tox.ini
M ftputil/session.py => ftputil/session.py +4 -39
@@ 11,12 11,6 @@ import ftplib

import ftputil.tool

    import M2Crypto
    import M2Crypto.ftpslib
except ImportError:
    M2Crypto = None

__all__ = ["session_factory"]

@@ 52,14 46,13 @@ def session_factory(base_class=ftplib.FTP, port=21, use_passive_mode=None,
    instance. The default is `None`, meaning no debugging output.

    This function should work for the base classes `ftplib.FTP`,
    `ftplib.FTP_TLS` and `M2Crypto.ftpslib.FTP_TLS` with TLS security.
    Other base classes should work if they use the same API as
    `ftplib.FTP_TLS`. Other base classes should work if they use the
    same API as `ftplib.FTP`.

    Usage example:

      my_session_factory = session_factory(
      with ftputil.FTPHost(host, user, password,

@@ 70,13 63,8 @@ def session_factory(base_class=ftplib.FTP, port=21, use_passive_mode=None,
        """Session factory class created by `session_factory`."""

        def __init__(self, host, user, password):
            # Don't use `super` in case `base_class` isn't a new-style
            # class (e. g. `ftplib.FTP` in Python 2).
            self.connect(host, port)
            if self._use_m2crypto_ftpslib():
            if debug_level is not None:
            self.login(user, password)

@@ 87,27 75,4 @@ def session_factory(base_class=ftplib.FTP, port=21, use_passive_mode=None,
            if encrypt_data_channel and hasattr(base_class, "prot_p"):

        def _use_m2crypto_ftpslib(self):
            Return `True` if the base class to use is
            `M2Crypto.ftpslib.FTP_TLS`, else return `False`.
            return (M2Crypto is not None and
                    issubclass(base_class, M2Crypto.ftpslib.FTP_TLS))

        def _fix_socket(self):
            Change the socket object so that arguments to `sendall`
            are converted to byte strings before being used.

            See the ftputil ticket #78 for details:
            original_sendall = self.sock.sendall
            # Bound method, therefore no `self` argument.
            def sendall(data):
                data = ftputil.tool.as_bytes(data)
                return original_sendall(data)
            self.sock.sendall = sendall

    return Session

M test/test_real_ftp.py => test/test_real_ftp.py +0 -20
@@ 963,23 963,3 @@ class TestOther(RealFTPTest):
        objects_after_test = len(gc.garbage)
        assert not objects_after_test - objects_before_test

      ftputil.compat.python_version > 2,
      reason="test requires M2Crypto which only works on Python 2")
    def test_m2crypto_session(self):
        Test if a session with `M2Crypto.ftpslib.FTP_TLS` is set up
        correctly and works with unicode input.
        # See ticket #78.
        # M2Crypto is only available for Python 2.
        import M2Crypto
        factory = ftputil.session.session_factory(
        with ftputil.FTPHost(*self.login_data, session_factory=factory) as host:
            # Test if unicode argument works.
            files = host.listdir(".")
        assert "CONTENTS" in files

M test/test_session.py => test/test_session.py +0 -21
@@ 24,9 24,6 @@ class MockSession:
    def connect(self, host, port):
        self.add_call("connect", host, port)

    def _fix_socket(self):

    def set_debuglevel(self, value):
        self.add_call("set_debuglevel", value)

@@ 119,21 116,3 @@ class TestSessionFactory:
        assert session.calls == [("connect", "host", 21),
                                 ("set_debuglevel", 1),
                                 ("login", "user", "password")]

    def test_m2crypto_session(self):
        """Test call sequence for M2Crypto session."""
        factory = \
        # Return `True` to fake that this is a session deriving from
        # `M2Crypto.ftpslib.FTP_TLS`.
        factory._use_m2crypto_ftpslib = lambda self: True
        # Override `_fix_socket` here, not in `MockSession`. Since
        # the created session class _inherits_ from `MockSession`,
        # it would override the `_fix_socket` there.
        factory._fix_socket = lambda self: self.add_call("_fix_socket")
        session = factory("host", "user", "password")
        assert session.calls == [("connect", "host", 21),
                                 ("login", "user", "password"),

M tox.ini => tox.ini +3 -12
@@ 4,19 4,10 @@
# "pip install tox" and then run "tox" from this directory.

#envlist = py27, py34, pypy
envlist = py27, py35
#envlist = py34, pypy
envlist = py35, py36, py37

commands = py.test test
commands = python -m pytest test
deps =

# setenv = 
#     # Used (hopefully) temporarily since M2Crypto build fails with
#     # SWIG error
#     PYTHONPATH=/usr/lib64/python2.7/site-packages
deps =