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

Issue:
Review typing



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index 534e3b3..cac1e13 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -12,3 +12,4 @@ jobs:
     with:
       ruff: true
       black: true
+      mypy: true
diff --git a/Pipfile b/Pipfile
index be54be0..97258fa 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,7 +6,7 @@ name = "pypi"
 [packages]
 pytest = "==7.4.0"
 mirakuru = "==2.5.1"
-port-for = "==0.7.0"
+port-for = "==0.7.1"
 pymongo = "==4.4.1"
 
 [dev-packages]
@@ -15,3 +15,4 @@ black = "==23.3.0"
 pytest-cov = "==4.1.0"
 pytest-xdist = "==3.3.1"
 ruff = "==0.0.278"
+mypy = "==1.4.1"
diff --git a/Pipfile.lock b/Pipfile.lock
index cf6e95c..6be8894 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "dde1325b1cbbe2cd2a48a8b8a14fe70b56375678593a7633def8a91e63bfb059"
+            "sha256": "dfceca4dfe376cb7df06b05193ec0c0e316824c6fea700d7e09ef33198f5a7b7"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -56,11 +56,11 @@
         },
         "port-for": {
             "hashes": [
-                "sha256:2cdd35f12919f48a3c2a78df1f64d5e0791a0bac34172253bd52a7e5a0c676c8",
-                "sha256:c8e948fd833b45af3814603fb11107ca2e58022ad6e6e24ae7428f8cd75cdd29"
+                "sha256:8abdaa1a7810281b0cecf718a6319da5f8538fdae3a5101d1e9afd54da0baf8c",
+                "sha256:e0f3b8d7ec8bdc388c14d88bdbab8d6441c8081ed2bdec7847b38ca6d1563f23"
             ],
             "index": "pypi",
-            "version": "==0.7.0"
+            "version": "==0.7.1"
         },
         "psutil": {
             "hashes": [
@@ -373,6 +373,38 @@
             "markers": "python_version >= '3.7'",
             "version": "==2.1.3"
         },
+        "mypy": {
+            "hashes": [
+                "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042",
+                "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd",
+                "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2",
+                "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01",
+                "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7",
+                "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3",
+                "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816",
+                "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3",
+                "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc",
+                "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4",
+                "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b",
+                "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8",
+                "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c",
+                "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462",
+                "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7",
+                "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc",
+                "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258",
+                "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b",
+                "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9",
+                "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6",
+                "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f",
+                "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1",
+                "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828",
+                "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878",
+                "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f",
+                "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"
+            ],
+            "index": "pypi",
+            "version": "==1.4.1"
+        },
         "mypy-extensions": {
             "hashes": [
                 "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
@@ -467,6 +499,14 @@
             ],
             "index": "pypi",
             "version": "==23.6.0"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
+                "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==4.7.1"
         }
     }
 }
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..2c2bfb0
--- /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
diff --git a/newsfragments/384.break.rst b/newsfragments/384.break.rst
new file mode 100644
index 0000000..3d2732e
--- /dev/null
+++ b/newsfragments/384.break.rst
@@ -0,0 +1,1 @@
+Dropped support for python 3.7
\ No newline at end of file
diff --git a/newsfragments/384.feature.rst b/newsfragments/384.feature.rst
new file mode 100644
index 0000000..2ef98d0
--- /dev/null
+++ b/newsfragments/384.feature.rst
@@ -0,0 +1,1 @@
+Add typing and check types on CI
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 59d75ec..7a5cbde 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,7 @@ dependencies = [
     "mirakuru",
     "pymongo",
 ]
