Repository: ClearcodeHQ/pytest-mysql (language: python)

Issue:
Add type hints



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index f72fadce..9618543f 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -12,3 +12,4 @@ jobs:
     with:
       pipenv: true
       pipenv-install-options: "--skip-lock"
+      mypy: true
diff --git a/Pipfile b/Pipfile
index 23abf0c3..42e8f7ec 100644
--- a/Pipfile
+++ b/Pipfile
@@ -19,3 +19,6 @@ pytest-xdist = "==3.2.0"
 mock = "==4.0.3"
 coverage = "==6.5.0"
 tbump = "==6.9.0"
+mypy = "==1.0.1"
+types-setuptools = "==67.4.0.3"
+types-mysqlclient = "==2.1.5.6"
diff --git a/Pipfile.lock b/Pipfile.lock
index d1f3ef47..6626470c 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "43f1357e6f14d2b420d7f1974ecf29eb09c8d997155e08cf4844d8b27aa487f0"
+            "sha256": "456aced7a504972fd7ebd4ae193ba69efbd049fab05505385b257bf5c2d4e2dc"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -335,6 +335,38 @@
             "index": "pypi",
             "version": "==4.0.3"
         },
+        "mypy": {
+            "hashes": [
+                "sha256:0af4f0e20706aadf4e6f8f8dc5ab739089146b83fd53cb4a7e0e850ef3de0bb6",
+                "sha256:15b5a824b58c7c822c51bc66308e759243c32631896743f030daf449fe3677f3",
+                "sha256:17455cda53eeee0a4adb6371a21dd3dbf465897de82843751cf822605d152c8c",
+                "sha256:2013226d17f20468f34feddd6aae4635a55f79626549099354ce641bc7d40262",
+                "sha256:24189f23dc66f83b839bd1cce2dfc356020dfc9a8bae03978477b15be61b062e",
+                "sha256:27a0f74a298769d9fdc8498fcb4f2beb86f0564bcdb1a37b58cbbe78e55cf8c0",
+                "sha256:28cea5a6392bb43d266782983b5a4216c25544cd7d80be681a155ddcdafd152d",
+                "sha256:448de661536d270ce04f2d7dddaa49b2fdba6e3bd8a83212164d4174ff43aa65",
+                "sha256:48525aec92b47baed9b3380371ab8ab6e63a5aab317347dfe9e55e02aaad22e8",
+                "sha256:5bc8d6bd3b274dd3846597855d96d38d947aedba18776aa998a8d46fabdaed76",
+                "sha256:5deb252fd42a77add936b463033a59b8e48eb2eaec2976d76b6878d031933fe4",
+                "sha256:5f546ac34093c6ce33f6278f7c88f0f147a4849386d3bf3ae193702f4fe31407",
+                "sha256:5fdd63e4f50e3538617887e9aee91855368d9fc1dea30da743837b0df7373bc4",
+                "sha256:65b122a993d9c81ea0bfde7689b3365318a88bde952e4dfa1b3a8b4ac05d168b",
+                "sha256:71a808334d3f41ef011faa5a5cd8153606df5fc0b56de5b2e89566c8093a0c9a",
+                "sha256:920169f0184215eef19294fa86ea49ffd4635dedfdea2b57e45cb4ee85d5ccaf",
+                "sha256:93a85495fb13dc484251b4c1fd7a5ac370cd0d812bbfc3b39c1bafefe95275d5",
+                "sha256:a2948c40a7dd46c1c33765718936669dc1f628f134013b02ff5ac6c7ef6942bf",
+                "sha256:c6c2ccb7af7154673c591189c3687b013122c5a891bb5651eca3db8e6c6c55bd",
+                "sha256:c96b8a0c019fe29040d520d9257d8c8f122a7343a8307bf8d6d4a43f5c5bfcc8",
+                "sha256:d42a98e76070a365a1d1c220fcac8aa4ada12ae0db679cb4d910fabefc88b994",
+                "sha256:dbeb24514c4acbc78d205f85dd0e800f34062efcc1f4a4857c57e4b4b8712bff",
+                "sha256:e60d0b09f62ae97a94605c3f73fd952395286cf3e3b9e7b97f60b01ddfbbda88",
+                "sha256:e64f48c6176e243ad015e995de05af7f22bbe370dbb5b32bd6988438ec873919",
+                "sha256:e831662208055b006eef68392a768ff83596035ffd6d846786578ba1714ba8f6",
+                "sha256:eda5c8b9949ed411ff752b9a01adda31afe7eae1e53e946dbdf9db23865e66c4"
+            ],
+            "index": "pypi",
+            "version": "==1.0.1"
+        },
         "mypy-extensions": {
             "hashes": [
                 "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
@@ -474,6 +506,30 @@
             "index": "pypi",
             "version": "==22.12"
         },
+        "types-mysqlclient": {
+            "hashes": [
+                "sha256:0ff43cfcd3d07d020d48c13a77ba5994439f0d4941fef2195df7d918e6aadbab",
+                "sha256:b28f164fd54ea5cece0399d05068621c268c8e39a48b3b18d3fd6d3d50b27fe9"
+            ],
+            "index": "pypi",
+            "version": "==2.1.5.6"
+        },
+        "types-setuptools": {
+            "hashes": [
+                "sha256:19e958dfdbf1c5a628e54c2a7ee84935051afb7278d0c1cdb08ac194757ee3b1",
+                "sha256:3c83c3a6363dd3ddcdd054796705605f0fa8b8e5a39390e07a05e5f7af054978"
+            ],
+            "index": "pypi",
+            "version": "==67.4.0.3"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+                "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==4.5.0"
+        },
         "unidecode": {
             "hashes": [
                 "sha256:547d7c479e4f377b430dd91ac1275d593308dce0fc464fb2ab7d41f82ec653be",
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..1f441d03
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,20 @@
+[mypy]
+allow_redefinition = False
+allow_untyped_globals = False
+check_untyped_defs = True
+disallow_incomplete_defs = True
+disallow_subclassing_any = True
+disallow_untyped_calls = True
+disallow_untyped_decorators = True
+disallow_untyped_defs = True
+follow_imports = silent
+ignore_missing_imports = False
+implicit_reexport = False
+no_implicit_optional = True
+pretty = True
+show_error_codes = True
+strict_equality = True
+warn_no_return = True
+warn_return_any = True
+warn_unreachable = True
+warn_unused_ignores = True
\ No newline at end of file
diff --git a/newsfragments/401.break.rst b/newsfragments/401.break.rst
new file mode 100644
index 00000000..0f4b94ff
--- /dev/null
+++ b/newsfragments/401.break.rst
@@ -0,0 +1,1 @@
+Dropped support for Python 3.7
\ No newline at end of file
diff --git a/newsfragments/401.feature.rst b/newsfragments/401.feature.rst
new file mode 100644
index 00000000..be5e364f
--- /dev/null
+++ b/newsfragments/401.feature.rst
@@ -0,0 +1,1 @@
+Add type hints and mypy checks
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 764b8958..f2741e11 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,7 @@ dependencies = [
     "mirakuru",
     "mysqlclient",
 ]
-requires-python = ">= 3.7"
+requires-python = ">= 3.8"
 
 [project.urls]
 "Source" = "https://github.com/ClearcodeHQ/pytest-mysql"


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 8e25ade3..e64a6784 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -12,7 +12,7 @@ jobs:
     strategy:
       fail-fast: true
       matrix:
-        python-version: [3.7, 3.8, 3.9, "3.10", "3.11", pypy-3.8]
+        python-version: [3.8, 3.9, "3.10", "3.11", pypy-3.8]
     env:
       OS: ubuntu-latest
       PYTHON: ${{ matrix.python-version }}
@@ -72,7 +72,7 @@ jobs:
     strategy:
       fail-fast: true
       matrix:
-        python-version: [3.7, 3.8, 3.9, "3.10", "3.11", pypy-3.8]
+        python-version: [3.8, 3.9, "3.10", "3.11", pypy-3.8]
     env:
       OS: ubuntu-latest
       PYTHON: ${{ matrix.python-version }}
diff --git a/pytest_mysql/config.py b/pytest_mysql/config.py
index 829e5f5e..525ccc44 100644
--- a/pytest_mysql/config.py
+++ b/pytest_mysql/config.py
@@ -1,26 +1,46 @@
 """Config module."""
+from pathlib import Path
+from typing import TypedDict, Any
 
+from pytest import FixtureRequest
 
-def get_config(request):
+
+class MySQLConfigType(TypedDict):
+    """Configuration type dict."""
+
+    mysqld: Path
+    mysqld_safe: Path
+    admin: str
+    host: str
+    port: str
+    user: str
+    passwd: str
+    dbname: str
+    params: str
+    logsdir: str
+    install_db: str
+
+
+def get_config(request: FixtureRequest) -> MySQLConfigType:
     """Return a dictionary with config options."""
-    config = {}
-    options = [
-        "mysqld",
-        "mysqld_safe",
-        "admin",
-        "host",
-        "port",
-        "user",
-        "passwd",
-        "dbname",
-        "params",
-        "logsdir",
-        "install_db",
-    ]
-    for option in options:
+
+    def get_conf_option(option: str) -> Any:
         option_name = "mysql_" + option
-        conf = request.config.getoption(option_name) or request.config.getini(
+        return request.config.getoption(option_name) or request.config.getini(
             option_name
         )
-        config[option] = conf
+
+    config: MySQLConfigType = {
+        "mysqld": Path(get_conf_option("mysqld")),
+        "mysqld_safe": Path(get_conf_option("mysqld_safe")),
+        "admin": get_conf_option("admin"),
+        "host": get_conf_option("host"),
+        "port": get_conf_option("port"),
+        "user": get_conf_option("user"),
+        "passwd": get_conf_option("passwd"),
+        "dbname": get_conf_option("dbname"),
+        "params": get_conf_option("params"),
+        "logsdir": get_conf_option("logsdir"),
+        "install_db": get_conf_option("install_db"),
+    }
     return config
diff --git a/pytest_mysql/exceptions.py b/pytest_mysql/exceptions.py
index 57a768b8..dbf9290b 100644
--- a/pytest_mysql/exceptions.py
+++ b/pytest_mysql/exceptions.py
@@ -12,7 +12,7 @@ class MySQLUnsupported(PytestMySQLException):
 class VersionNotDetected(PytestMySQLException):
     """Exception raised when exector could not detect mysqls' version."""
 
-    def __init__(self, output):
+    def __init__(self, output: str) -> None:
         """Create error message."""
         super().__init__("Could not detect version in {}".format(output))
 
diff --git a/pytest_mysql/executor.py b/pytest_mysql/executor.py
index b8f9fad8..68c62798 100644
--- a/pytest_mysql/executor.py
+++ b/pytest_mysql/executor.py
@@ -1,6 +1,10 @@
 """Specified MySQL Executor."""
 import re
 import subprocess
+from pathlib import Path
+from typing import Optional, Union, Literal, Any
+
+from mirakuru.base import SimpleExecutorType
 from pkg_resources import parse_version
 
 from mirakuru import TCPExecutor
@@ -16,42 +20,43 @@ class MySQLExecutor(TCPExecutor):
 
     def __init__(
         self,
-        mysqld_safe,
-        mysqld,
-        admin_exec,
-        logfile_path,
-        params,
-        base_directory,
-        user,
-        host,
-        port,
-        timeout=60,
-        install_db=None,
-    ):
+        mysqld_safe: Path,
+        mysqld: Path,
+        admin_exec: str,
+        logfile_path: str,
+        params: str,
+        base_directory: Path,
+        user: str,
+        host: str,
+        port: int,
+        timeout: int = 60,
+        install_db: Optional[str] = None,
+    ) -> None:
         """
         Specialised Executor to run and manage MySQL server process.
 
-        :param str mysqld_safe: path to mysqld_safe executable
-        :param str mysqld: path to mysqld executable
-        :param str admin_exec: path to mysqladmin executable
-        :param str logfile_path: where the server shoyld wrute it's logs
-        :param str params: string containing additional starting parameters
-        :param path base_directory: base directory where the temporary files,
+        :param mysqld_safe: path to mysqld_safe executable
+        :param mysqld: path to mysqld executable
+        :param admin_exec: path to mysqladmin executable
+        :param logfile_path: where the server shoyld wrute it's logs
+        :param params: string containing additional starting parameters
+        :param base_directory: base directory where the temporary files,
             database files, socket and pid will be placed in.
-        :param str user: mysql user name
-        :param str host: server's host
-        :param int port: server's port
-        :param int timeout: executor's timeout for start and stop actions
-        :param int install_db:
+        :param user: mysql user name
+        :param host: server's host
+        :param port: server's port
+        :param timeout: executor's timeout for start and stop actions
+        :param install_db:
         """
         self.mysqld_safe = mysqld_safe
         self.mysqld = mysqld
         self.install_db = install_db
         self.admin_exec = admin_exec
         self.base_directory = base_directory
-        self.datadir = self.base_directory.mkdir(f"mysqldata_{port}")
-        self.pidfile = self.base_directory.join(f"mysql-server.{port}.pid")
-        self.unixsocket = str(self.base_directory.join(f"mysql.{port}.sock"))
+        self.datadir = self.base_directory / f"mysqldata_{port}"
+        self.datadir.mkdir()
+        self.pidfile = self.base_directory / f"mysql-server.{port}.pid"
+        self.unixsocket = str(self.base_directory / f"mysql.{port}.sock")
         self.logfile_path = logfile_path
         self.user = user
         self._initialised = False
@@ -67,17 +72,17 @@ def __init__(
         )
         super().__init__(command, host, port, timeout=timeout)
 
-    def version(self):
+    def version(self) -> str:
         """Read MySQL's version."""
         version_output = subprocess.check_output(
             [self.mysqld, "--version"]
         ).decode("utf-8")
-        try:
-            return self.VERSION_RE.search(version_output).groupdict()["version"]
-        except AttributeError as exc:
-            raise VersionNotDetected(version_output) from exc
+        matches = self.VERSION_RE.search(version_output)
+        if not matches:
+            raise VersionNotDetected(version_output)
+        return matches.groupdict()["version"]
 
-    def implementation(self):
+    def implementation(self) -> Union[Literal["mariadb"], Literal["mysql"]]:
         """Detect MySQL Implementation."""
         version_output = subprocess.check_output(
             [self.mysqld, "--version"]
@@ -86,7 +91,7 @@ def implementation(self):
             return "mariadb"
         return "mysql"
 
-    def initialize_mysqld(self):
+    def initialize_mysqld(self) -> None:
         """
         Initialise mysql directory.
 
@@ -109,7 +114,7 @@ def initialize_mysqld(self):
         subprocess.check_output(init_command, shell=True)
         self._initialised = True
 
-    def initialise_mysql_db_install(self):
+    def initialise_mysql_db_install(self) -> None:
         """
         Initialise mysql directory for older MySQL installations or MariaDB.
 
@@ -131,7 +136,7 @@ def initialise_mysql_db_install(self):
         subprocess.check_output(init_command, shell=True)
         self._initialised = True
 
-    def start(self):
+    def start(self) -> "MySQLExecutor":
         """Trigger initialisation during start."""
         implementation = self.implementation()
         if implementation == "mysql" and parse_version(
@@ -147,9 +152,9 @@ def start(self):
             raise MySQLUnsupported(
                 "Only MySQL and MariaDB servers are supported with MariaDB."
             )
-        super().start()
+        return super().start()
 
-    def shutdown(self):
+    def shutdown(self) -> None:
         """Send shutdown command to the server."""
         shutdown_command = (
             f"{self.admin_exec} --socket={self.unixsocket} "
@@ -165,7 +170,7 @@ def shutdown(self):
             )
             subprocess.check_output(shutdown_command, shell=True)
 
-    def stop(self, sig=None, exp_sig=None):
+    def stop(self, *args: Any, **kwargs: Any) -> "MySQLExecutor":
         """Stop the server."""
         self.shutdown()
-        super().stop(sig, exp_sig)
+        return super().stop(*args, **kwargs)
diff --git a/pytest_mysql/executor_noop.py b/pytest_mysql/executor_noop.py
index 2cc57c0e..0dad804d 100644
--- a/pytest_mysql/executor_noop.py
+++ b/pytest_mysql/executor_noop.py
@@ -1,4 +1,5 @@
 """Module containing Noop executor."""
+from typing import Any, Literal
 
 
 class NoopMySQLExecutor:
@@ -10,24 +11,27 @@ class NoopMySQLExecutor:
 
     def __init__(
         self,
-        user,
-        host,
-        port,
-    ):
+        user: str,
+        host: str,
+        port: int,
+    ) -> None:
         """Initialize NoopMySQLExecutor."""
         self.user = user
         self.host = host
         self.port = port
         self.unixsocket = None
 
-    def running(self):
+    def running(self) -> Literal[True]:
         """Check if process is running."""
         return True
 
-    def __enter__(self):
+    def start(self) -> None:
+        """Do nothing starter."""
+
+    def __enter__(self) -> None:
         """Do nothing enter method."""
         pass
 
-    def __exit__(self, *args, **kwargs):
+    def __exit__(self, *args: Any, **kwargs: Any) -> None:
         """Do nothing exit method."""
         pass
diff --git a/pytest_mysql/factories/client.py b/pytest_mysql/factories/client.py
index e0790862..005ec4ca 100644
--- a/pytest_mysql/factories/client.py
+++ b/pytest_mysql/factories/client.py
@@ -16,11 +16,11 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with pytest-mysql.  If not, see <http://www.gnu.org/licenses/>.
 """Client fixture factory for MySQL database."""
-from typing import Union
+from typing import Union, Generator, Callable, Any, Optional, Dict
 
 import pytest
 import MySQLdb
-from MySQLdb import ProgrammingError, OperationalError
+from MySQLdb import ProgrammingError, OperationalError, Connection
 from _pytest.fixtures import FixtureRequest
 
 from pytest_mysql.config import get_config
@@ -30,12 +30,12 @@
 
 
 def mysql(
-    process_fixture_name,
-    passwd=None,
-    dbname=None,
-    charset="utf8",
-    collation="utf8_general_ci",
-):
+    process_fixture_name: str,
+    passwd: Optional[str] = None,
+    dbname: Optional[str] = None,
+    charset: str = "utf8",
+    collation: str = "utf8_general_ci",
+) -> Callable[[FixtureRequest], Any]:
     """
     Client fixture factory for MySQL server.
 
@@ -62,7 +62,7 @@ def _connect(
         connect_kwargs: dict, query_str: str, mysql_db: str
     ) -> MySQLdb.Connection:
         """Apply given query to a  given MySQLdb connection."""
-        mysql_conn: MySQLdb.Connection = MySQLdb.connect(**connect_kwargs)
+        mysql_conn: MySQLdb.Connection = Connection(**connect_kwargs)
         try:
             mysql_conn.query(query_str)
         except ProgrammingError as e:
@@ -78,7 +78,9 @@ def _connect(
         return mysql_conn
 
     @pytest.fixture
-    def mysql_fixture(request: FixtureRequest) -> MySQLdb.Connection:
+    def mysql_fixture(
+        request: FixtureRequest,
+    ) -> Generator[MySQLdb.Connection, None, None]:
         """
         Client fixture for MySQL server.
 
@@ -104,7 +106,7 @@ def mysql_fixture(request: FixtureRequest) -> MySQLdb.Connection:
         mysql_passwd = passwd or config["passwd"]
         mysql_db = dbname or config["dbname"]
 
-        connection_kwargs = {
+        connection_kwargs: Dict[str, Union[str, int]] = {
             "host": process.host,
             "user": mysql_user,
             "passwd": mysql_passwd,
@@ -126,17 +128,13 @@ def mysql_fixture(request: FixtureRequest) -> MySQLdb.Connection:
         except OperationalError:
             # Fallback to mysql connection with root user
             connection_kwargs["user"] = "root"
-            mysql_conn: MySQLdb.Connection = _connect(
-                connection_kwargs, query_str, mysql_db
-            )
+            mysql_conn = _connect(connection_kwargs, query_str, mysql_db)
         mysql_conn.query(f"USE `{mysql_db}`")
         yield mysql_conn
 
         # clean up after test that forgot to fetch selected data
         if not mysql_conn.open:
-            mysql_conn: MySQLdb.Connection = MySQLdb.connect(
-                **connection_kwargs
-            )
+            mysql_conn = Connection(**connection_kwargs)
         try:
             mysql_conn.store_result()
         except Exception as e:
diff --git a/pytest_mysql/factories/noprocess.py b/pytest_mysql/factories/noprocess.py
index f8091dbb..56ffa986 100644
--- a/pytest_mysql/factories/noprocess.py
+++ b/pytest_mysql/factories/noprocess.py
@@ -18,6 +18,7 @@
 """Process fixture factory for MySQL database."""
 
 import os
+from typing import Optional, Callable, Any, Generator
 from warnings import warn
 
 import pytest
@@ -27,7 +28,11 @@
 from pytest_mysql.executor_noop import NoopMySQLExecutor
 
 
-def mysql_noproc(host=None, port=None, user=None):
+def mysql_noproc(
+    host: Optional[str] = None,
+    port: Optional[int] = None,
+    user: Optional[str] = None,
+) -> Callable[[FixtureRequest], Generator[NoopMySQLExecutor, None, None]]:
     """
     Process fixture factory for MySQL server.
 
@@ -40,7 +45,9 @@ def mysql_noproc(host=None, port=None, user=None):
     """
 
     @pytest.fixture(scope="session")
-    def mysql_noproc_fixture(request: FixtureRequest):
+    def mysql_noproc_fixture(
+        request: FixtureRequest,
+    ) -> Generator[NoopMySQLExecutor, None, None]:
         """
         Process fixture for MySQL server.
 
diff --git a/pytest_mysql/factories/process.py b/pytest_mysql/factories/process.py
index de93487c..57f4ded1 100644
--- a/pytest_mysql/factories/process.py
+++ b/pytest_mysql/factories/process.py
@@ -18,7 +18,8 @@
 """Process fixture factory for MySQL database."""
 
 import os
-import py
+from pathlib import Path
+from typing import Callable, Any, Optional, Union, Tuple, Set, List, Generator
 from warnings import warn
 
 import pytest
@@ -30,42 +31,54 @@
 
 
 def mysql_proc(
-    mysqld_exec=None,
-    admin_executable=None,
-    mysqld_safe=None,
-    host=None,
-    user=None,
-    port=-1,
-    params=None,
-    logs_prefix="",
-    install_db=None,
-):
+    mysqld_exec: Optional[Path] = None,
+    admin_executable: Optional[str] = None,
+    mysqld_safe: Optional[Path] = None,
+    host: Optional[str] = None,
+    user: Optional[str] = None,
+    port: Union[
+        None,
+        str,
+        int,
+        Tuple[int, int],
+        Set[int],
+        List[str],
+        List[int],
+        List[Tuple[int, int]],
+        List[Set[int]],
+        List[Union[Set[int], Tuple[int, int]]],
+        List[Union[str, int, Tuple[int, int], Set[int]]],
+    ] = -1,
+    params: Optional[str] = None,
+    logs_prefix: str = "",
+    install_db: Optional[str] = None,
+) -> Callable[
+    [FixtureRequest, TempPathFactory], Generator[MySQLExecutor, None, None]
+]:
     """
     Process fixture factory for MySQL server.
 
-    :param str mysqld_exec: path to mysql executable
-    :param str admin_executable: path to mysql_admin executable
-    :param str mysqld_safe: path to mysqld_safe executable
-    :param str host: hostname
-    :param str user: user name
-    :param str|int|tuple|set|list port:
+    :param mysqld_exec: path to mysql executable
+    :param admin_executable: path to mysql_admin executable
+    :param mysqld_safe: path to mysqld_safe executable
+    :param host: hostname
+    :param user: user name
+    :param port:
         exact port (e.g. '8000', 8000)
         randomly selected port (None) - any random available port
         [(2000,3000)] or (2000,3000) - random available port from a given range
         [{4002,4003}] or {4002,4003} - random of 4002 or 4003 ports
         [(2000,3000), {4002,4003}] -random of given range and set
-    :param str params: additional command-line mysqld parameters
-    :param str logs_prefix: prefix for log filename
-    :param str install_db: path to legacy mysql_install_db script
-    :rtype: func
+    :param params: additional command-line mysqld parameters
+    :param logs_prefix: prefix for log filename
+    :param install_db: path to legacy mysql_install_db script
     :returns: function which makes a mysql process
-
     """
 
     @pytest.fixture(scope="session")
     def mysql_proc_fixture(
         request: FixtureRequest, tmp_path_factory: TempPathFactory
-    ):
+    ) -> Generator[MySQLExecutor, None, None]:
         """
         Process fixture for MySQL server.
 
@@ -87,13 +100,12 @@ def mysql_proc_fixture(
         mysql_admin_exec = admin_executable or config["admin"]
         mysql_mysqld_safe = mysqld_safe or config["mysqld_safe"]
         mysql_port = get_port(port) or get_port(config["port"])
+        assert mysql_port
         mysql_host = host or config["host"]
         mysql_params = params or config["params"]
         mysql_install_db = install_db or config["install_db"]
 
-        tmpdir = py.path.local(
-            tmp_path_factory.mktemp(f"pytest-mysql-{request.fixturename}")
-        )
+        tmpdir = tmp_path_factory.mktemp(f"pytest-mysql-{request.fixturename}")
 
         if logs_prefix:
             warn(
@@ -104,6 +116,7 @@ def mysql_proc_fixture(
                 DeprecationWarning,
             )
 
+        logfile_path = tmpdir / f"mysql-server.{port}.log"
         logsdir = config["logsdir"]
         if logsdir:
             warn(
@@ -113,18 +126,16 @@ def mysql_proc_fixture(
                 DeprecationWarning,
             )
             if logs_prefix:
-                logfile_path = os.path.join(
-                    logsdir,
-                    f"{logs_prefix}mysql-server.{mysql_port}.log",
+                logfile_path = (
+                    Path(logsdir)
+                    / f"{logs_prefix}mysql-server.{mysql_port}.log"
                 )
-        else:
-            logfile_path = tmpdir.join(f"mysql-server.{port}.log")
 
         mysql_executor = MySQLExecutor(
             mysqld_safe=mysql_mysqld_safe,
             mysqld=mysql_mysqld,
             admin_exec=mysql_admin_exec,
-            logfile_path=logfile_path,
+            logfile_path=str(logfile_path),
             base_directory=tmpdir,
             params=mysql_params,
             user=user or config["user"] or "root",
diff --git a/pytest_mysql/plugin.py b/pytest_mysql/plugin.py
index f19921d7..a65cbb84 100644
--- a/pytest_mysql/plugin.py
+++ b/pytest_mysql/plugin.py
@@ -18,6 +18,8 @@
 """Plugin definition."""
 from tempfile import gettempdir
 
+from pytest import Parser
+
 from pytest_mysql import factories
 
 # pylint:disable=invalid-name
@@ -36,7 +38,7 @@
 _help_params = "Starting parameters for the MySQL"
 
 
-def pytest_addoption(parser):
+def pytest_addoption(parser: Parser) -> None:
     """Plugin configuration."""
     parser.addini(name="mysql_mysqld", help=_help_mysqld, default="mysqld")
 
diff --git a/pytest_mysql/py.typed b/pytest_mysql/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/conftest.py b/tests/conftest.py
index 1eb18b83..815c9326 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,5 @@
 """Tests main conftest file."""
+from pytest_mysql.executor import MySQLExecutor
 from pytest_mysql.plugin import *
 from pytest_mysql import factories
 
diff --git a/tests/test_executor.py b/tests/test_executor.py
index e3ad95e0..da1f0c3d 100644
--- a/tests/test_executor.py
+++ b/tests/test_executor.py
@@ -1,7 +1,9 @@
 """Executor tests."""
+from pathlib import Path
 from unittest.mock import patch
 
 import pytest
+from pytest import TempPathFactory
 
 from pytest_mysql.executor import MySQLExecutor
 from pytest_mysql.exceptions import MySQLUnsupported
@@ -37,15 +39,17 @@
         ((b"\nmysqld  Ver 5.7.23 for osx10.13 on x86_64 (Homebrew)"), "5.7.23"),
     ),
 )
-def test_version_check(verstr, version, tmpdir_factory):
+def test_version_check(
+    verstr: bytes, version: str, tmp_path_factory: TempPathFactory
+) -> None:
     """Test executor's version property."""
     executor = MySQLExecutor(
-        mysqld_safe="",
-        mysqld="",
+        mysqld_safe=Path(""),
+        mysqld=Path(""),
         admin_exec="",
         logfile_path="",
-        params={},
-        base_directory=tmpdir_factory.mktemp("pytest-mysql"),
+        params="",
+        base_directory=tmp_path_factory.mktemp("pytest-mysql"),
         user="",
         host="",
         port=8838,
@@ -89,15 +93,17 @@ def test_version_check(verstr, version, tmpdir_factory):
         ),
     ),
 )
-def test_implementation(verstr, implementation, tmpdir_factory):
+def test_implementation(
+    verstr: bytes, implementation: str, tmp_path_factory: TempPathFactory
+) -> None:
     """Check detecting implementation."""
     executor = MySQLExecutor(
-        mysqld_safe="",
-        mysqld="",
+        mysqld_safe=Path(""),
+        mysqld=Path(""),
         admin_exec="",
         logfile_path="",
-        params={},
-        base_directory=tmpdir_factory.mktemp("pytest-mysql"),
+        params="",
+        base_directory=tmp_path_factory.mktemp("pytest-mysql"),
         user="",
         host="",
         port=8838,
@@ -119,15 +125,17 @@ def test_implementation(verstr, implementation, tmpdir_factory):
         ),
     ),
 )
-def test_exception_raised(verstr, tmpdir_factory):
+def test_exception_raised(
+    verstr: bytes, tmp_path_factory: TempPathFactory
+) -> None:
     """Raise exception on not supported versions."""
     executor = MySQLExecutor(
-        mysqld_safe="",
-        mysqld="",
+        mysqld_safe=Path(""),
+        mysqld=Path(""),
         admin_exec="",
         logfile_path="",
-        params={},
-        base_directory=tmpdir_factory.mktemp("pytest-mysql"),
+        params="",
+        base_directory=tmp_path_factory.mktemp("pytest-mysql"),
         user="",
         host="",
         port=8838,
diff --git a/tests/test_mysql.py b/tests/test_mysql.py
index b579fef2..ab8e8269 100644
--- a/tests/test_mysql.py
+++ b/tests/test_mysql.py
@@ -1,16 +1,18 @@
 """Actual tests for pytest-mysql."""
+from MySQLdb import Connection
 
+from pytest_mysql.executor import MySQLExecutor
 
 QUERY = """CREATE TABLE pet (name VARCHAR(20), owner VARCHAR(20),
     species VARCHAR(20), sex CHAR(1), birth DATE, death DATE);"""
 
 
-def test_proc(mysql_proc):
+def test_proc(mysql_proc: MySQLExecutor) -> None:
     """Check first, basic server fixture factory."""
     assert mysql_proc.running()
 
 
-def test_mysql(mysql):
+def test_mysql(mysql: Connection) -> None:
     """Check first, basic client fixture factory."""
     cursor = mysql.cursor()
     cursor.execute(QUERY)
@@ -18,12 +20,12 @@ def test_mysql(mysql):
     cursor.close()
 
 
-def test_mysql_test_without_cursor(mysql):
+def test_mysql_test_without_cursor(mysql: Connection) -> None:
     """Run test without cursor and without fetching the data."""
     mysql.query("SELECT VERSION();")
 
 
-def test_mysql_newfixture(mysql, mysql2):
+def test_mysql_newfixture(mysql: Connection, mysql2: Connection) -> None:
     """More complext test with several mysql_processes."""
     cursor = mysql.cursor()
     cursor.execute(QUERY)
@@ -36,7 +38,7 @@ def test_mysql_newfixture(mysql, mysql2):
     cursor.close()
 
 
-def test_random_port(mysql_rand):
+def test_random_port(mysql_rand: Connection) -> None:
     """Test if mysql fixture can be started on random port."""
     mysql = mysql_rand
     mysql.cursor()
diff --git a/tests/test_mysqlnoproc.py b/tests/test_mysqlnoproc.py
index a6d2186c..c85fbc5b 100644
--- a/tests/test_mysqlnoproc.py
+++ b/tests/test_mysqlnoproc.py
@@ -1,3 +1,5 @@
+from MySQLdb import Connection
+
 from pytest_mysql import factories
 from tests.test_mysql import QUERY
 
@@ -5,7 +7,7 @@
 mysqlnoproc_client = factories.mysql("mysql_noproc2")
 
 
-def test_mysql_noproc(mysqlnoproc_client):
+def test_mysql_noproc(mysqlnoproc_client: Connection) -> None:
     """Check if noproc fixture connects to the running mysql instance."""
     cursor = mysqlnoproc_client.cursor()
     cursor.execute(QUERY)
@@ -14,8 +16,8 @@ def test_mysql_noproc(mysqlnoproc_client):
 
 
 def test_mysql_noproc_closing_connection_not_throwing_exception(
-    mysqlnoproc_client,
-):
+    mysqlnoproc_client: Connection,
+) -> None:
     """
     Check if closing the connection doesn't throw an exception
     when cleaning the fixture.