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/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index b6a7238dd..d559b1cd2 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'
@@ -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
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"
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
new file mode 100644
index 000000000..5dde1354a
--- /dev/null
+++ b/.github/workflows/lock.yml
@@ -0,0 +1,16 @@
+name: 'Lock Threads'
+
+on:
+ schedule:
+ - cron: '30 9 * * 1'
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ action:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v4
+
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 09846c943..993347f64 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -4,33 +4,39 @@ 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
+ runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
include:
- - db: "mariadb:10.0"
- 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"
+ - db: "mariadb:10.7"
+ py: "3.11"
+
+ - db: "mariadb:10.8"
+ py: "3.9"
- 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"
+
services:
mysql:
image: "${{ matrix.db }}"
@@ -39,24 +45,28 @@ jobs:
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
options: "--name=mysqld"
+ volumes:
+ - /run/mysqld:/run/mysqld
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
+
+ - 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
+ uses: actions/setup-python@v4
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 --upgrade -r requirements-dev.txt
- name: Set up MySQL
run: |
@@ -66,15 +76,15 @@ 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
run: |
pytest -v --cov --cov-config .coveragerc pymysql
+ pytest -v --cov-append --cov-config .coveragerc --doctest-modules pymysql/converters.py
- name: Run MySQL8 auth test
if: ${{ matrix.mysql_auth }}
@@ -84,43 +94,34 @@ 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
+ if: github.repository == 'PyMySQL/PyMySQL'
+ 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:
+ if: github.repository == 'PyMySQL/PyMySQL'
name: Finish coveralls
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
needs: test
- container: python:3-slim
steps:
+ - name: requirements.
+ run: |
+ echo coveralls > requirements.txt
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.x'
+ cache: 'pip'
+
- name: Finished
run: |
- pip3 install --upgrade coveralls
- coveralls --finish
+ pip install --upgrade coveralls
+ coveralls --finish --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9885af526..87c3f9e86 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.3
+* Dropped support of end of life Python version 3.6
+
+
## v1.0.2
Release date: 2021-01-09
@@ -195,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/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
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..dec840803
--- /dev/null
+++ b/README.md
@@ -0,0 +1,105 @@
+[](https://pymysql.readthedocs.io/)
+[](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 279181f16..000000000
--- a/README.rst
+++ /dev/null
@@ -1,148 +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=master&service=github
- :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=master
-
-.. 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
-=======
-
-.. contents:: Table of Contents
- :local:
-
-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/
-
-
-Requirements
--------------
-
-* Python -- one of the following:
-
- - CPython_ : 3.6 and newer
- - PyPy_ : Latest 3.x version
-
-* MySQL Server -- one of the following:
-
- - MySQL_ >= 5.6
- - MariaDB_ >= 10.0
-
-.. _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/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/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"}
]
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/docs/source/user/installation.rst b/docs/source/user/installation.rst
index 0fea27266..9313f14d3 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.3
.. _CPython: http://www.python.org/
.. _PyPy: http://pypy.org/
diff --git a/pymysql/__init__.py b/pymysql/__init__.py
index 5fe2aec54..c0039c3fe 100644
--- a/pymysql/__init__.py
+++ b/pymysql/__init__.py
@@ -47,11 +47,11 @@
)
-VERSION = (1, 0, 2, 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
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/connections.py b/pymysql/connections.py
index 92b7a77e5..3265d32ee 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
@@ -61,7 +61,7 @@
DEFAULT_CHARSET = "utf8mb4"
-MAX_PACKET_LEN = 2 ** 24 - 1
+MAX_PACKET_LEN = 2**24 - 1
def _pack_int24(n):
@@ -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 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.
+ :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.
@@ -205,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
@@ -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()
@@ -442,13 +441,16 @@ 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
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 +498,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 +512,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 +532,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 +566,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:
@@ -652,9 +655,10 @@ def connect(self, sock=None):
except: # noqa
pass
- if isinstance(e, (OSError, IOError, socket.error)):
+ if isinstance(e, (OSError, IOError)):
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
@@ -898,10 +902,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()
@@ -920,10 +924,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 switch request")
elif auth_packet.is_extra_auth_data():
if DEBUG:
print("received extra data")
@@ -948,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)),
@@ -986,23 +987,22 @@ 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,
- "Authentication plugin '%s' (%r) not configured"
- % (plugin_name, handler),
+ CR.CR_AUTH_PLUGIN_CANNOT_LOAD,
+ "Authentication plugin '%s' not configured" % (plugin_name,),
)
pkt = self._read_packet()
pkt.check_error()
@@ -1011,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)
@@ -1028,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),
@@ -1215,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):
@@ -1361,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"")
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
diff --git a/pymysql/converters.py b/pymysql/converters.py
index d910f5c5c..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"
@@ -155,18 +155,17 @@ 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
- True
- >>> datetime_or_None('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")
@@ -189,15 +188,15 @@ def convert_datetime(obj):
def convert_timedelta(obj):
"""Returns a TIME column as a timedelta object:
- >>> timedelta_or_None('25:06:17')
- datetime.timedelta(1, 3977)
- >>> timedelta_or_None('-25:06:17')
- datetime.timedelta(-2, 83177)
+ >>> convert_timedelta('25:06:17')
+ datetime.timedelta(days=1, seconds=3977)
+ >>> convert_timedelta('-25:06:17')
+ datetime.timedelta(days=-2, seconds=82423)
- Illegal values are returned as None:
+ Illegal values are returned as string:
- >>> timedelta_or_None('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
@@ -236,15 +235,15 @@ 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:
+ Illegal values are returned as str:
- >>> time_or_None('-25:06:17') is None
- True
- >>> time_or_None('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
@@ -279,16 +278,15 @@ 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
- True
- >>> date_or_None('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`
diff --git a/pymysql/cursors.py b/pymysql/cursors.py
index 666970b98..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 ()
@@ -305,7 +321,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 +425,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()
@@ -420,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:
@@ -452,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
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_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 c2590bf2f..8af07da09 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,11 +172,9 @@ 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)):
- 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))")
@@ -243,7 +241,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 +253,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 +267,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(
@@ -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,
@@ -312,7 +312,6 @@ def test_json(self):
class TestBulkInserts(base.PyMySQLTestCase):
-
cursor_type = pymysql.cursors.DictCursor
def setUp(self):
@@ -320,7 +319,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",
@@ -353,7 +352,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 +376,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 +421,7 @@ def test_issue_288(self):
data,
)
self.assertEqual(
- cursor._last_executed.strip(),
+ cursor._executed.strip(),
bytearray(
b"""insert
into bulkinsert (id, name,
diff --git a/pymysql/tests/test_connection.py b/pymysql/tests/test_connection.py
index 75db73cd0..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
@@ -53,6 +52,7 @@ class TestAuthentication(base.PyMySQLTestCase):
pam_found = False
mysql_old_password_found = False
sha256_password_found = False
+ ed25519_found = False
import os
@@ -97,13 +97,13 @@ 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])
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()"
@@ -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
@@ -398,13 +398,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"
@@ -412,6 +406,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):
@@ -468,7 +491,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 b4ced4b06..733d56a16 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():
@@ -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"]
@@ -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,
@@ -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/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/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_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()
diff --git a/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py b/pymysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py
index e882c5eb3..bc1e1b2ea 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
@@ -95,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)
@@ -108,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)
)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..0f043181a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,52 @@
+[project]
+name = "PyMySQL"
+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.md"
+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",
+]
+dynamic = ["version"]
+
+[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*"]
+
+[tool.setuptools.dynamic]
+version = {attr = "pymysql.VERSION"}
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
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index b40802e4b..000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,13 +0,0 @@
-[flake8]
-ignore = E203,E501,W503,E722
-exclude = tests,build,.venv,docs
-
-[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 1510a0cf8..000000000
--- a/setup.py
+++ /dev/null
@@ -1,38 +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.6",
- extras_require={
- "rsa": ["cryptography"],
- "ed25519": ["PyNaCl>=1.4.0"],
- },
- 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 :: Implementation :: CPython",
- "Programming Language :: Python :: Implementation :: PyPy",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: MIT License",
- "Topic :: Database",
- ],
- keywords="MySQL",
-)
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