-requires-python = ">= 3.7"
+requires-python = ">= 3.8"
 
 [project.urls]
 "Source" = "https://github.com/ClearcodeHQ/pytest-mongo"


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 1d9ac6d..dc7bf8b 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 }}
diff --git a/pytest_mongo/config.py b/pytest_mongo/config.py
new file mode 100644
index 0000000..42f90b7
--- /dev/null
+++ b/pytest_mongo/config.py
@@ -0,0 +1,34 @@
+"""Mongo config getter."""
+from pathlib import Path
+from typing import Any, Optional, TypedDict
+
+from pytest import FixtureRequest
+
+
+class MongoConfigDict(TypedDict):
+    """Typed Config dictionary."""
+
+    exec: str
+    host: str
+    port: Optional[int]
+    params: str
+    logsdir: Path
+    tz_aware: bool
+
+
+def get_config(request: FixtureRequest) -> MongoConfigDict:
+    """Return a dictionary with config options."""
+
+    def get_mongo_option(option: str) -> Any:
+        name = "mongo_" + option
+        return request.config.getoption(name) or request.config.getini(name)
+
+    port = get_mongo_option("port")
+    return MongoConfigDict(
+        exec=get_mongo_option("exec"),
+        host=get_mongo_option("host"),
+        port=int(port) if port else None,
+        params=get_mongo_option("params"),
+        logsdir=get_mongo_option("logsdir"),
+        tz_aware=get_mongo_option("tz_aware"),
+    )
diff --git a/pytest_mongo/executor_noop.py b/pytest_mongo/executor_noop.py
index 5774188..7a05376 100644
--- a/pytest_mongo/executor_noop.py
+++ b/pytest_mongo/executor_noop.py
@@ -1,5 +1,7 @@
 """MongoDB Noop executor providing connection details for mongodb client."""
-import pymongo
+from typing import Optional
+
+from pymongo import MongoClient
 
 
 class NoopExecutor:  # pylint: disable=too-few-public-methods
@@ -10,21 +12,22 @@ class NoopExecutor:  # pylint: disable=too-few-public-methods
     or with the use of containerisation like kubernetes or docker compose.
     """
 
-    def __init__(self, host, port):
+    def __init__(self, host: str, port: int) -> None:
         """Initialize nooperator executor mock.
 
-        :param str host: MongoDB hostname
-        :param str|int port: MongoDB port
+        :param host: MongoDB hostname
+        :param port: MongoDB port
         """
         self.host = host
-        self.port = int(port)
-        self._version = None
+        self.port = port
+        self._version: Optional[str] = None
 
     @property
-    def version(self):
+    def version(self) -> str:
         """Get MongoDB's version."""
         if not self._version:
-            client = pymongo.MongoClient(host=self.host, port=self.port)
+            client: MongoClient = MongoClient(host=self.host, port=self.port)
             server_info = client.server_info()
             self._version = server_info["version"]
+        assert self._version
         return self._version
diff --git a/pytest_mongo/factories.py b/pytest_mongo/factories.py
index dc6eb2c..e216e53 100644
--- a/pytest_mongo/factories.py
+++ b/pytest_mongo/factories.py
@@ -19,52 +19,41 @@
 import os
 from shutil import rmtree
 from tempfile import gettempdir
-from typing import Callable, Union
+from typing import Callable, Iterator, Optional
 
-import pymongo
 import pytest
-from _pytest.fixtures import FixtureRequest
 from mirakuru import TCPExecutor
 from port_for import get_port
+from port_for.api import PortType
+from pymongo import MongoClient
+from pytest import FixtureRequest
 
+from pytest_mongo.config import get_config
 from pytest_mongo.executor_noop import NoopExecutor
 
 
-def get_config(request):
-    """Return a dictionary with config options."""
-    config = {}
-    options = ["exec", "host", "port", "params", "logsdir", "tz_aware"]
-    for option in options:
-        option_name = "mongo_" + option
-        conf = request.config.getoption(option_name) or request.config.getini(
-            option_name
-        )
-        config[option] = conf
-    return config
-
-
-def mongo_proc(executable=None, params=None, host=None, port=-1, logsdir=None):
+def mongo_proc(
+    executable: Optional[str] = None,
+    params: Optional[str] = None,
+    host: Optional[str] = None,
+    port: Optional[PortType] = -1,
+    logsdir: Optional[str] = None,
+) -> Callable[[FixtureRequest], Iterator[TCPExecutor]]:
     """Mongo process fixture factory.
 
     .. note::
         `mongod <http://docs.mongodb.org/v2.2/reference/mongod/>`_
 
-    :param str executable: path to mongod
-    :param str params: params
-    :param str host: hostname
-    :param str|int|tuple|set|list 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 logsdir: path to store log files.
-    :rtype: func
+    :param executable: path to mongod
+    :param params: params
+    :param host: hostname
+    :param port:
+    :param logsdir: path to store log files.
     :returns: function which makes a mongo process
     """
 
     @pytest.fixture(scope="session")
