From 1fd5292f33868f9f9c8b90e1e53f82dd4aa992b4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 18 Jan 2021 17:08:55 +0900 Subject: [PATCH 01/50] Update README.rst --- README.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.rst b/README.rst index 279181f16..f514d901e 100644 --- a/README.rst +++ b/README.rst @@ -17,13 +17,6 @@ PyMySQL This package contains a pure-Python MySQL client library, based on `PEP 249`_. -Most public APIs are compatible with mysqlclient and MySQLdb. - -NOTE: PyMySQL doesn't support low level APIs `_mysql` provides like `data_seek`, -`store_result`, and `use_result`. You should use high level APIs defined in `PEP 249`_. -But some APIs like `autocommit` and `ping` are supported because `PEP 249`_ doesn't cover -their usecase. - .. _`PEP 249`: https://www.python.org/dev/peps/pep-0249/ From 96d738a051673deff4d6b85d0d263c404e37e181 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Tue, 19 Jan 2021 17:21:28 +0530 Subject: [PATCH 02/50] Remove Cursor._last_executed (#948) Fixes: #947. --- pymysql/cursors.py | 2 -- pymysql/tests/test_basic.py | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pymysql/cursors.py b/pymysql/cursors.py index 666970b98..727a28e04 100644 --- a/pymysql/cursors.py +++ b/pymysql/cursors.py @@ -305,7 +305,6 @@ def scroll(self, value, mode="relative"): def _query(self, q): conn = self._get_db() - self._last_executed = q self._clear_result() conn.query(q) self._do_get_result() @@ -410,7 +409,6 @@ def close(self): def _query(self, q): conn = self._get_db() - self._last_executed = q self._clear_result() conn.query(q, unbuffered=True) self._do_get_result() diff --git a/pymysql/tests/test_basic.py b/pymysql/tests/test_basic.py index c2590bf2f..678ea9235 100644 --- a/pymysql/tests/test_basic.py +++ b/pymysql/tests/test_basic.py @@ -353,7 +353,7 @@ def test_bulk_insert(self): data, ) self.assertEqual( - cursor._last_executed, + cursor._executed, bytearray( b"insert into bulkinsert (id, name, age, height) values " b"(0,'bob',21,123),(1,'jim',56,45),(2,'fred',100,180)" @@ -377,7 +377,7 @@ def test_bulk_insert_multiline_statement(self): data, ) self.assertEqual( - cursor._last_executed.strip(), + cursor._executed.strip(), bytearray( b"""insert into bulkinsert (id, name, @@ -422,7 +422,7 @@ def test_issue_288(self): data, ) self.assertEqual( - cursor._last_executed.strip(), + cursor._executed.strip(), bytearray( b"""insert into bulkinsert (id, name, From 381e6aba21687cba18ca002db062f2fab3a04a9b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Jan 2021 22:12:11 +0900 Subject: [PATCH 03/50] Actions: Fix 422 error on Coveralls (#949) * Actions: Update coveralls flag name * fix 422 error See https://github.com/TheKevJames/coveralls-python/issues/252 --- .github/workflows/test.yaml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 09846c943..26b3f9c92 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -106,21 +106,23 @@ jobs: pytest -v --cov --cov-config .coveragerc tests/test_mariadb_auth.py - name: Report coverage - run: coveralls + run: coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: ${{ matrix.test-name }} + COVERALLS_FLAG_NAME: ${{ matrix.py }}-${{ matrix.db }} COVERALLS_PARALLEL: true coveralls: name: Finish coveralls runs-on: ubuntu-20.04 needs: test - container: python:3-slim steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.9 - name: Finished run: | - pip3 install --upgrade coveralls - coveralls --finish + pip install --upgrade coveralls + coveralls --finish --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 565dc36985a0d2c38a5a85cb4aa5b53e5c086f7c Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 19 Jan 2021 22:25:28 +0900 Subject: [PATCH 04/50] Actions: Use cache in finish (#950) --- .github/workflows/test.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 26b3f9c92..158188cd1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -120,6 +120,14 @@ jobs: - uses: actions/setup-python@v2 with: python-version: 3.9 + + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: finish-pip-1 + restore-keys: | + finish-pip- + - name: Finished run: | pip install --upgrade coveralls From 5a11bab69075a5b9120877aa70f5b86f930809c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scheibe?= Date: Sun, 24 Jan 2021 04:00:02 +0100 Subject: [PATCH 05/50] Fix docstring for converter functions (#952) Co-authored-by: Rene Scheibe --- pymysql/converters.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pymysql/converters.py b/pymysql/converters.py index d910f5c5c..200cae5fa 100644 --- a/pymysql/converters.py +++ b/pymysql/converters.py @@ -155,16 +155,16 @@ def _convert_second_fraction(s): def convert_datetime(obj): """Returns a DATETIME or TIMESTAMP column value as a datetime object: - >>> datetime_or_None('2007-02-25 23:06:20') + >>> convert_datetime('2007-02-25 23:06:20') datetime.datetime(2007, 2, 25, 23, 6, 20) - >>> datetime_or_None('2007-02-25T23:06:20') + >>> convert_datetime('2007-02-25T23:06:20') datetime.datetime(2007, 2, 25, 23, 6, 20) Illegal values are returned as None: - >>> datetime_or_None('2007-02-31T23:06:20') is None + >>> convert_datetime('2007-02-31T23:06:20') is None True - >>> datetime_or_None('0000-00-00 00:00:00') is None + >>> convert_datetime('0000-00-00 00:00:00') is None True """ @@ -189,14 +189,14 @@ def convert_datetime(obj): def convert_timedelta(obj): """Returns a TIME column as a timedelta object: - >>> timedelta_or_None('25:06:17') + >>> convert_timedelta('25:06:17') datetime.timedelta(1, 3977) - >>> timedelta_or_None('-25:06:17') + >>> convert_timedelta('-25:06:17') datetime.timedelta(-2, 83177) Illegal values are returned as None: - >>> timedelta_or_None('random crap') is None + >>> convert_timedelta('random crap') is None True Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but @@ -236,14 +236,14 @@ def convert_timedelta(obj): def convert_time(obj): """Returns a TIME column as a time object: - >>> time_or_None('15:06:17') + >>> convert_time('15:06:17') datetime.time(15, 6, 17) Illegal values are returned as None: - >>> time_or_None('-25:06:17') is None + >>> convert_time('-25:06:17') is None True - >>> time_or_None('random crap') is None + >>> convert_time('random crap') is None True Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but @@ -279,14 +279,14 @@ def convert_time(obj): def convert_date(obj): """Returns a DATE column as a date object: - >>> date_or_None('2007-02-26') + >>> convert_date('2007-02-26') datetime.date(2007, 2, 26) Illegal values are returned as None: - >>> date_or_None('2007-02-31') is None + >>> convert_date('2007-02-31') is None True - >>> date_or_None('0000-00-00') is None + >>> convert_date('0000-00-00') is None True """ From 6ccbecc1a0dfd04065b081950d2d35b1dac0aaa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scheibe?= Date: Tue, 2 Feb 2021 07:23:09 +0100 Subject: [PATCH 06/50] Improve docstrings (#954) - dot at the end of descriptions - 3rd instead of 2nd person - more type information - minor rephrasing Co-authored-by: Rene Scheibe --- pymysql/connections.py | 46 +++++++++++++++++----------------- pymysql/cursors.py | 56 +++++++++++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 92b7a77e5..b525014ca 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -99,18 +99,18 @@ class Connection: Establish a connection to the MySQL database. Accepts several arguments: - :param host: Host where the database server is located - :param user: Username to log in as + :param host: Host where the database server is located. + :param user: Username to log in as. :param password: Password to use. :param database: Database to use, None to not use a particular one. :param port: MySQL port to use, default is usually OK. (default: 3306) :param bind_address: When the client has multiple network interfaces, specify the interface from which to connect to the host. Argument can be a hostname or an IP address. - :param unix_socket: Optionally, you can use a unix socket rather than TCP/IP. + :param unix_socket: Use a unix socket rather than TCP/IP. :param read_timeout: The timeout for reading from the connection in seconds (default: None - no timeout) :param write_timeout: The timeout for writing to the connection in seconds (default: None - no timeout) - :param charset: Charset you want to use. + :param charset: Charset to use. :param sql_mode: Default SQL_MODE to use. :param read_default_file: Specifies my.cnf file to read these parameters from under the [client] section. @@ -124,16 +124,15 @@ class Connection: :param client_flag: Custom flags to send to MySQL. Find potential values in constants.CLIENT. :param cursorclass: Custom cursor class to use. :param init_command: Initial SQL statement to run when connection is established. - :param connect_timeout: Timeout before throwing an exception when connecting. + :param connect_timeout: The timeout for connecting to the database in seconds. (default: 10, min: 1, max: 31536000) - :param ssl: - A dict of arguments similar to mysql_ssl_set()'s parameters. - :param ssl_ca: Path to the file that contains a PEM-formatted CA certificate - :param ssl_cert: Path to the file that contains a PEM-formatted client certificate - :param ssl_disabled: A boolean value that disables usage of TLS - :param ssl_key: Path to the file that contains a PEM-formatted private key for the client certificate - :param ssl_verify_cert: Set to true to check the validity of server certificates - :param ssl_verify_identity: Set to true to check the server's identity + :param ssl: A dict of arguments similar to mysql_ssl_set()'s parameters. + :param ssl_ca: Path to the file that contains a PEM-formatted CA certificate. + :param ssl_cert: Path to the file that contains a PEM-formatted client certificate. + :param ssl_disabled: A boolean value that disables usage of TLS. + :param ssl_key: Path to the file that contains a PEM-formatted private key for the client certificate. + :param ssl_verify_cert: Set to true to check the server certificate's validity. + :param ssl_verify_identity: Set to true to check the server's identity. :param read_default_group: Group to read from in the configuration file. :param autocommit: Autocommit mode. None means use server default. (default: False) :param local_infile: Boolean to enable the use of LOAD DATA LOCAL command. (default: False) @@ -148,8 +147,8 @@ class Connection: (if no authenticate method) for returning a string from the user. (experimental) :param server_public_key: SHA256 authentication plugin public key value. (default: None) :param binary_prefix: Add _binary prefix on bytes and bytearray. (default: False) - :param compress: Not supported - :param named_pipe: Not supported + :param compress: Not supported. + :param named_pipe: Not supported. :param db: **DEPRECATED** Alias for database. :param passwd: **DEPRECATED** Alias for password. @@ -415,11 +414,11 @@ def close(self): @property def open(self): - """Return True if the connection is open""" + """Return True if the connection is open.""" return self._sock is not None def _force_close(self): - """Close connection without QUIT message""" + """Close connection without QUIT message.""" if self._sock: try: self._sock.close() @@ -448,7 +447,7 @@ def _read_ok_packet(self): return ok def _send_autocommit_mode(self): - """Set whether or not to commit after every execute()""" + """Set whether or not to commit after every execute().""" self._execute_command( COMMAND.COM_QUERY, "SET AUTOCOMMIT = %s" % self.escape(self.autocommit_mode) ) @@ -496,7 +495,7 @@ def select_db(self, db): self._read_ok_packet() def escape(self, obj, mapping=None): - """Escape whatever value you pass to it. + """Escape whatever value is passed. Non-standard, for internal use; do not use this in your applications. """ @@ -510,7 +509,7 @@ def escape(self, obj, mapping=None): return converters.escape_item(obj, self.charset, mapping=mapping) def literal(self, obj): - """Alias for escape() + """Alias for escape(). Non-standard, for internal use; do not use this in your applications. """ @@ -530,9 +529,8 @@ def cursor(self, cursor=None): """ Create a new cursor to execute queries with. - :param cursor: The type of cursor to create; one of :py:class:`Cursor`, - :py:class:`SSCursor`, :py:class:`DictCursor`, or :py:class:`SSDictCursor`. - None means use Cursor. + :param cursor: The type of cursor to create. None means use Cursor. + :type cursor: :py:class:`Cursor`, :py:class:`SSCursor`, :py:class:`DictCursor`, or :py:class:`SSDictCursor`. """ if cursor: return cursor(self) @@ -565,6 +563,8 @@ def ping(self, reconnect=True): Check if the server is alive. :param reconnect: If the connection is closed, reconnect. + :type reconnect: boolean + :raise Error: If the connection is closed and reconnect=False. """ if self._sock is None: diff --git a/pymysql/cursors.py b/pymysql/cursors.py index 727a28e04..2b5ccca90 100644 --- a/pymysql/cursors.py +++ b/pymysql/cursors.py @@ -15,7 +15,7 @@ class Cursor: """ - This is the object you use to interact with the database. + This is the object used to interact with the database. Do not create an instance of a Cursor yourself. Call connections.Connection.cursor(). @@ -79,7 +79,7 @@ def setoutputsizes(self, *args): """Does nothing, required by DB API.""" def _nextset(self, unbuffered=False): - """Get the next query set""" + """Get the next query set.""" conn = self._get_db() current_result = self._result if current_result is None or current_result is not conn._result: @@ -114,9 +114,18 @@ def _escape_args(self, args, conn): def mogrify(self, query, args=None): """ - Returns the exact string that is sent to the database by calling the + Returns the exact string that would be sent to the database by calling the execute() method. + :param query: Query to mogrify. + :type query: str + + :param args: Parameters used with query. (optional) + :type args: tuple, list or dict + + :return: The query with argument binding applied. + :rtype: str + This method follows the extension to the DB API 2.0 followed by Psycopg. """ conn = self._get_db() @@ -127,14 +136,15 @@ def mogrify(self, query, args=None): return query def execute(self, query, args=None): - """Execute a query + """Execute a query. - :param str query: Query to execute. + :param query: Query to execute. + :type query: str - :param args: parameters used with query. (optional) + :param args: Parameters used with query. (optional) :type args: tuple, list or dict - :return: Number of affected rows + :return: Number of affected rows. :rtype: int If args is a list or tuple, %s can be used as a placeholder in the query. @@ -150,12 +160,16 @@ def execute(self, query, args=None): return result def executemany(self, query, args): - # type: (str, list) -> int - """Run several data against one query + """Run several data against one query. + + :param query: Query to execute. + :type query: str + + :param args: Sequence of sequences or mappings. It is used as parameter. + :type args: tuple or list - :param query: query to execute on server - :param args: Sequence of sequences or mappings. It is used as parameter. :return: Number of rows affected, if any. + :rtype: int or None This method improves performance on multiple-row INSERT and REPLACE. Otherwise it is equivalent to looping over args with @@ -213,11 +227,13 @@ def _do_execute_many( return rows def callproc(self, procname, args=()): - """Execute stored procedure procname with args + """Execute stored procedure procname with args. - procname -- string, name of procedure to execute on server + :param procname: Name of procedure to execute on server. + :type procname: str - args -- Sequence of parameters to use with procedure + :param args: Sequence of parameters to use with procedure. + :type args: tuple or list Returns the original args. @@ -260,7 +276,7 @@ def callproc(self, procname, args=()): return args def fetchone(self): - """Fetch the next row""" + """Fetch the next row.""" self._check_executed() if self._rows is None or self.rownumber >= len(self._rows): return None @@ -269,7 +285,7 @@ def fetchone(self): return result def fetchmany(self, size=None): - """Fetch several rows""" + """Fetch several rows.""" self._check_executed() if self._rows is None: return () @@ -279,7 +295,7 @@ def fetchmany(self, size=None): return result def fetchall(self): - """Fetch all the rows""" + """Fetch all the rows.""" self._check_executed() if self._rows is None: return () @@ -418,11 +434,11 @@ def nextset(self): return self._nextset(unbuffered=True) def read_next(self): - """Read next row""" + """Read next row.""" return self._conv_row(self._result._read_rowdata_packet_unbuffered()) def fetchone(self): - """Fetch next row""" + """Fetch next row.""" self._check_executed() row = self.read_next() if row is None: @@ -450,7 +466,7 @@ def __iter__(self): return self.fetchall_unbuffered() def fetchmany(self, size=None): - """Fetch many""" + """Fetch many.""" self._check_executed() if size is None: size = self.arraysize From fb10477caf21122a89d7f216a0670d49dd2aa5d2 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sun, 27 Jun 2021 10:55:03 +0900 Subject: [PATCH 07/50] black --- pymysql/tests/test_basic.py | 16 ++++++++-------- pymysql/tests/test_connection.py | 18 +++++++++--------- pymysql/tests/test_issues.py | 32 ++++++++++++++++---------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pymysql/tests/test_basic.py b/pymysql/tests/test_basic.py index 678ea9235..a0dea9c86 100644 --- a/pymysql/tests/test_basic.py +++ b/pymysql/tests/test_basic.py @@ -14,7 +14,7 @@ class TestConversion(base.PyMySQLTestCase): def test_datatypes(self): - """ test every data type """ + """test every data type""" conn = self.connect() c = conn.cursor() c.execute( @@ -80,7 +80,7 @@ def test_datatypes(self): c.execute("drop table test_datatypes") def test_dict(self): - """ test dict escaping """ + """test dict escaping""" conn = self.connect() c = conn.cursor() c.execute("create table test_dict (a integer, b integer, c integer)") @@ -143,7 +143,7 @@ def test_blob(self): self.assertEqual(data, c.fetchone()[0]) def test_untyped(self): - """ test conversion of null, empty string """ + """test conversion of null, empty string""" conn = self.connect() c = conn.cursor() c.execute("select null,''") @@ -152,7 +152,7 @@ def test_untyped(self): self.assertEqual(("", None), c.fetchone()) def test_timedelta(self): - """ test timedelta conversion """ + """test timedelta conversion""" conn = self.connect() c = conn.cursor() c.execute( @@ -172,7 +172,7 @@ def test_timedelta(self): ) def test_datetime_microseconds(self): - """ test datetime conversion w microseconds""" + """test datetime conversion w microseconds""" conn = self.connect() if not self.mysql_server_is(conn, (5, 6, 4)): @@ -243,7 +243,7 @@ class TestCursor(base.PyMySQLTestCase): # self.assertEqual(r, c.description) def test_fetch_no_result(self): - """ test a fetchone() with no rows """ + """test a fetchone() with no rows""" conn = self.connect() c = conn.cursor() c.execute("create table test_nr (b varchar(32))") @@ -255,7 +255,7 @@ def test_fetch_no_result(self): c.execute("drop table test_nr") def test_aggregates(self): - """ test aggregate functions """ + """test aggregate functions""" conn = self.connect() c = conn.cursor() try: @@ -269,7 +269,7 @@ def test_aggregates(self): c.execute("drop table test_aggregates") def test_single_tuple(self): - """ test a single tuple """ + """test a single tuple""" conn = self.connect() c = conn.cursor() self.safe_create_table( diff --git a/pymysql/tests/test_connection.py b/pymysql/tests/test_connection.py index 75db73cd0..a469be5a2 100644 --- a/pymysql/tests/test_connection.py +++ b/pymysql/tests/test_connection.py @@ -226,7 +226,7 @@ def realTestDialogAuthTwoQuestions(self): pymysql.connect( user="pymysql_2q", auth_plugin_map={b"dialog": TestAuthentication.Dialog}, - **self.db + **self.db, ) @pytest.mark.skipif(not socket_auth, reason="connection to unix_socket required") @@ -266,12 +266,12 @@ def realTestDialogAuthThreeAttempts(self): pymysql.connect( user="pymysql_3a", auth_plugin_map={b"dialog": TestAuthentication.Dialog}, - **self.db + **self.db, ) pymysql.connect( user="pymysql_3a", auth_plugin_map={b"dialog": TestAuthentication.DialogHandler}, - **self.db + **self.db, ) with self.assertRaises(pymysql.err.OperationalError): pymysql.connect( @@ -282,27 +282,27 @@ def realTestDialogAuthThreeAttempts(self): pymysql.connect( user="pymysql_3a", auth_plugin_map={b"dialog": TestAuthentication.DefectiveHandler}, - **self.db + **self.db, ) with self.assertRaises(pymysql.err.OperationalError): pymysql.connect( user="pymysql_3a", auth_plugin_map={b"notdialogplugin": TestAuthentication.Dialog}, - **self.db + **self.db, ) TestAuthentication.Dialog.m = {b"Password, please:": b"I do not know"} with self.assertRaises(pymysql.err.OperationalError): pymysql.connect( user="pymysql_3a", auth_plugin_map={b"dialog": TestAuthentication.Dialog}, - **self.db + **self.db, ) TestAuthentication.Dialog.m = {b"Password, please:": None} with self.assertRaises(pymysql.err.OperationalError): pymysql.connect( user="pymysql_3a", auth_plugin_map={b"dialog": TestAuthentication.Dialog}, - **self.db + **self.db, ) @pytest.mark.skipif(not socket_auth, reason="connection to unix_socket required") @@ -367,7 +367,7 @@ def realTestPamAuth(self): auth_plugin_map={ b"mysql_cleartext_password": TestAuthentication.DefectiveHandler }, - **self.db + **self.db, ) except pymysql.OperationalError as e: self.assertEqual(1045, e.args[0]) @@ -378,7 +378,7 @@ def realTestPamAuth(self): auth_plugin_map={ b"mysql_cleartext_password": TestAuthentication.DefectiveHandler }, - **self.db + **self.db, ) if grants: # recreate the user diff --git a/pymysql/tests/test_issues.py b/pymysql/tests/test_issues.py index b4ced4b06..76d4b1334 100644 --- a/pymysql/tests/test_issues.py +++ b/pymysql/tests/test_issues.py @@ -14,7 +14,7 @@ class TestOldIssues(base.PyMySQLTestCase): def test_issue_3(self): - """ undefined methods datetime_or_None, date_or_None """ + """undefined methods datetime_or_None, date_or_None""" conn = self.connect() c = conn.cursor() with warnings.catch_warnings(): @@ -42,7 +42,7 @@ def test_issue_3(self): c.execute("drop table issue3") def test_issue_4(self): - """ can't retrieve TIMESTAMP fields """ + """can't retrieve TIMESTAMP fields""" conn = self.connect() c = conn.cursor() with warnings.catch_warnings(): @@ -57,13 +57,13 @@ def test_issue_4(self): c.execute("drop table issue4") def test_issue_5(self): - """ query on information_schema.tables fails """ + """query on information_schema.tables fails""" con = self.connect() cur = con.cursor() cur.execute("select * from information_schema.tables") def test_issue_6(self): - """ exception: TypeError: ord() expected a character, but string of length 0 found """ + """exception: TypeError: ord() expected a character, but string of length 0 found""" # ToDo: this test requires access to db 'mysql'. kwargs = self.databases[0].copy() kwargs["database"] = "mysql" @@ -73,7 +73,7 @@ def test_issue_6(self): conn.close() def test_issue_8(self): - """ Primary Key and Index error when selecting data """ + """Primary Key and Index error when selecting data""" conn = self.connect() c = conn.cursor() with warnings.catch_warnings(): @@ -93,7 +93,7 @@ def test_issue_8(self): c.execute("drop table test") def test_issue_13(self): - """ can't handle large result fields """ + """can't handle large result fields""" conn = self.connect() cur = conn.cursor() with warnings.catch_warnings(): @@ -112,7 +112,7 @@ def test_issue_13(self): cur.execute("drop table issue13") def test_issue_15(self): - """ query should be expanded before perform character encoding """ + """query should be expanded before perform character encoding""" conn = self.connect() c = conn.cursor() with warnings.catch_warnings(): @@ -127,7 +127,7 @@ def test_issue_15(self): c.execute("drop table issue15") def test_issue_16(self): - """ Patch for string and tuple escaping """ + """Patch for string and tuple escaping""" conn = self.connect() c = conn.cursor() with warnings.catch_warnings(): @@ -285,7 +285,7 @@ def disabled_test_issue_54(self): class TestGitHubIssues(base.PyMySQLTestCase): def test_issue_66(self): - """ 'Connection' object has no attribute 'insert_id' """ + """'Connection' object has no attribute 'insert_id'""" conn = self.connect() c = conn.cursor() self.assertEqual(0, conn.insert_id()) @@ -303,7 +303,7 @@ def test_issue_66(self): c.execute("drop table issue66") def test_issue_79(self): - """ Duplicate field overwrites the previous one in the result of DictCursor """ + """Duplicate field overwrites the previous one in the result of DictCursor""" conn = self.connect() c = conn.cursor(pymysql.cursors.DictCursor) @@ -330,7 +330,7 @@ def test_issue_79(self): c.execute("drop table b") def test_issue_95(self): - """ Leftover trailing OK packet for "CALL my_sp" queries """ + """Leftover trailing OK packet for "CALL my_sp" queries""" conn = self.connect() cur = conn.cursor() with warnings.catch_warnings(): @@ -352,7 +352,7 @@ def test_issue_95(self): cur.execute("DROP PROCEDURE IF EXISTS `foo`") def test_issue_114(self): - """ autocommit is not set after reconnecting with ping() """ + """autocommit is not set after reconnecting with ping()""" conn = pymysql.connect(charset="utf8", **self.databases[0]) conn.autocommit(False) c = conn.cursor() @@ -377,7 +377,7 @@ def test_issue_114(self): conn.close() def test_issue_175(self): - """ The number of fields returned by server is read in wrong way """ + """The number of fields returned by server is read in wrong way""" conn = self.connect() cur = conn.cursor() for length in (200, 300): @@ -393,7 +393,7 @@ def test_issue_175(self): cur.execute("drop table if exists test_field_count") def test_issue_321(self): - """ Test iterable as query argument. """ + """Test iterable as query argument.""" conn = pymysql.connect(charset="utf8", **self.databases[0]) self.safe_create_table( conn, @@ -422,7 +422,7 @@ def test_issue_321(self): self.assertEqual(cur.fetchone(), ("c", "\u0430")) def test_issue_364(self): - """ Test mixed unicode/binary arguments in executemany. """ + """Test mixed unicode/binary arguments in executemany.""" conn = pymysql.connect(charset="utf8mb4", **self.databases[0]) self.safe_create_table( conn, @@ -454,7 +454,7 @@ def test_issue_364(self): cur.executemany(usql, args=(values, values, values)) def test_issue_363(self): - """ Test binary / geometry types. """ + """Test binary / geometry types.""" conn = pymysql.connect(charset="utf8", **self.databases[0]) self.safe_create_table( conn, From 46d17402afaa07369b954eee026f68c5b96207ba Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 30 Jul 2021 12:44:50 +0900 Subject: [PATCH 08/50] Use dessant/lock-threads. --- .github/workflows/lock.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/lock.yml diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 000000000..1b25b4c79 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,16 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '0 0 * * *' + +permissions: + issues: write + pull-requests: write + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + From d0cd254bb4886d04b74f868b4e63f2c595bebe2b Mon Sep 17 00:00:00 2001 From: Valentin Nechayev Date: Tue, 3 Aug 2021 08:57:21 +0300 Subject: [PATCH 09/50] Fix generating authentication response with long strings (#988) Connection attributes shall be encoded using lenenc-str approach for a separate string and the whole section. --- pymysql/connections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index b525014ca..00605dd9b 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -898,10 +898,10 @@ def _request_authentication(self): connect_attrs = b"" for k, v in self._connect_attrs.items(): k = k.encode("utf-8") - connect_attrs += struct.pack("B", len(k)) + k + connect_attrs += _lenenc_int(len(k)) + k v = v.encode("utf-8") - connect_attrs += struct.pack("B", len(v)) + v - data += struct.pack("B", len(connect_attrs)) + connect_attrs + connect_attrs += _lenenc_int(len(v)) + v + data += _lenenc_int(len(connect_attrs)) + connect_attrs self.write_packet(data) auth_packet = self._read_packet() From f0091e09889a3db2400f821bee6a411fa1822a44 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 3 Aug 2021 15:06:22 +0900 Subject: [PATCH 10/50] Fix doctest in pymysql.converters (#994) Fixes #993 --- pymysql/converters.py | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/pymysql/converters.py b/pymysql/converters.py index 200cae5fa..da63ceb7b 100644 --- a/pymysql/converters.py +++ b/pymysql/converters.py @@ -160,13 +160,12 @@ def convert_datetime(obj): >>> convert_datetime('2007-02-25T23:06:20') datetime.datetime(2007, 2, 25, 23, 6, 20) - Illegal values are returned as None: - - >>> convert_datetime('2007-02-31T23:06:20') is None - True - >>> convert_datetime('0000-00-00 00:00:00') is None - True + Illegal values are returned as str: + >>> convert_datetime('2007-02-31T23:06:20') + '2007-02-31T23:06:20' + >>> convert_datetime('0000-00-00 00:00:00') + '0000-00-00 00:00:00' """ if isinstance(obj, (bytes, bytearray)): obj = obj.decode("ascii") @@ -190,14 +189,14 @@ def convert_timedelta(obj): """Returns a TIME column as a timedelta object: >>> convert_timedelta('25:06:17') - datetime.timedelta(1, 3977) + datetime.timedelta(days=1, seconds=3977) >>> convert_timedelta('-25:06:17') - datetime.timedelta(-2, 83177) + datetime.timedelta(days=-2, seconds=82423) - Illegal values are returned as None: + Illegal values are returned as string: - >>> convert_timedelta('random crap') is None - True + >>> convert_timedelta('random crap') + 'random crap' Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but can accept values as (+|-)DD HH:MM:SS. The latter format will not @@ -239,12 +238,12 @@ def convert_time(obj): >>> convert_time('15:06:17') datetime.time(15, 6, 17) - Illegal values are returned as None: + Illegal values are returned as str: - >>> convert_time('-25:06:17') is None - True - >>> convert_time('random crap') is None - True + >>> convert_time('-25:06:17') + '-25:06:17' + >>> convert_time('random crap') + 'random crap' Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but can accept values as (+|-)DD HH:MM:SS. The latter format will not @@ -282,13 +281,12 @@ def convert_date(obj): >>> convert_date('2007-02-26') datetime.date(2007, 2, 26) - Illegal values are returned as None: - - >>> convert_date('2007-02-31') is None - True - >>> convert_date('0000-00-00') is None - True + Illegal values are returned as str: + >>> convert_date('2007-02-31') + '2007-02-31' + >>> convert_date('0000-00-00') + '0000-00-00' """ if isinstance(obj, (bytes, bytearray)): obj = obj.decode("ascii") @@ -362,3 +360,5 @@ def through(x): conversions = encoders.copy() conversions.update(decoders) Thing2Literal = escape_str + +# Run doctests with `pytest --doctest-modules pymysql/converters.py` From eba874bd771901b54440b40265b26b0597ea6146 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 4 Aug 2021 13:08:47 +0900 Subject: [PATCH 11/50] Actions: Run test with Python 3.10 (#996) --- .github/workflows/test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 158188cd1..6f6f97a58 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,6 +31,9 @@ jobs: py: "3.9" mysql_auth: true + - db: "mysql:8.0" + py: "3.10-dev" + services: mysql: image: "${{ matrix.db }}" From 33d165dc3087d298ed0e2d7c4e306ccfdab1ec2c Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 28 Aug 2021 12:28:44 +0900 Subject: [PATCH 12/50] Fix calling undefined function (#1003) Fixes #981. --- pymysql/connections.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 00605dd9b..32b37bbf6 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -920,10 +920,7 @@ def _request_authentication(self): ): auth_packet = self._process_auth(plugin_name, auth_packet) else: - # send legacy handshake - data = _auth.scramble_old_password(self.password, self.salt) + b"\0" - self.write_packet(data) - auth_packet = self._read_packet() + raise err.OperationalError("received unknown auth swich request") elif auth_packet.is_extra_auth_data(): if DEBUG: print("received extra data") From 78f0cf99e5d5351df0821442e4dc35c49a6390c6 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Sat, 28 Aug 2021 13:19:08 +0900 Subject: [PATCH 13/50] Stop showing handler name when hander is not set. (#1004) Fixes #987. --- pymysql/connections.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 32b37bbf6..199558ec5 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -998,8 +998,7 @@ def _process_auth(self, plugin_name, auth_packet): else: raise err.OperationalError( 2059, - "Authentication plugin '%s' (%r) not configured" - % (plugin_name, handler), + "Authentication plugin '%s' not configured" % (plugin_name,), ) pkt = self._read_packet() pkt.check_error() From f24cb9aa7295921bcd8f34f752c8a05b981d3125 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 2 Oct 2021 17:23:14 +1000 Subject: [PATCH 14/50] tests: container docker-entrypoint-initdb.d for ease of testing (#1009) This allows easier local testing in a container image. mysql (mysql in ubuntu) --comments is needed to push mariab comments to the server side for processing. --- .github/workflows/test.yaml | 30 +++------------------ ci/docker-entrypoint-initdb.d/README | 12 +++++++++ ci/docker-entrypoint-initdb.d/init.sql | 7 +++++ ci/docker-entrypoint-initdb.d/mariadb.sql | 2 ++ ci/docker-entrypoint-initdb.d/mysql.sql | 8 ++++++ pymysql/tests/test_connection.py | 32 +++++++++++++++++++++++ tests/test_mariadb_auth.py | 24 ----------------- 7 files changed, 65 insertions(+), 50 deletions(-) create mode 100644 ci/docker-entrypoint-initdb.d/README create mode 100644 ci/docker-entrypoint-initdb.d/init.sql create mode 100644 ci/docker-entrypoint-initdb.d/mariadb.sql create mode 100644 ci/docker-entrypoint-initdb.d/mysql.sql delete mode 100644 tests/test_mariadb_auth.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6f6f97a58..1269ad057 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,16 +10,14 @@ jobs: strategy: matrix: include: - - db: "mariadb:10.0" + - db: "mariadb:10.2" py: "3.9" - db: "mariadb:10.3" py: "3.8" - mariadb_auth: true - db: "mariadb:10.5" py: "3.7" - mariadb_auth: true - db: "mysql:5.6" py: "3.6" @@ -69,10 +67,9 @@ jobs: mysql -h127.0.0.1 -uroot -e 'select version()' && break done mysql -h127.0.0.1 -uroot -e "SET GLOBAL local_infile=on" - mysql -h127.0.0.1 -uroot -e 'create database test1 DEFAULT CHARACTER SET utf8mb4' - mysql -h127.0.0.1 -uroot -e 'create database test2 DEFAULT CHARACTER SET utf8mb4' - mysql -h127.0.0.1 -uroot -e "create user test2 identified ${WITH_PLUGIN} by 'some password'; grant all on test2.* to test2;" - mysql -h127.0.0.1 -uroot -e "create user test2@localhost identified ${WITH_PLUGIN} by 'some password'; grant all on test2.* to test2@localhost;" + mysql -h127.0.0.1 -uroot --comments < ci/docker-entrypoint-initdb.d/init.sql + mysql -h127.0.0.1 -uroot --comments < ci/docker-entrypoint-initdb.d/mysql.sql + mysql -h127.0.0.1 -uroot --comments < ci/docker-entrypoint-initdb.d/mariadb.sql cp ci/docker.json pymysql/tests/databases.json - name: Run test @@ -87,27 +84,8 @@ jobs: docker cp mysqld:/var/lib/mysql/server-cert.pem "${HOME}" docker cp mysqld:/var/lib/mysql/client-key.pem "${HOME}" docker cp mysqld:/var/lib/mysql/client-cert.pem "${HOME}" - mysql -uroot -h127.0.0.1 -e ' - CREATE USER - user_sha256 IDENTIFIED WITH "sha256_password" BY "pass_sha256_01234567890123456789", - nopass_sha256 IDENTIFIED WITH "sha256_password", - user_caching_sha2 IDENTIFIED WITH "caching_sha2_password" BY "pass_caching_sha2_01234567890123456789", - nopass_caching_sha2 IDENTIFIED WITH "caching_sha2_password" - PASSWORD EXPIRE NEVER; - GRANT RELOAD ON *.* TO user_caching_sha2;' pytest -v --cov --cov-config .coveragerc tests/test_auth.py; - - name: Run MariaDB auth test - if: ${{ matrix.mariadb_auth }} - run: | - mysql -uroot -h127.0.0.1 -e ' - INSTALL SONAME "auth_ed25519"; - CREATE FUNCTION ed25519_password RETURNS STRING SONAME "auth_ed25519.so";' - # we need to pass the hashed password manually until 10.4, so hide it here - mysql -uroot -h127.0.0.1 -sNe "SELECT CONCAT('CREATE USER nopass_ed25519 IDENTIFIED VIA ed25519 USING \"',ed25519_password(\"\"),'\";');" | mysql -uroot -h127.0.0.1 - mysql -uroot -h127.0.0.1 -sNe "SELECT CONCAT('CREATE USER user_ed25519 IDENTIFIED VIA ed25519 USING \"',ed25519_password(\"pass_ed25519\"),'\";');" | mysql -uroot -h127.0.0.1 - pytest -v --cov --cov-config .coveragerc tests/test_mariadb_auth.py - - name: Report coverage run: coveralls --service=github env: diff --git a/ci/docker-entrypoint-initdb.d/README b/ci/docker-entrypoint-initdb.d/README new file mode 100644 index 000000000..6a54b93da --- /dev/null +++ b/ci/docker-entrypoint-initdb.d/README @@ -0,0 +1,12 @@ +To test with a MariaDB or MySQL container image: + +docker run -d -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=1 \ + --name=mysqld -v ./ci/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:z \ + mysql:8.0.26 --local-infile=1 + +cp ci/docker.json pymysql/tests/databases.json + +pytest + + +Note: Some authentication tests that don't match the image version will fail. diff --git a/ci/docker-entrypoint-initdb.d/init.sql b/ci/docker-entrypoint-initdb.d/init.sql new file mode 100644 index 000000000..b741d41c5 --- /dev/null +++ b/ci/docker-entrypoint-initdb.d/init.sql @@ -0,0 +1,7 @@ +create database test1 DEFAULT CHARACTER SET utf8mb4; +create database test2 DEFAULT CHARACTER SET utf8mb4; +create user test2 identified by 'some password'; +grant all on test2.* to test2; +create user test2@localhost identified by 'some password'; +grant all on test2.* to test2@localhost; + diff --git a/ci/docker-entrypoint-initdb.d/mariadb.sql b/ci/docker-entrypoint-initdb.d/mariadb.sql new file mode 100644 index 000000000..912d365a9 --- /dev/null +++ b/ci/docker-entrypoint-initdb.d/mariadb.sql @@ -0,0 +1,2 @@ +/*M!100122 INSTALL SONAME "auth_ed25519" */; +/*M!100122 CREATE FUNCTION ed25519_password RETURNS STRING SONAME "auth_ed25519.so" */; diff --git a/ci/docker-entrypoint-initdb.d/mysql.sql b/ci/docker-entrypoint-initdb.d/mysql.sql new file mode 100644 index 000000000..a4ba0927d --- /dev/null +++ b/ci/docker-entrypoint-initdb.d/mysql.sql @@ -0,0 +1,8 @@ +/*!80001 CREATE USER + user_sha256 IDENTIFIED WITH "sha256_password" BY "pass_sha256_01234567890123456789", + nopass_sha256 IDENTIFIED WITH "sha256_password", + user_caching_sha2 IDENTIFIED WITH "caching_sha2_password" BY "pass_caching_sha2_01234567890123456789", + nopass_caching_sha2 IDENTIFIED WITH "caching_sha2_password" + PASSWORD EXPIRE NEVER */; + +/*!80001 GRANT RELOAD ON *.* TO user_caching_sha2 */; diff --git a/pymysql/tests/test_connection.py b/pymysql/tests/test_connection.py index a469be5a2..e95b75d6f 100644 --- a/pymysql/tests/test_connection.py +++ b/pymysql/tests/test_connection.py @@ -53,6 +53,7 @@ class TestAuthentication(base.PyMySQLTestCase): pam_found = False mysql_old_password_found = False sha256_password_found = False + ed25519_found = False import os @@ -97,6 +98,8 @@ class TestAuthentication(base.PyMySQLTestCase): mysql_old_password_found = True elif r[0] == "sha256_password": sha256_password_found = True + elif r[0] == "ed25519": + ed25519_found = True # else: # print("plugin: %r" % r[0]) @@ -412,6 +415,35 @@ def testAuthSHA256(self): with self.assertRaises(pymysql.err.OperationalError): pymysql.connect(user="pymysql_sha256", **db) + @pytest.mark.skipif(not ed25519_found, reason="no ed25519 authention plugin") + def testAuthEd25519(self): + db = self.db.copy() + del db["password"] + conn = self.connect() + c = conn.cursor() + c.execute("select ed25519_password(''), ed25519_password('ed25519_password')") + for r in c: + empty_pass = r[0].decode("ascii") + non_empty_pass = r[1].decode("ascii") + + with TempUser( + c, + "pymysql_ed25519", + self.databases[0]["database"], + "ed25519", + empty_pass, + ) as u: + pymysql.connect(user="pymysql_ed25519", password="", **db) + + with TempUser( + c, + "pymysql_ed25519", + self.databases[0]["database"], + "ed25519", + non_empty_pass, + ) as u: + pymysql.connect(user="pymysql_ed25519", password="ed25519_password", **db) + class TestConnection(base.PyMySQLTestCase): def test_utf8mb4(self): diff --git a/tests/test_mariadb_auth.py b/tests/test_mariadb_auth.py deleted file mode 100644 index b3a2719cd..000000000 --- a/tests/test_mariadb_auth.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Test for auth methods supported by MariaDB 10.3+""" - -import pymysql - -# pymysql.connections.DEBUG = True -# pymysql._auth.DEBUG = True - -host = "127.0.0.1" -port = 3306 - - -def test_ed25519_no_password(): - con = pymysql.connect(user="nopass_ed25519", host=host, port=port, ssl=None) - con.close() - - -def test_ed25519_password(): # nosec - con = pymysql.connect( - user="user_ed25519", password="pass_ed25519", host=host, port=port, ssl=None - ) - con.close() - - -# default mariadb docker images aren't configured with SSL From 534f4a6f53097384842b55ac7466a8033c0d1375 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Mon, 31 Jan 2022 05:32:17 +0100 Subject: [PATCH 15/50] fix typo in comment (#1024) --- pymysql/connections.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 199558ec5..bfe8b10ac 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -204,12 +204,12 @@ def __init__( db=None, # deprecated ): if db is not None and database is None: - # We will raise warining in 2022 or later. + # We will raise warning in 2022 or later. # See https://github.com/PyMySQL/PyMySQL/issues/939 # warnings.warn("'db' is deprecated, use 'database'", DeprecationWarning, 3) database = db if passwd is not None and not password: - # We will raise warining in 2022 or later. + # We will raise warning in 2022 or later. # See https://github.com/PyMySQL/PyMySQL/issues/939 # warnings.warn( # "'passwd' is deprecated, use 'password'", DeprecationWarning, 3 From 72f70c9ff81103b4a2e0b8531663a80d44595c2d Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 31 Jan 2022 13:50:32 +0900 Subject: [PATCH 16/50] Update black version (#1026) --- docs/source/conf.py | 16 ++++++++-------- pymysql/connections.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 77d7073a8..a57a03c44 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,8 +46,8 @@ master_doc = "index" # General information about the project. -project = u"PyMySQL" -copyright = u"2016, Yutaka Matsubara and GitHub contributors" +project = "PyMySQL" +copyright = "2016, Yutaka Matsubara and GitHub contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -200,8 +200,8 @@ ( "index", "PyMySQL.tex", - u"PyMySQL Documentation", - u"Yutaka Matsubara and GitHub contributors", + "PyMySQL Documentation", + "Yutaka Matsubara and GitHub contributors", "manual", ), ] @@ -235,8 +235,8 @@ ( "index", "pymysql", - u"PyMySQL Documentation", - [u"Yutaka Matsubara and GitHub contributors"], + "PyMySQL Documentation", + ["Yutaka Matsubara and GitHub contributors"], 1, ) ] @@ -254,8 +254,8 @@ ( "index", "PyMySQL", - u"PyMySQL Documentation", - u"Yutaka Matsubara and GitHub contributors", + "PyMySQL Documentation", + "Yutaka Matsubara and GitHub contributors", "PyMySQL", "One line description of project.", "Miscellaneous", diff --git a/pymysql/connections.py b/pymysql/connections.py index bfe8b10ac..2edeb5083 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -61,7 +61,7 @@ DEFAULT_CHARSET = "utf8mb4" -MAX_PACKET_LEN = 2 ** 24 - 1 +MAX_PACKET_LEN = 2**24 - 1 def _pack_int24(n): From afbef5ea0d1bc4c5c2d5d15c5ce519ecdfd29a1d Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 31 Jan 2022 14:35:31 +0900 Subject: [PATCH 17/50] Actions: Use actions/setup-python cache (#1027) --- .github/workflows/test.yaml | 25 +++++++++---------------- requirements-dev.txt | 2 ++ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1269ad057..2a9ff0a6c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -47,17 +47,12 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.py }} - - - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-1 - restore-keys: | - ${{ runner.os }}-pip- + cache: 'pip' + cache-dependency-path: 'requirements-dev.txt' - name: Install dependency run: | - pip install -U cryptography PyNaCl pytest pytest-cov coveralls + pip install -U -r requirements-dev.txt - name: Set up MySQL run: | @@ -98,16 +93,14 @@ jobs: runs-on: ubuntu-20.04 needs: test steps: - - uses: actions/setup-python@v2 - with: - python-version: 3.9 + - name: requirements. + run: | + echo coveralls > requirements.txt - - uses: actions/cache@v2 + - uses: actions/setup-python@v2 with: - path: ~/.cache/pip - key: finish-pip-1 - restore-keys: | - finish-pip- + python-version: '3.9' + cache: 'pip' - name: Finished run: | diff --git a/requirements-dev.txt b/requirements-dev.txt index d65512fbb..13d7f7fb4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,5 @@ cryptography PyNaCl>=1.4.0 pytest +pytest-cov +coveralls From 2beebd92b8ad3fb59a93714c799450dbfebe3922 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Tue, 1 Feb 2022 01:04:50 +0100 Subject: [PATCH 18/50] update pymysql.constants.CR (#1029) values from https://github.com/mysql/mysql-server/blob/mysql-8.0.28/include/errmsg.h --- pymysql/constants/CR.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pymysql/constants/CR.py b/pymysql/constants/CR.py index 25579a7c6..deae977e5 100644 --- a/pymysql/constants/CR.py +++ b/pymysql/constants/CR.py @@ -65,4 +65,15 @@ CR_AUTH_PLUGIN_CANNOT_LOAD = 2059 CR_DUPLICATE_CONNECTION_ATTR = 2060 CR_AUTH_PLUGIN_ERR = 2061 -CR_ERROR_LAST = 2061 +CR_INSECURE_API_ERR = 2062 +CR_FILE_NAME_TOO_LONG = 2063 +CR_SSL_FIPS_MODE_ERR = 2064 +CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065 +CR_COMPRESSION_WRONGLY_CONFIGURED = 2066 +CR_KERBEROS_USER_NOT_FOUND = 2067 +CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068 +CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069 +CR_DNS_SRV_LOOKUP_FAILED = 2070 +CR_MANDATORY_TRACKER_NOT_FOUND = 2071 +CR_INVALID_FACTOR_NO = 2072 +CR_ERROR_LAST = 2072 From 3fb9dd9b1f88334bb8014969a7b7f7027632dcca Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Tue, 1 Feb 2022 04:57:02 +0100 Subject: [PATCH 19/50] Use constants (#1028) --- pymysql/connections.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 2edeb5083..04e3c53fb 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -13,7 +13,7 @@ from . import _auth from .charset import charset_by_name, charset_by_id -from .constants import CLIENT, COMMAND, CR, FIELD_TYPE, SERVER_STATUS +from .constants import CLIENT, COMMAND, CR, ER, FIELD_TYPE, SERVER_STATUS from . import converters from .cursors import Cursor from .optionfile import Parser @@ -441,7 +441,10 @@ def get_autocommit(self): def _read_ok_packet(self): pkt = self._read_packet() if not pkt.is_ok_packet(): - raise err.OperationalError(2014, "Command Out of Sync") + raise err.OperationalError( + CR.CR_COMMANDS_OUT_OF_SYNC, + "Command Out of Sync", + ) ok = OKPacketWrapper(pkt) self.server_status = ok.server_status return ok @@ -654,7 +657,8 @@ def connect(self, sock=None): if isinstance(e, (OSError, IOError, socket.error)): exc = err.OperationalError( - 2003, "Can't connect to MySQL server on %r (%s)" % (self.host, e) + CR.CR_CONN_HOST_ERROR, + "Can't connect to MySQL server on %r (%s)" % (self.host, e), ) # Keep original exception and traceback to investigate error. exc.original_exception = e @@ -945,7 +949,7 @@ def _process_auth(self, plugin_name, auth_packet): except AttributeError: if plugin_name != b"dialog": raise err.OperationalError( - 2059, + CR.CR_AUTH_PLUGIN_CANNOT_LOAD, "Authentication plugin '%s'" " not loaded: - %r missing authenticate method" % (plugin_name, type(handler)), @@ -983,21 +987,21 @@ def _process_auth(self, plugin_name, auth_packet): self.write_packet(resp + b"\0") except AttributeError: raise err.OperationalError( - 2059, + CR.CR_AUTH_PLUGIN_CANNOT_LOAD, "Authentication plugin '%s'" " not loaded: - %r missing prompt method" % (plugin_name, handler), ) except TypeError: raise err.OperationalError( - 2061, + CR.CR_AUTH_PLUGIN_ERR, "Authentication plugin '%s'" " %r didn't respond with string. Returned '%r' to prompt %r" % (plugin_name, handler, resp, prompt), ) else: raise err.OperationalError( - 2059, + CR.CR_AUTH_PLUGIN_CANNOT_LOAD, "Authentication plugin '%s' not configured" % (plugin_name,), ) pkt = self._read_packet() @@ -1007,7 +1011,8 @@ def _process_auth(self, plugin_name, auth_packet): return pkt else: raise err.OperationalError( - 2059, "Authentication plugin '%s' not configured" % plugin_name + CR.CR_AUTH_PLUGIN_CANNOT_LOAD, + "Authentication plugin '%s' not configured" % plugin_name, ) self.write_packet(data) @@ -1024,7 +1029,7 @@ def _get_auth_plugin_handler(self, plugin_name): handler = plugin_class(self) except TypeError: raise err.OperationalError( - 2059, + CR.CR_AUTH_PLUGIN_CANNOT_LOAD, "Authentication plugin '%s'" " not loaded: - %r cannot be constructed with connection object" % (plugin_name, plugin_class), @@ -1211,7 +1216,10 @@ def _read_load_local_packet(self, first_packet): if ( not ok_packet.is_ok_packet() ): # pragma: no cover - upstream induced protocol error - raise err.OperationalError(2014, "Commands Out of Sync") + raise err.OperationalError( + CR.CR_COMMANDS_OUT_OF_SYNC, + "Commands Out of Sync", + ) self._read_ok_packet(ok_packet) def _check_packet_is_eof(self, packet): @@ -1357,7 +1365,10 @@ def send_data(self): break conn.write_packet(chunk) except IOError: - raise err.OperationalError(1017, f"Can't find file '{self.filename}'") + raise err.OperationalError( + ER.FILE_NOT_FOUND, + f"Can't find file '{self.filename}'", + ) finally: # send the empty packet to signify we are done sending data conn.write_packet(b"") From cebba92d338d89ac46381f3e1ca637416a77c0e2 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Sun, 6 Feb 2022 08:50:49 +0100 Subject: [PATCH 20/50] Improve GitHub workflow (#1031) - concurrency cancels builds in progress e.g. on pull requests - matrix jobs no longer fail fast, allowing to see failure reasons for all matrix jobs - coveralls no longer runs on forks, this would fail anyways --- .github/workflows/test.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2a9ff0a6c..d9b9e2afb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,10 +4,15 @@ on: push: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: include: - db: "mariadb:10.2" @@ -82,6 +87,7 @@ jobs: pytest -v --cov --cov-config .coveragerc tests/test_auth.py; - name: Report coverage + if: github.repository == 'PyMySQL/PyMySQL' run: coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -89,6 +95,7 @@ jobs: COVERALLS_PARALLEL: true coveralls: + if: github.repository == 'PyMySQL/PyMySQL' name: Finish coveralls runs-on: ubuntu-20.04 needs: test From 062384c26d10556529af91d0f0946e302b727d18 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Sun, 6 Feb 2022 08:52:15 +0100 Subject: [PATCH 21/50] Drop support of EOL Python and DB versions (#1030) - Python now requires 3.7+, reflected in python_requires - MySQL now requires 5.7+ in tests - MariaDB unchanged in tests, only dropped support in documentation - Added Python 3.11 to test matrix - Added MariaDB 10.7 to test matrix - DB version checks have been removed from various tests where no longer needed this also results in running a few tests on MariaDB which were previously only running on MySQL. --- .github/workflows/test.yaml | 8 ++++---- CHANGELOG.md | 9 +++++++++ README.rst | 6 +++--- docs/source/user/installation.rst | 6 +++--- pymysql/tests/base.py | 5 +++++ pymysql/tests/test_basic.py | 6 +++--- pymysql/tests/test_connection.py | 10 +--------- pymysql/tests/test_issues.py | 15 +++------------ setup.py | 5 +++-- 9 files changed, 34 insertions(+), 36 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d9b9e2afb..0d2e99983 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,18 +24,18 @@ jobs: - db: "mariadb:10.5" py: "3.7" - - db: "mysql:5.6" - py: "3.6" + - db: "mariadb:10.7" + py: "3.11-dev" - db: "mysql:5.7" - py: "pypy-3.6" + py: "pypy-3.8" - db: "mysql:8.0" py: "3.9" mysql_auth: true - db: "mysql:8.0" - py: "3.10-dev" + py: "3.10" services: mysql: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9885af526..abf38b3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changes +## v1.0.3 + +Release date: TBD + +* Dropped support of end of life MySQL version 5.6 +* Dropped support of end of life MariaDB versions below 10.2 +* Dropped support of end of life Python version 3.6 + + ## v1.0.2 Release date: 2021-01-09 diff --git a/README.rst b/README.rst index f514d901e..e7c9419ee 100644 --- a/README.rst +++ b/README.rst @@ -25,13 +25,13 @@ Requirements * Python -- one of the following: - - CPython_ : 3.6 and newer + - CPython_ : 3.7 and newer - PyPy_ : Latest 3.x version * MySQL Server -- one of the following: - - MySQL_ >= 5.6 - - MariaDB_ >= 10.0 + - MySQL_ >= 5.7 + - MariaDB_ >= 10.2 .. _CPython: https://www.python.org/ .. _PyPy: https://pypy.org/ diff --git a/docs/source/user/installation.rst b/docs/source/user/installation.rst index 0fea27266..c66aae3d4 100644 --- a/docs/source/user/installation.rst +++ b/docs/source/user/installation.rst @@ -18,13 +18,13 @@ Requirements * Python -- one of the following: - - CPython_ >= 3.6 + - CPython_ >= 3.7 - Latest PyPy_ 3 * MySQL Server -- one of the following: - - MySQL_ >= 5.6 - - MariaDB_ >= 10.0 + - MySQL_ >= 5.7 + - MariaDB_ >= 10.2 .. _CPython: http://www.python.org/ .. _PyPy: http://pypy.org/ diff --git a/pymysql/tests/base.py b/pymysql/tests/base.py index 6f93a8317..a87307a57 100644 --- a/pymysql/tests/base.py +++ b/pymysql/tests/base.py @@ -32,6 +32,11 @@ def mysql_server_is(self, conn, version_tuple): """Return True if the given connection is on the version given or greater. + This only checks the server version string provided when the + connection is established, therefore any check for a version tuple + greater than (5, 5, 5) will always fail on MariaDB, as it always + starts with 5.5.5, e.g. 5.5.5-10.7.1-MariaDB-1:10.7.1+maria~focal. + e.g.:: if self.mysql_server_is(conn, (5, 6, 4)): diff --git a/pymysql/tests/test_basic.py b/pymysql/tests/test_basic.py index a0dea9c86..d37d19762 100644 --- a/pymysql/tests/test_basic.py +++ b/pymysql/tests/test_basic.py @@ -175,8 +175,6 @@ def test_datetime_microseconds(self): """test datetime conversion w microseconds""" conn = self.connect() - if not self.mysql_server_is(conn, (5, 6, 4)): - pytest.skip("target backend does not support microseconds") c = conn.cursor() dt = datetime.datetime(2013, 11, 12, 9, 9, 9, 123450) c.execute("create table test_datetime (id int, ts datetime(6))") @@ -285,8 +283,10 @@ def test_json(self): args = self.databases[0].copy() args["charset"] = "utf8mb4" conn = pymysql.connect(**args) + # MariaDB only has limited JSON support, stores data as longtext + # https://mariadb.com/kb/en/json-data-type/ if not self.mysql_server_is(conn, (5, 7, 0)): - pytest.skip("JSON type is not supported on MySQL <= 5.6") + pytest.skip("JSON type is only supported on MySQL >= 5.7") self.safe_create_table( conn, diff --git a/pymysql/tests/test_connection.py b/pymysql/tests/test_connection.py index e95b75d6f..23a2aa047 100644 --- a/pymysql/tests/test_connection.py +++ b/pymysql/tests/test_connection.py @@ -105,8 +105,6 @@ class TestAuthentication(base.PyMySQLTestCase): def test_plugin(self): conn = self.connect() - if not self.mysql_server_is(conn, (5, 5, 0)): - pytest.skip("MySQL-5.5 required for plugins") cur = conn.cursor() cur.execute( "select plugin from mysql.user where concat(user, '@', host)=current_user()" @@ -401,13 +399,7 @@ def testAuthSHA256(self): self.databases[0]["database"], "sha256_password", ) as u: - if self.mysql_server_is(conn, (5, 7, 0)): - c.execute("SET PASSWORD FOR 'pymysql_sha256'@'localhost' ='Sh@256Pa33'") - else: - c.execute("SET old_passwords = 2") - c.execute( - "SET PASSWORD FOR 'pymysql_sha256'@'localhost' = PASSWORD('Sh@256Pa33')" - ) + c.execute("SET PASSWORD FOR 'pymysql_sha256'@'localhost' ='Sh@256Pa33'") c.execute("FLUSH PRIVILEGES") db = self.db.copy() db["password"] = "Sh@256Pa33" diff --git a/pymysql/tests/test_issues.py b/pymysql/tests/test_issues.py index 76d4b1334..3ea2c2c46 100644 --- a/pymysql/tests/test_issues.py +++ b/pymysql/tests/test_issues.py @@ -466,29 +466,20 @@ def test_issue_363(self): ) cur = conn.cursor() - # From MySQL 5.7, ST_GeomFromText is added and GeomFromText is deprecated. - if self.mysql_server_is(conn, (5, 7, 0)): - geom_from_text = "ST_GeomFromText" - geom_as_text = "ST_AsText" - geom_as_bin = "ST_AsBinary" - else: - geom_from_text = "GeomFromText" - geom_as_text = "AsText" - geom_as_bin = "AsBinary" query = ( "INSERT INTO issue363 (id, geom) VALUES" - "(1998, %s('LINESTRING(1.1 1.1,2.2 2.2)'))" % geom_from_text + "(1998, ST_GeomFromText('LINESTRING(1.1 1.1,2.2 2.2)'))" ) cur.execute(query) # select WKT - query = "SELECT %s(geom) FROM issue363" % geom_as_text + query = "SELECT ST_AsText(geom) FROM issue363" cur.execute(query) row = cur.fetchone() self.assertEqual(row, ("LINESTRING(1.1 1.1,2.2 2.2)",)) # select WKB - query = "SELECT %s(geom) FROM issue363" % geom_as_bin + query = "SELECT ST_AsBinary(geom) FROM issue363" cur.execute(query) row = cur.fetchone() self.assertEqual( diff --git a/setup.py b/setup.py index 1510a0cf8..7cdc692fb 100755 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ description="Pure Python MySQL Driver", long_description=readme, packages=find_packages(exclude=["tests*", "pymysql.tests*"]), - python_requires=">=3.6", + python_requires=">=3.7", extras_require={ "rsa": ["cryptography"], "ed25519": ["PyNaCl>=1.4.0"], @@ -24,10 +24,11 @@ classifiers=[ "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Intended Audience :: Developers", From ee88d0f0e6499ad3054edbf057e08abfe25993c4 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Sun, 6 Feb 2022 08:53:30 +0100 Subject: [PATCH 22/50] Fix coveralls branch in README.rst (#1034) --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e7c9419ee..f1384c92f 100644 --- a/README.rst +++ b/README.rst @@ -2,8 +2,8 @@ :target: https://pymysql.readthedocs.io/ :alt: Documentation Status -.. image:: https://coveralls.io/repos/PyMySQL/PyMySQL/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=master +.. image:: https://coveralls.io/repos/PyMySQL/PyMySQL/badge.svg?branch=main&service=github + :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=main .. image:: https://img.shields.io/lgtm/grade/python/g/PyMySQL/PyMySQL.svg?logo=lgtm&logoWidth=18 :target: https://lgtm.com/projects/g/PyMySQL/PyMySQL/context:python From eb108a61669f8883426d35f153dc48c6348d4b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A4=80=EA=B7=9C?= Date: Tue, 22 Mar 2022 14:54:05 +0900 Subject: [PATCH 23/50] Fix minor typo in error message (#1038) --- pymysql/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 04e3c53fb..9de40dea5 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -924,7 +924,7 @@ def _request_authentication(self): ): auth_packet = self._process_auth(plugin_name, auth_packet) else: - raise err.OperationalError("received unknown auth swich request") + raise err.OperationalError("received unknown auth switch request") elif auth_packet.is_extra_auth_data(): if DEBUG: print("received extra data") From b9e07c5bb56806a167003ced8d3c5e704657e503 Mon Sep 17 00:00:00 2001 From: Daniel Golding Date: Sat, 16 Apr 2022 07:23:52 +0200 Subject: [PATCH 24/50] Document that the ssl connection parameter can be an SSLContext (#1045) --- pymysql/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 9de40dea5..94ea545fc 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -126,7 +126,7 @@ class Connection: :param init_command: Initial SQL statement to run when connection is established. :param connect_timeout: The timeout for connecting to the database in seconds. (default: 10, min: 1, max: 31536000) - :param ssl: A dict of arguments similar to mysql_ssl_set()'s parameters. + :param ssl: A dict of arguments similar to mysql_ssl_set()'s parameters or an ssl.SSLContext. :param ssl_ca: Path to the file that contains a PEM-formatted CA certificate. :param ssl_cert: Path to the file that contains a PEM-formatted client certificate. :param ssl_disabled: A boolean value that disables usage of TLS. From 72ee1f3804082442fcbc5c0b1a054ed5c284cd7d Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Tue, 14 Jun 2022 06:40:21 +0200 Subject: [PATCH 25/50] Update mariadb tests to 10.8, remove end of life mariadb 10.2 (#1049) --- .github/workflows/test.yaml | 6 +++--- CHANGELOG.md | 2 +- README.rst | 2 +- docs/source/user/installation.rst | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0d2e99983..e07a4c9b8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,9 +15,6 @@ jobs: fail-fast: false matrix: include: - - db: "mariadb:10.2" - py: "3.9" - - db: "mariadb:10.3" py: "3.8" @@ -27,6 +24,9 @@ jobs: - db: "mariadb:10.7" py: "3.11-dev" + - db: "mariadb:10.8" + py: "3.9" + - db: "mysql:5.7" py: "pypy-3.8" diff --git a/CHANGELOG.md b/CHANGELOG.md index abf38b3ff..5a4292449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Release date: TBD * Dropped support of end of life MySQL version 5.6 -* Dropped support of end of life MariaDB versions below 10.2 +* Dropped support of end of life MariaDB versions below 10.3 * Dropped support of end of life Python version 3.6 diff --git a/README.rst b/README.rst index f1384c92f..318e94604 100644 --- a/README.rst +++ b/README.rst @@ -31,7 +31,7 @@ Requirements * MySQL Server -- one of the following: - MySQL_ >= 5.7 - - MariaDB_ >= 10.2 + - MariaDB_ >= 10.3 .. _CPython: https://www.python.org/ .. _PyPy: https://pypy.org/ diff --git a/docs/source/user/installation.rst b/docs/source/user/installation.rst index c66aae3d4..9313f14d3 100644 --- a/docs/source/user/installation.rst +++ b/docs/source/user/installation.rst @@ -24,7 +24,7 @@ Requirements * MySQL Server -- one of the following: - MySQL_ >= 5.7 - - MariaDB_ >= 10.2 + - MariaDB_ >= 10.3 .. _CPython: http://www.python.org/ .. _PyPy: http://pypy.org/ From 0ab388939ae96fa32acc59ebcc2e7b1a2a4da8c1 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Thu, 14 Jul 2022 07:57:13 +0200 Subject: [PATCH 26/50] Fix CodeQL target branch (#1054) master branch was renamed to main some time ago, leading to this action no longer working properly, at least for PRs --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b6a7238dd..94165437a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [ main ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ main ] schedule: - cron: '34 7 * * 2' From 7f47ac0184294b15a3b53cdcbe96b9895d0c6f4c Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Thu, 14 Jul 2022 07:57:25 +0200 Subject: [PATCH 27/50] Update CodeQL GitHub action to v2 (#1055) v1 has been deprecated: https://github.blog/changelog/2022-04-27-code-scanning-deprecation-of-codeql-action-v1/ --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 94165437a..d559b1cd2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From d1748350b9b6b4efdcead428fad2fbcdb7cfddd0 Mon Sep 17 00:00:00 2001 From: WangDi Date: Fri, 22 Jul 2022 13:12:12 +0800 Subject: [PATCH 28/50] tests: remove duplicate test (#1057) --- pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py index e882c5eb3..9ac190f27 100644 --- a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +++ b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py @@ -23,9 +23,6 @@ def test_setoutputsize(self): def test_setoutputsize_basic(self): pass - def test_nextset(self): - pass - """The tests on fetchone and fetchall and rowcount bogusly test for an exception if the statement cannot return a result set. MySQL always returns a result set; it's just that From dd47caae95011e79b9e2ee12549d23f05a7f839d Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Wed, 24 Aug 2022 04:50:30 +0200 Subject: [PATCH 29/50] Remove deprecated socket.error from Connection.connect exception handler (#1062) Since python 3.3, `socket.error` is a deprecated alias for OSError, which is already included. --- pymysql/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymysql/connections.py b/pymysql/connections.py index 94ea545fc..3265d32ee 100644 --- a/pymysql/connections.py +++ b/pymysql/connections.py @@ -655,7 +655,7 @@ def connect(self, sock=None): except: # noqa pass - if isinstance(e, (OSError, IOError, socket.error)): + if isinstance(e, (OSError, IOError)): exc = err.OperationalError( CR.CR_CONN_HOST_ERROR, "Can't connect to MySQL server on %r (%s)" % (self.host, e), From e77b21898ab46887067df981eaa19809533ec4bf Mon Sep 17 00:00:00 2001 From: Chuck Cadman <51368516+cdcadman@users.noreply.github.com> Date: Mon, 19 Sep 2022 00:06:49 -0700 Subject: [PATCH 30/50] Raise ProgrammingError on -inf in addition to inf (#1067) Co-authored-by: Chuck Cadman --- pymysql/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymysql/converters.py b/pymysql/converters.py index da63ceb7b..2acc3e58d 100644 --- a/pymysql/converters.py +++ b/pymysql/converters.py @@ -56,7 +56,7 @@ def escape_int(value, mapping=None): def escape_float(value, mapping=None): s = repr(value) - if s in ("inf", "nan"): + if s in ("inf", "-inf", "nan"): raise ProgrammingError("%s can not be used with MySQL" % s) if "e" not in s: s += "e0" From 3dc1abbdaf7af99357c834c58f0e27f871ebe885 Mon Sep 17 00:00:00 2001 From: SergeantMenacingGarlic <87030047+SergeantMenacingGarlic@users.noreply.github.com> Date: Tue, 11 Oct 2022 03:06:18 -0400 Subject: [PATCH 31/50] Add unix socket test (#1061) --- .github/workflows/test.yaml | 9 +++++++++ ci/docker.json | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e07a4c9b8..5a8f6dab5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -45,9 +45,18 @@ jobs: env: MYSQL_ALLOW_EMPTY_PASSWORD: yes options: "--name=mysqld" + volumes: + - /run/mysqld:/run/mysqld steps: - uses: actions/checkout@v2 + + - name: Workaround MySQL container permissions + if: startsWith(matrix.db, 'mysql') + run: | + sudo chown 999:999 /run/mysqld + /usr/bin/docker ps --all --filter status=exited --no-trunc --format "{{.ID}}" | xargs -r /usr/bin/docker start + - name: Set up Python ${{ matrix.py }} uses: actions/setup-python@v2 with: diff --git a/ci/docker.json b/ci/docker.json index 34a5c7b7c..63d19a687 100644 --- a/ci/docker.json +++ b/ci/docker.json @@ -1,4 +1,5 @@ [ {"host": "127.0.0.1", "port": 3306, "user": "root", "password": "", "database": "test1", "use_unicode": true, "local_infile": true}, - {"host": "127.0.0.1", "port": 3306, "user": "test2", "password": "some password", "database": "test2" } + {"host": "127.0.0.1", "port": 3306, "user": "test2", "password": "some password", "database": "test2" }, + {"host": "localhost", "port": 3306, "user": "test2", "password": "some password", "database": "test2", "unix_socket": "/run/mysqld/mysqld.sock"} ] From 90317924e8f4ae5af871d4ef32cfadf963a795f4 Mon Sep 17 00:00:00 2001 From: Richard Schwab Date: Fri, 11 Nov 2022 03:27:42 +0100 Subject: [PATCH 32/50] Use Python 3.11 release instead of -dev in tests (#1076) --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5a8f6dab5..39afc5791 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -22,7 +22,7 @@ jobs: py: "3.7" - db: "mariadb:10.7" - py: "3.11-dev" + py: "3.11" - db: "mariadb:10.8" py: "3.9" From ed56379dcc165f8810c8678c56bff7bb544a710f Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 11 Nov 2022 13:28:06 +1100 Subject: [PATCH 33/50] docs: Fix a few typos (#1053) --- pymysql/tests/test_connection.py | 2 +- pymysql/tests/test_issues.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pymysql/tests/test_connection.py b/pymysql/tests/test_connection.py index 23a2aa047..94a8dea04 100644 --- a/pymysql/tests/test_connection.py +++ b/pymysql/tests/test_connection.py @@ -492,7 +492,7 @@ def test_connection_gone_away(self): time.sleep(2) with self.assertRaises(pymysql.OperationalError) as cm: cur.execute("SELECT 1+1") - # error occures while reading, not writing because of socket buffer. + # error occurs while reading, not writing because of socket buffer. # self.assertEqual(cm.exception.args[0], 2006) self.assertIn(cm.exception.args[0], (2006, 2013)) diff --git a/pymysql/tests/test_issues.py b/pymysql/tests/test_issues.py index 3ea2c2c46..733d56a16 100644 --- a/pymysql/tests/test_issues.py +++ b/pymysql/tests/test_issues.py @@ -149,7 +149,7 @@ def test_issue_16(self): "test_issue_17() requires a custom, legacy MySQL configuration and will not be run." ) def test_issue_17(self): - """could not connect mysql use passwod""" + """could not connect mysql use password""" conn = self.connect() host = self.databases[0]["host"] db = self.databases[0]["database"] From e3a1beba22234f419d68c6947d7a1a0bf5d2eae4 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 9 Jan 2023 09:36:10 +0100 Subject: [PATCH 34/50] flake8: Use max_line_length instead of ignoring E501 (#1081) --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b40802e4b..e487e5e74 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,7 @@ [flake8] -ignore = E203,E501,W503,E722 exclude = tests,build,.venv,docs +ignore = E203,W503,E722 +max_line_length=129 [metadata] license = "MIT" From e91d097029f90055237741b5e56f81933ec1c981 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 9 Jan 2023 13:10:32 +0100 Subject: [PATCH 35/50] Fix typos discovered by codespell (#1082) --- CHANGELOG.md | 2 +- pymysql/_auth.py | 2 +- pymysql/tests/test_DictCursor.py | 2 +- pymysql/tests/test_basic.py | 2 +- pymysql/tests/thirdparty/test_MySQLdb/dbapi20.py | 12 ++++++------ .../thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4292449..87c3f9e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -204,7 +204,7 @@ Release date: 2016-08-30 Release date: 2016-07-29 * Fix SELECT JSON type cause UnicodeError -* Avoid float convertion while parsing microseconds +* Avoid float conversion while parsing microseconds * Warning has number * SSCursor supports warnings diff --git a/pymysql/_auth.py b/pymysql/_auth.py index 33fd9df86..f6c9eb967 100644 --- a/pymysql/_auth.py +++ b/pymysql/_auth.py @@ -241,7 +241,7 @@ def caching_sha2_password_auth(conn, pkt): return pkt if n != 4: - raise OperationalError("caching sha2: Unknwon result for fast auth: %s" % n) + raise OperationalError("caching sha2: Unknown result for fast auth: %s" % n) if DEBUG: print("caching sha2: Trying full auth...") diff --git a/pymysql/tests/test_DictCursor.py b/pymysql/tests/test_DictCursor.py index 581a0c4ae..bbc87d032 100644 --- a/pymysql/tests/test_DictCursor.py +++ b/pymysql/tests/test_DictCursor.py @@ -17,7 +17,7 @@ def setUp(self): self.conn = conn = self.connect() c = conn.cursor(self.cursor_type) - # create a table ane some data to query + # create a table and some data to query with warnings.catch_warnings(): warnings.filterwarnings("ignore") c.execute("drop table if exists dictcursor") diff --git a/pymysql/tests/test_basic.py b/pymysql/tests/test_basic.py index d37d19762..bc88e5a55 100644 --- a/pymysql/tests/test_basic.py +++ b/pymysql/tests/test_basic.py @@ -320,7 +320,7 @@ def setUp(self): self.conn = conn = self.connect() c = conn.cursor(self.cursor_type) - # create a table ane some data to query + # create a table and some data to query self.safe_create_table( conn, "bulkinsert", diff --git a/pymysql/tests/thirdparty/test_MySQLdb/dbapi20.py b/pymysql/tests/thirdparty/test_MySQLdb/dbapi20.py index 6766aff32..30620ce41 100644 --- a/pymysql/tests/thirdparty/test_MySQLdb/dbapi20.py +++ b/pymysql/tests/thirdparty/test_MySQLdb/dbapi20.py @@ -51,9 +51,9 @@ # - Now a subclass of TestCase, to avoid requiring the driver stub # to use multiple inheritance # - Reversed the polarity of buggy test in test_description -# - Test exception heirarchy correctly +# - Test exception hierarchy correctly # - self.populate is now self._populate(), so if a driver stub -# overrides self.ddl1 this change propogates +# overrides self.ddl1 this change propagates # - VARCHAR columns now have a width, which will hopefully make the # DDL even more portible (this will be reversed if it causes more problems) # - cursor.rowcount being checked after various execute and fetchXXX methods @@ -174,7 +174,7 @@ def test_paramstyle(self): def test_Exceptions(self): # Make sure required exceptions exist, and are in the - # defined heirarchy. + # defined hierarchy. self.assertTrue(issubclass(self.driver.Warning, Exception)) self.assertTrue(issubclass(self.driver.Error, Exception)) self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error)) @@ -474,7 +474,7 @@ def test_fetchone(self): self.assertRaises(self.driver.Error, cur.fetchone) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows self.executeDDL1(cur) self.assertRaises(self.driver.Error, cur.fetchone) @@ -487,7 +487,7 @@ def test_fetchone(self): self.assertTrue(cur.rowcount in (-1, 0)) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows cur.execute( "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) ) @@ -792,7 +792,7 @@ def test_setoutputsize_basic(self): con.close() def test_setoutputsize(self): - # Real test for setoutputsize is driver dependant + # Real test for setoutputsize is driver dependent raise NotImplementedError("Driver need to override this test") def test_None(self): diff --git a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py index 9ac190f27..bc1e1b2ea 100644 --- a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +++ b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py @@ -92,7 +92,7 @@ def test_fetchone(self): self.assertRaises(self.driver.Error, cur.fetchone) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows self.executeDDL1(cur) ## self.assertRaises(self.driver.Error,cur.fetchone) @@ -105,7 +105,7 @@ def test_fetchone(self): self.assertTrue(cur.rowcount in (-1, 0)) # cursor.fetchone should raise an Error if called after - # executing a query that cannnot return rows + # executing a query that cannot return rows cur.execute( "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix) ) From 15c2e4c88bfffacce3cc7eaa5a89fdf25c58edea Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 19 Jan 2023 10:10:37 +0900 Subject: [PATCH 36/50] Action: Update to dessant/lock-threads@v4 --- .github/workflows/lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 1b25b4c79..7806b7db5 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -12,5 +12,5 @@ jobs: action: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v4 From 67af9a55b4f6fa9fe7d0cc13877b4f6016db3680 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 19 Jan 2023 13:27:07 +0900 Subject: [PATCH 37/50] Action: Run 'Lock Threads' weekly. --- .github/workflows/lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 7806b7db5..c8f2ca245 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,7 +2,7 @@ name: 'Lock Threads' on: schedule: - - cron: '0 0 * * *' + - cron: '9 30 * * 1' permissions: issues: write From d734f15bd8ed20a7442c6bac59d3894181cc326e Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Feb 2023 14:35:02 +0900 Subject: [PATCH 38/50] Action: Add doctest (#1086) --- .github/workflows/test.yaml | 1 + pymysql/tests/test_basic.py | 1 - pymysql/tests/test_connection.py | 1 - pymysql/tests/thirdparty/test_MySQLdb/capabilities.py | 1 - .../tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 39afc5791..aee9e1bca 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -84,6 +84,7 @@ jobs: - name: Run test run: | pytest -v --cov --cov-config .coveragerc pymysql + pytest -v --cov --cov-config .coveragerc --doctest-modules pymysql/converters.py - name: Run MySQL8 auth test if: ${{ matrix.mysql_auth }} diff --git a/pymysql/tests/test_basic.py b/pymysql/tests/test_basic.py index bc88e5a55..8af07da09 100644 --- a/pymysql/tests/test_basic.py +++ b/pymysql/tests/test_basic.py @@ -312,7 +312,6 @@ def test_json(self): class TestBulkInserts(base.PyMySQLTestCase): - cursor_type = pymysql.cursors.DictCursor def setUp(self): diff --git a/pymysql/tests/test_connection.py b/pymysql/tests/test_connection.py index 94a8dea04..d6fb5e523 100644 --- a/pymysql/tests/test_connection.py +++ b/pymysql/tests/test_connection.py @@ -45,7 +45,6 @@ def __exit__(self, exc_type, exc_value, traceback): class TestAuthentication(base.PyMySQLTestCase): - socket_auth = False socket_found = False two_questions_found = False diff --git a/pymysql/tests/thirdparty/test_MySQLdb/capabilities.py b/pymysql/tests/thirdparty/test_MySQLdb/capabilities.py index ffead0caf..0276a558a 100644 --- a/pymysql/tests/thirdparty/test_MySQLdb/capabilities.py +++ b/pymysql/tests/thirdparty/test_MySQLdb/capabilities.py @@ -10,7 +10,6 @@ class DatabaseTest(unittest.TestCase): - db_module = None connect_args = () connect_kwargs = dict(use_unicode=True, charset="utf8mb4", binary_prefix=True) diff --git a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py index 139089ab1..11bfdbe29 100644 --- a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +++ b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py @@ -8,7 +8,6 @@ class test_MySQLdb(capabilities.DatabaseTest): - db_module = pymysql connect_args = () connect_kwargs = base.PyMySQLTestCase.databases[0].copy() From 958a195d20551821db34b0c6b2d79739bc5543cf Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Feb 2023 15:58:08 +0900 Subject: [PATCH 39/50] Action: Fix lock --- .github/workflows/lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index c8f2ca245..5dde1354a 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,7 +2,7 @@ name: 'Lock Threads' on: schedule: - - cron: '9 30 * * 1' + - cron: '30 9 * * 1' permissions: issues: write From 6270177c19fcb29e9d48c5178f91601a0e1a1fb1 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Feb 2023 16:58:15 +0900 Subject: [PATCH 40/50] README: Remove LGTM label --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 318e94604..592b295a5 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,6 @@ .. image:: https://coveralls.io/repos/PyMySQL/PyMySQL/badge.svg?branch=main&service=github :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=main -.. image:: https://img.shields.io/lgtm/grade/python/g/PyMySQL/PyMySQL.svg?logo=lgtm&logoWidth=18 - :target: https://lgtm.com/projects/g/PyMySQL/PyMySQL/context:python - PyMySQL ======= From 592c4d2cf29702d36ad56469d74de4510fb5a376 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 3 Feb 2023 17:01:16 +0900 Subject: [PATCH 41/50] Action: Fix test coverage --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index aee9e1bca..2b334503a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -84,7 +84,7 @@ jobs: - name: Run test run: | pytest -v --cov --cov-config .coveragerc pymysql - pytest -v --cov --cov-config .coveragerc --doctest-modules pymysql/converters.py + pytest -v --cov-append --cov-config .coveragerc --doctest-modules pymysql/converters.py - name: Run MySQL8 auth test if: ${{ matrix.mysql_auth }} From ded5f5a2d20f6eb033ade4096e88e291e432740b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 6 Feb 2023 20:39:57 +0900 Subject: [PATCH 42/50] Use pyproject.toml (#1087) --- .flake8 | 4 ++++ pyproject.toml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 14 -------------- setup.py | 39 --------------------------------------- 4 files changed, 53 insertions(+), 53 deletions(-) create mode 100644 .flake8 create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..3f1c38a3f --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +exclude = tests,build,.venv,docs +ignore = E203,W503,E722 +max_line_length=129 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..3793a8c1b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[project] +name = "PyMySQL" +version = "1.0.2" +description = "Pure Python MySQL Driver" +authors = [ + {name = "Inada Naoki", email = "songofacandy@gmail.com"}, + {name = "Yutaka Matsubara", email = "yutaka.matsubara@gmail.com"} +] +dependencies = [] + +requires-python = ">=3.7" +readme = "README.rst" +license = {text = "MIT License"} +keywords = ["MySQL"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Topic :: Database", +] + +[project.optional-dependencies] +"rsa" = [ + "cryptography" +] +"ed25519" = [ + "PyNaCl>=1.4.0" +] + +[project.urls] +"Project" = "https://github.com/PyMySQL/PyMySQL" +"Documentation" = "https://pymysql.readthedocs.io/" + +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +namespaces = false +include = ["pymysql"] +exclude = ["tests*", "pymysql.tests*"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e487e5e74..000000000 --- a/setup.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[flake8] -exclude = tests,build,.venv,docs -ignore = E203,W503,E722 -max_line_length=129 - -[metadata] -license = "MIT" -license_files = LICENSE - -author=yutaka.matsubara -author_email=yutaka.matsubara@gmail.com - -maintainer=Inada Naoki -maintainer_email=songofacandy@gmail.com diff --git a/setup.py b/setup.py deleted file mode 100755 index 7cdc692fb..000000000 --- a/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup, find_packages - -version = "1.0.2" - -with open("./README.rst", encoding="utf-8") as f: - readme = f.read() - -setup( - name="PyMySQL", - version=version, - url="https://github.com/PyMySQL/PyMySQL/", - project_urls={ - "Documentation": "https://pymysql.readthedocs.io/", - }, - description="Pure Python MySQL Driver", - long_description=readme, - packages=find_packages(exclude=["tests*", "pymysql.tests*"]), - python_requires=">=3.7", - extras_require={ - "rsa": ["cryptography"], - "ed25519": ["PyNaCl>=1.4.0"], - }, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Topic :: Database", - ], - keywords="MySQL", -) From 5fa787694107c5a5dd7742852a0f830dc7bcf560 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 6 Feb 2023 12:40:18 +0100 Subject: [PATCH 43/50] Upgrade GitHub Actions (#1080) --- .github/workflows/lint.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 887a8f261..a3131ce25 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,10 +10,12 @@ on: jobs: lint: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x - uses: psf/black@stable with: args: ". --diff --check" From b1399c95bcde8ef73cbc3a6d4e8bf767094bbd9e Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 7 Feb 2023 00:55:20 +0100 Subject: [PATCH 44/50] Upgrade more GitHub Actions (#1088) Followup to #1080 --- .github/workflows/test.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2b334503a..993347f64 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,7 +10,7 @@ concurrency: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -49,7 +49,7 @@ jobs: - /run/mysqld:/run/mysqld steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Workaround MySQL container permissions if: startsWith(matrix.db, 'mysql') @@ -58,7 +58,7 @@ jobs: /usr/bin/docker ps --all --filter status=exited --no-trunc --format "{{.ID}}" | xargs -r /usr/bin/docker start - name: Set up Python ${{ matrix.py }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} cache: 'pip' @@ -66,7 +66,7 @@ jobs: - name: Install dependency run: | - pip install -U -r requirements-dev.txt + pip install --upgrade -r requirements-dev.txt - name: Set up MySQL run: | @@ -107,16 +107,16 @@ jobs: coveralls: if: github.repository == 'PyMySQL/PyMySQL' name: Finish coveralls - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: test steps: - name: requirements. run: | echo coveralls > requirements.txt - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.x' cache: 'pip' - name: Finished From d894ab5c045fd4bc86edbe8321454b86410e12c4 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 22 Mar 2023 19:54:05 +0900 Subject: [PATCH 45/50] Convert README to Markdown (#1093) --- README.md | 105 +++++++++++++++++++++++++++++++++++++ README.rst | 138 ------------------------------------------------- pyproject.toml | 2 +- 3 files changed, 106 insertions(+), 139 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 000000000..dec840803 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +[![Documentation Status](https://readthedocs.org/projects/pymysql/badge/?version=latest)](https://pymysql.readthedocs.io/) +[![image](https://coveralls.io/repos/PyMySQL/PyMySQL/badge.svg?branch=main&service=github)](https://coveralls.io/github/PyMySQL/PyMySQL?branch=main) + +# PyMySQL + +This package contains a pure-Python MySQL client library, based on [PEP +249](https://www.python.org/dev/peps/pep-0249/). + +## Requirements + +- Python -- one of the following: + - [CPython](https://www.python.org/) : 3.7 and newer + - [PyPy](https://pypy.org/) : Latest 3.x version +- MySQL Server -- one of the following: + - [MySQL](https://www.mysql.com/) \>= 5.7 + - [MariaDB](https://mariadb.org/) \>= 10.3 + +## Installation + +Package is uploaded on [PyPI](https://pypi.org/project/PyMySQL). + +You can install it with pip: + + $ python3 -m pip install PyMySQL + +To use "sha256_password" or "caching_sha2_password" for authenticate, +you need to install additional dependency: + + $ python3 -m pip install PyMySQL[rsa] + +To use MariaDB's "ed25519" authentication method, you need to install +additional dependency: + + $ python3 -m pip install PyMySQL[ed25519] + +## Documentation + +Documentation is available online: + +For support, please refer to the +[StackOverflow](https://stackoverflow.com/questions/tagged/pymysql). + +## Example + +The following examples make use of a simple table + +``` sql +CREATE TABLE `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `email` varchar(255) COLLATE utf8_bin NOT NULL, + `password` varchar(255) COLLATE utf8_bin NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin +AUTO_INCREMENT=1 ; +``` + +``` python +import pymysql.cursors + +# Connect to the database +connection = pymysql.connect(host='localhost', + user='user', + password='passwd', + database='db', + cursorclass=pymysql.cursors.DictCursor) + +with connection: + with connection.cursor() as cursor: + # Create a new record + sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" + cursor.execute(sql, ('webmaster@python.org', 'very-secret')) + + # connection is not autocommit by default. So you must commit to save + # your changes. + connection.commit() + + with connection.cursor() as cursor: + # Read a single record + sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s" + cursor.execute(sql, ('webmaster@python.org',)) + result = cursor.fetchone() + print(result) +``` + +This example will print: + +``` python +{'password': 'very-secret', 'id': 1} +``` + +## Resources + +- DB-API 2.0: +- MySQL Reference Manuals: +- MySQL client/server protocol: + +- "Connector" channel in MySQL Community Slack: + +- PyMySQL mailing list: + + +## License + +PyMySQL is released under the MIT License. See LICENSE for more +information. diff --git a/README.rst b/README.rst deleted file mode 100644 index 592b295a5..000000000 --- a/README.rst +++ /dev/null @@ -1,138 +0,0 @@ -.. image:: https://readthedocs.org/projects/pymysql/badge/?version=latest - :target: https://pymysql.readthedocs.io/ - :alt: Documentation Status - -.. image:: https://coveralls.io/repos/PyMySQL/PyMySQL/badge.svg?branch=main&service=github - :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=main - - -PyMySQL -======= - -.. contents:: Table of Contents - :local: - -This package contains a pure-Python MySQL client library, based on `PEP 249`_. - -.. _`PEP 249`: https://www.python.org/dev/peps/pep-0249/ - - -Requirements -------------- - -* Python -- one of the following: - - - CPython_ : 3.7 and newer - - PyPy_ : Latest 3.x version - -* MySQL Server -- one of the following: - - - MySQL_ >= 5.7 - - MariaDB_ >= 10.3 - -.. _CPython: https://www.python.org/ -.. _PyPy: https://pypy.org/ -.. _MySQL: https://www.mysql.com/ -.. _MariaDB: https://mariadb.org/ - - -Installation ------------- - -Package is uploaded on `PyPI `_. - -You can install it with pip:: - - $ python3 -m pip install PyMySQL - -To use "sha256_password" or "caching_sha2_password" for authenticate, -you need to install additional dependency:: - - $ python3 -m pip install PyMySQL[rsa] - -To use MariaDB's "ed25519" authentication method, you need to install -additional dependency:: - - $ python3 -m pip install PyMySQL[ed25519] - - -Documentation -------------- - -Documentation is available online: https://pymysql.readthedocs.io/ - -For support, please refer to the `StackOverflow -`_. - - -Example -------- - -The following examples make use of a simple table - -.. code:: sql - - CREATE TABLE `users` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `email` varchar(255) COLLATE utf8_bin NOT NULL, - `password` varchar(255) COLLATE utf8_bin NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin - AUTO_INCREMENT=1 ; - - -.. code:: python - - import pymysql.cursors - - # Connect to the database - connection = pymysql.connect(host='localhost', - user='user', - password='passwd', - database='db', - cursorclass=pymysql.cursors.DictCursor) - - with connection: - with connection.cursor() as cursor: - # Create a new record - sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" - cursor.execute(sql, ('webmaster@python.org', 'very-secret')) - - # connection is not autocommit by default. So you must commit to save - # your changes. - connection.commit() - - with connection.cursor() as cursor: - # Read a single record - sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s" - cursor.execute(sql, ('webmaster@python.org',)) - result = cursor.fetchone() - print(result) - - -This example will print: - -.. code:: python - - {'password': 'very-secret', 'id': 1} - - -Resources ---------- - -* DB-API 2.0: https://www.python.org/dev/peps/pep-0249/ - -* MySQL Reference Manuals: https://dev.mysql.com/doc/ - -* MySQL client/server protocol: - https://dev.mysql.com/doc/internals/en/client-server-protocol.html - -* "Connector" channel in MySQL Community Slack: - https://lefred.be/mysql-community-on-slack/ - -* PyMySQL mailing list: https://groups.google.com/forum/#!forum/pymysql-users - -License -------- - -PyMySQL is released under the MIT License. See LICENSE for more information. diff --git a/pyproject.toml b/pyproject.toml index 3793a8c1b..a0a36105c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ dependencies = [] requires-python = ">=3.7" -readme = "README.rst" +readme = "README.md" license = {text = "MIT License"} keywords = ["MySQL"] classifiers = [ From adff5ee6bf62be0d1bbc7eb8cb49e310d258ad51 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 23 Mar 2023 18:11:35 +0900 Subject: [PATCH 46/50] Update MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index e9e1eebcb..e2e577a9d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.rst LICENSE CHANGELOG.md +include README.md LICENSE CHANGELOG.md From d0c2871192b9a53733f32158dade3ea2e1847eab Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Fri, 24 Mar 2023 01:41:54 +0900 Subject: [PATCH 47/50] Release v1.0.3rc1 (#1094) --- pymysql/__init__.py | 2 +- pyproject.toml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pymysql/__init__.py b/pymysql/__init__.py index 5fe2aec54..291d5c6a9 100644 --- a/pymysql/__init__.py +++ b/pymysql/__init__.py @@ -47,7 +47,7 @@ ) -VERSION = (1, 0, 2, None) +VERSION = (1, 0, 3, "rc1") if VERSION[3] is not None: VERSION_STRING = "%d.%d.%d_%s" % VERSION else: diff --git a/pyproject.toml b/pyproject.toml index a0a36105c..dbb82c8d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "PyMySQL" -version = "1.0.2" description = "Pure Python MySQL Driver" authors = [ {name = "Inada Naoki", email = "songofacandy@gmail.com"}, @@ -26,6 +25,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Topic :: Database", ] +dynamic = ["version"] [project.optional-dependencies] "rsa" = [ @@ -47,3 +47,6 @@ build-backend = "setuptools.build_meta" namespaces = false include = ["pymysql"] exclude = ["tests*", "pymysql.tests*"] + +[tool.setuptools.dynamic] +version = {attr = "pymysql.VERSION"} From 35bf026a7fda258277548ab93195972aeb867322 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 27 Mar 2023 13:59:34 +0900 Subject: [PATCH 48/50] Fix setuptools didn't include pymysql.constants (#1096) Fix #1095 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dbb82c8d7..0f043181a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] namespaces = false -include = ["pymysql"] +include = ["pymysql*"] exclude = ["tests*", "pymysql.tests*"] [tool.setuptools.dynamic] From 7b0e0eab5fe0293a24adcdbdf479043eef939793 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 28 Mar 2023 12:34:54 +0900 Subject: [PATCH 49/50] v1.0.3 (#1097) --- pymysql/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymysql/__init__.py b/pymysql/__init__.py index 291d5c6a9..4b6cc2a99 100644 --- a/pymysql/__init__.py +++ b/pymysql/__init__.py @@ -47,7 +47,7 @@ ) -VERSION = (1, 0, 3, "rc1") +VERSION = (1, 0, 3, None) if VERSION[3] is not None: VERSION_STRING = "%d.%d.%d_%s" % VERSION else: From 930b25034f1a3b6e3a202e072675f163770b25cb Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 28 Mar 2023 12:53:08 +0900 Subject: [PATCH 50/50] Fix VERSION for dynamic version (#1098) --- pymysql/__init__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pymysql/__init__.py b/pymysql/__init__.py index 4b6cc2a99..c0039c3fe 100644 --- a/pymysql/__init__.py +++ b/pymysql/__init__.py @@ -47,11 +47,11 @@ ) -VERSION = (1, 0, 3, None) -if VERSION[3] is not None: +VERSION = (1, 0, 3) +if len(VERSION) > 3: VERSION_STRING = "%d.%d.%d_%s" % VERSION else: - VERSION_STRING = "%d.%d.%d" % VERSION[:3] + VERSION_STRING = "%d.%d.%d" % VERSION threadsafety = 1 apilevel = "2.0" paramstyle = "pyformat" @@ -113,10 +113,7 @@ def Binary(x): def get_client_info(): # for MySQLdb compatibility - version = VERSION - if VERSION[3] is None: - version = VERSION[:3] - return ".".join(map(str, version)) + return VERSION_STRING # we include a doctored version_info here for MySQLdb compatibility