Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyMySQL 0.10.X Compatibility Issues #814

Open
guzman-raphael opened this issue Oct 14, 2020 · 3 comments
Open

PyMySQL 0.10.X Compatibility Issues #814

guzman-raphael opened this issue Oct 14, 2020 · 3 comments
Labels
bug needs-discussion Issues requiring further development review and verify impact.
Milestone

Comments

@guzman-raphael
Copy link
Collaborator

It appears that one of the recent PyMySQL==0.10.X releases broke a few of our tests. Seems as if most features are working but some edge cases need to be patched. Here is a TravisCI run where from PR #811 where you can see the test results. So far, I can see that if you set PyMySQL==0.9.2 the tests pass just fine but when running newer, say PyMySQL==0.10.1, 5 tests fail. I have not pinpointed anything more specific yet on this. Failed tests are:

  • tests.test_fetch.TestFetch.test_misspelled_attribute
  • tests.test_reconnection.TestReconnect.test_reconnect
  • tests.test_reconnection.TestReconnect.test_reconnect_throws_error_in_transaction
  • tests.test_relation.TestRelation.test_no_error_suppression
  • tests.test_schema.test_unauthorized_database
@gmaggi
Copy link

gmaggi commented Nov 17, 2020

Hi,

maybe related:

DataJoint (tested on 0.12.5 and 0.12.7) fails with pymysql:0.10.1. That throws: pymysql.err.OperationalError: (1043, 'Bad handshake'). I changed to pymysql:0.9.3 and that fixed it (tested on DJ: 0.12.5 only).

@khl02007
Copy link

khl02007 commented Dec 3, 2020

Just wanted to report that this issue still persists. Connecting to remote server running mysql version 5.7

Steps to recreate the error and the error message:

Conda environment:
(base) kyu@cyclone:~$ conda create --name dj_test python=3.8
(base) kyu@cyclone:~$ conda activate dj_test
(dj_test) kyu@cyclone:~$ pip install datajoint

In ipython:

In [1]: import pymysql

In [2]: pymysql.__version__
Out[2]: '0.10.1'

In [3]: import datajoint as dj

In [4]: dj.__version__
Out[4]: '0.12.7'

In [5]: dj.config
Out[5]: 
{   'connection.charset': '',
    'connection.init_function': None,
    'database.host': 'lmf-db.cin.ucsf.edu',
    'database.password': '<password>',
    'database.port': 3306,
    'database.reconnect': True,
    'database.use_tls': None,
    'database.user': 'kyu',
    'display.limit': 12,
    'display.show_tuple_count': True,
    'display.width': 14,
    'enable_python_native_blobs': False,
    'fetch_format': 'array',
    'loglevel': 'INFO',
    'safemode': True}

In [6]: dj.schema('test')
Connecting [email protected]:3306
---------------------------------------------------------------------------
OperationalError                          Traceback (most recent call last)
<ipython-input-6-3e1091301354> in <module>
----> 1 dj.schema('test')

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/datajoint/schemas.py in __init__(self, schema_name, context, connection, create_schema, create_tables)
     54         """
     55         if connection is None:
---> 56             connection = conn()
     57         self._log = None
     58 

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/datajoint/connection.py in conn(host, user, password, init_fun, reset, use_tls)
     86         init_fun = init_fun if init_fun is not None else config['connection.init_function']
     87         use_tls = use_tls if use_tls is not None else config['database.use_tls']
---> 88         conn.connection = Connection(host, user, password, None, init_fun, use_tls)
     89     return conn.connection
     90 

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/datajoint/connection.py in __init__(self, host, user, password, port, init_fun, use_tls)
    119         print("Connecting {user}@{host}:{port}".format(**self.conn_info))
    120         self._conn = None
--> 121         self.connect()
    122         if self.is_connected:
    123             logger.info("Connected {user}@{host}:{port}".format(**self.conn_info))

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/datajoint/connection.py in connect(self)
    144             warnings.filterwarnings('ignore', '.*deprecated.*')
    145             try:
--> 146                 self._conn = client.connect(
    147                     init_command=self.init_fun,
    148                     sql_mode="NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,"

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/__init__.py in Connect(*args, **kwargs)
     92     """
     93     from .connections import Connection
---> 94     return Connection(*args, **kwargs)
     95 
     96 from . import connections as _orig_conn

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/connections.py in __init__(self, host, user, password, database, port, unix_socket, charset, sql_mode, read_default_file, conv, use_unicode, client_flag, cursorclass, init_command, connect_timeout, ssl, read_default_group, compress, named_pipe, autocommit, db, passwd, local_infile, max_allowed_packet, defer_connect, auth_plugin_map, read_timeout, write_timeout, bind_address, binary_prefix, program_name, server_public_key)
    325             self._sock = None
    326         else:
--> 327             self.connect()
    328 
    329     def _create_ssl_ctx(self, sslp):

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/connections.py in connect(self, sock)
    586 
    587             self._get_server_information()
--> 588             self._request_authentication()
    589 
    590             if self.sql_mode is not None:

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/connections.py in _request_authentication(self)
    851 
    852         self.write_packet(data)
--> 853         auth_packet = self._read_packet()
    854 
    855         # if authentication method isn't accepted the first byte

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/connections.py in _read_packet(self, packet_type)
    674             if self._result is not None and self._result.unbuffered_active is True:
    675                 self._result.unbuffered_active = False
--> 676             packet.raise_for_error()
    677         return packet
    678 

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/protocol.py in raise_for_error(self)
    221         errno = self.read_uint16()
    222         if DEBUG: print("errno =", errno)
--> 223         err.raise_mysql_exception(self._data)
    224 
    225     def dump(self):

~/miniconda3/envs/dj_test/lib/python3.8/site-packages/pymysql/err.py in raise_mysql_exception(data)
    105     if errorclass is None:
    106         errorclass = InternalError if errno < 1000 else OperationalError
--> 107     raise errorclass(errno, errval)

OperationalError: (1043, 'Bad handshake')

@guzman-raphael guzman-raphael added bug needs-discussion Issues requiring further development review and verify impact. labels Jan 15, 2021
@guzman-raphael guzman-raphael added this to the Release 0.13.0 milestone Jan 15, 2021
@guzman-raphael guzman-raphael self-assigned this Jan 20, 2021
@guzman-raphael
Copy link
Collaborator Author

guzman-raphael commented Jan 28, 2021

Met with @ttngu207 (thanks man!) for a debug session and was able to identify the root cause and successfully reproduce the issue on my side. Should be a simple fix now with all the details.

Here is a summary of what I've found:

  • Issue: Due to error classes being modified in PyMySQL==0.10.0 (specifically the error raised when a secure connection is attempted on a server that does not support secure connections), dj-python now expects the wrong error class. Essentially here now receives an OperationalError (1043).
  • Why was it missed? DataJoint performs connections with encryption preferred as the default. Therefore, users who've experienced this are working with DB servers that explicitly have encrypted connections disabled (likely WIN users since this is the default configuration for native MySQL service install). This is a challenging corner case to reproduce in tests w/o spinning up 2 servers (1 encryption allowed, 1 encryption disabled) since the failover is what needs to be verified. Tests have been utilizing a server that allows encryption (also the same which has been distributed among community). Therefore, it has not been raised simply because it isn't properly covered. Will issue a fix soon.

A simple check or workaround for users who might be experiencing this is to simply connect using: dj.conn(use_tls=False).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug needs-discussion Issues requiring further development review and verify impact.
Projects
None yet
Development

No branches or pull requests

4 participants