-    def mongo_proc_fixture(request):
+    def mongo_proc_fixture(request: FixtureRequest) -> Iterator[TCPExecutor]:
         """Mongodb process fixture.
 
         :param FixtureRequest request: fixture request object
@@ -78,15 +67,14 @@ def mongo_proc_fixture(request):
         mongo_params = params or config["params"]
 
         mongo_host = host or config["host"]
+        assert mongo_host
         mongo_port = get_port(port) or get_port(config["port"])
+        assert mongo_port
 
         mongo_logsdir = logsdir or config["logsdir"]
         mongo_logpath = os.path.join(mongo_logsdir, f"mongo.{mongo_port}.log")
         mongo_db_path = os.path.join(tmpdir, f"mongo.{mongo_port}")
         os.mkdir(mongo_db_path)
-        request.addfinalizer(
-            lambda: os.path.exists(mongo_db_path) and rmtree(mongo_db_path)
-        )
 
         mongo_executor = TCPExecutor(
             (
@@ -98,18 +86,17 @@ def mongo_proc_fixture(request):
             port=mongo_port,
             timeout=60,
         )
-        mongo_executor.start()
-
-        request.addfinalizer(mongo_executor.stop)
-
-        return mongo_executor
+        with mongo_executor:
+            yield mongo_executor
+        os.path.exists(mongo_db_path)
+        rmtree(mongo_db_path)
 
     return mongo_proc_fixture
 
 
 def mongo_noproc(
-    host: str = None, port: Union[str, int] = None
-) -> Callable[[FixtureRequest], NoopExecutor]:
+    host: Optional[str] = None, port: Optional[int] = None
+) -> Callable[[FixtureRequest], Iterator[NoopExecutor]]:
     """MongoDB noprocess factory.
 
     :param host: hostname
@@ -121,7 +108,7 @@ def mongo_noproc(
     """
 
     @pytest.fixture(scope="session")
-    def mongo_noproc_fixture(request: FixtureRequest) -> NoopExecutor:
+    def mongo_noproc_fixture(request: FixtureRequest) -> Iterator[NoopExecutor]:
         """Noop Process fixture for MongoDB.
 
         :param FixtureRequest request: fixture request object
@@ -130,6 +117,7 @@ def mongo_noproc_fixture(request: FixtureRequest) -> NoopExecutor:
         config = get_config(request)
         mongo_host = host or config["host"]
         mongo_port = port or config["port"] or 27017
+        assert mongo_port
 
         noop_exec = NoopExecutor(host=mongo_host, port=mongo_port)
 
@@ -138,7 +126,9 @@ def mongo_noproc_fixture(request: FixtureRequest) -> NoopExecutor:
     return mongo_noproc_fixture
 
 
-def mongodb(process_fixture_name, tz_aware=None):
+def mongodb(
+    process_fixture_name: str, tz_aware: Optional[bool] = None
+) -> Callable[[FixtureRequest], Iterator[MongoClient]]:
     """Mongo database factory.
 
     :param str process_fixture_name: name of the process fixture
@@ -148,7 +138,7 @@ def mongodb(process_fixture_name, tz_aware=None):
     """
 
     @pytest.fixture
-    def mongodb_factory(request):
+    def mongodb_factory(request: FixtureRequest) -> Iterator[MongoClient]:
         """Client fixture for MongoDB.
 
         :param FixtureRequest request: fixture request object
@@ -168,9 +158,9 @@ def mongodb_factory(request):
         mongo_host = mongodb_process.host
         mongo_port = mongodb_process.port
 
-        client = pymongo.MongoClient
-
-        mongo_conn = client(mongo_host, mongo_port, tz_aware=mongo_tz_aware)
+        mongo_conn: MongoClient = MongoClient(
+            mongo_host, mongo_port, tz_aware=mongo_tz_aware
+        )
 
         yield mongo_conn
 
diff --git a/pytest_mongo/plugin.py b/pytest_mongo/plugin.py
index 952c826..a4f19e5 100644
--- a/pytest_mongo/plugin.py
+++ b/pytest_mongo/plugin.py
@@ -19,6 +19,8 @@
 """Pytest-mongo plugin definition."""
 from tempfile import gettempdir
 
+from pytest import Parser
+
 from pytest_mongo import factories
 
 # pylint:disable=invalid-name
@@ -30,7 +32,7 @@
 _help_tz_aware = "Have mongo client timezone aware"
 
 
-def pytest_addoption(parser):
+def pytest_addoption(parser: Parser) -> None:
     """Configure pytest-mongo configuration options."""
     parser.addini(
         name="mongo_exec", help=_help_executable, default="/usr/bin/mongod"
diff --git a/tests/test_mongo.py b/tests/test_mongo.py
index 9b1d3a6..0ef082f 100644
--- a/tests/test_mongo.py
+++ b/tests/test_mongo.py
@@ -1,8 +1,11 @@
 """pytest-mongo tests collection."""
 import os
 
+from mirakuru import TCPExecutor
+from pymongo import MongoClient
 
-def test_mongo(mongodb):
+
+def test_mongo(mongodb: MongoClient) -> None:
     """Simple test for mongodb connection to the set up process."""
     test_data = {
         "test1": "test1",
@@ -10,40 +13,44 @@ def test_mongo(mongodb):
 
     database = mongodb["test_db"]
     database.test.insert_one(test_data)
-    assert database.test.find_one()["test1"] == "test1"
+    assert database.test.find_one()["test1"] == "test1"  # type:ignore
 
 
-def test_third_mongo(mongodb, mongodb2, mongodb3):
+def test_third_mongo(
+    mongodb: MongoClient, mongodb2: MongoClient, mongodb3: MongoClient
+) -> None:
     """Test with everal mongo processes and connections."""
     test_data_one = {
         "test1": "test1",
     }
     database = mongodb["test_db"]
     database.test.insert_one(test_data_one)
-    assert database.test.find_one()["test1"] == "test1"
+    assert database.test.find_one()["test1"] == "test1"  # type:ignore
 
     test_data_two = {
         "test2": "test2",
     }
     database = mongodb2["test_db"]
     database.test.insert_one(test_data_two)
-    assert database.test.find_one()["test2"] == "test2"
+    assert database.test.find_one()["test2"] == "test2"  # type:ignore
 
     test_data_three = {
         "test3": "test3",
     }
     database = mongodb3["test_db"]
     database.test.insert_one(test_data_three)
-    assert database.test.find_one()["test3"] == "test3"
+    assert database.test.find_one()["test3"] == "test3"  # type:ignore
 
 
-def test_mongo_proc(mongo_proc, mongo_proc2, mongo_proc3):
+def test_mongo_proc(
+    mongo_proc: TCPExecutor, mongo_proc2: TCPExecutor, mongo_proc3: TCPExecutor
+) -> None:
     """Several mongodb processes running."""
     for mongo in (mongo_proc, mongo_proc2, mongo_proc3):
         assert os.path.isfile("/tmp/mongo.{port}.log".format(port=mongo.port))
 
 
-def test_random_port(mongodb_rand):
+def test_random_port(mongodb_rand: MongoClient) -> None:
     """Test if mongo fixture can be started on random port."""
     server_info = mongodb_rand.server_info()
     assert "ok" in server_info
diff --git a/tests/test_noopexecutor.py b/tests/test_noopexecutor.py
index 9ac9488..8009b46 100644
--- a/tests/test_noopexecutor.py
+++ b/tests/test_noopexecutor.py
@@ -1,20 +1,21 @@
 """Test for NoopExecutor."""
 
-import pymongo
+from mirakuru import TCPExecutor
+from pymongo import MongoClient
 
 from pytest_mongo.executor_noop import NoopExecutor
 
 
-def test_nooproc_version(mongo_proc):
+def test_nooproc_version(mongo_proc: TCPExecutor) -> None:
     """Test the way mongo version is being read."""
     mongo_nooproc = NoopExecutor(mongo_proc.host, mongo_proc.port)
-    proc_client = pymongo.MongoClient(
+    proc_client: MongoClient = MongoClient(
         host=mongo_proc.host, port=mongo_proc.port
     )
     assert proc_client.server_info()["version"] == mongo_nooproc.version
 
 
-def test_nooproc_cached_version(mongo_proc):
+def test_nooproc_cached_version(mongo_proc: TCPExecutor) -> None:
     """Test that the version is being cached."""
     mongo_nooproc = NoopExecutor(mongo_proc.host, mongo_proc.port)
     ver = mongo_nooproc.version