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

Issue:
Add type hints



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index 88be0398..ab2522cd 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -10,5 +10,6 @@ jobs:
   lint:
     uses: fizyk/actions-reuse/.github/workflows/linters-python.yml@v1.6.4
     with:
+      mypy: true
       pipenv: true
       pipenv-install-options: "--skip-lock"
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index a540bfc1..00000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-include *.rst *.py
-recursive-include src/pytest_dynamodb *.py
-recursive-include docs *
-prune docs/build
diff --git a/Pipfile b/Pipfile
index 457faf57..2cef44c2 100644
--- a/Pipfile
+++ b/Pipfile
@@ -8,6 +8,7 @@ pytest = "==7.2.1"
 port-for = "==0.6.3"
 mirakuru = "==2.5.1"
 boto3 = "==1.26.79"
+boto3-stubs = {version = "==1.26.78", extras = ["dynamodb"]}
 
 [dev-packages]
 towncrier = "==22.12"
@@ -16,3 +17,4 @@ pytest-xdist = "==3.2.0"
 pycodestyle = "==2.10.0"
 pydocstyle = {version = "==6.3.0", extras = ["toml"]}
 black = "==23.1.0"
+mypy = "==1.0.1"
diff --git a/Pipfile.lock b/Pipfile.lock
index f93f5e8f..49bf638e 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,12 +1,10 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "c378159a52a1a1297978c86f070f7e4bc0e01b3e3baa2a693dfee639dc946086"
+            "sha256": "f027a91c5ba263029d5d204c71a59b09051ad172f47c2870ced47f2fef2d71f6"
         },
         "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.11"
-        },
+        "requires": {},
         "sources": [
             {
                 "name": "pypi",
@@ -32,6 +30,18 @@
             "index": "pypi",
             "version": "==1.26.79"
         },
+        "boto3-stubs": {
+            "extras": [
+                "dynamodb"
+            ],
+            "hashes": [
+                "sha256:0d48657c3f47b6455144020a11dab7d6049737136c5c85c9e07939696785d146",
+                "sha256:e8f1996078a696f88ba7ac1c39e3427cee82b9c9675150121a458ecc0a24e325"
+            ],
+            "index": "pypi",
+            "markers": null,
+            "version": "==1.26.78"
+        },
         "botocore": {
             "hashes": [
                 "sha256:5f254f019e8641f8b2ba6dddc1f7541e8c6d25d976802392710b2fc4bac925b1",
@@ -40,6 +50,14 @@
             "markers": "python_version >= '3.7'",
             "version": "==1.29.79"
         },
+        "botocore-stubs": {
+            "hashes": [
+                "sha256:14ddd9c2d3368db7dcec01864dff04d93d26881d5d1368e4b6493f6a2e77f023",
+                "sha256:d6f16d27cd3e2edafe72b6d3aa1fc8fe687feae1337753a67ceafc9a40670bc8"
+            ],
+            "markers": "python_version >= '3.7' and python_version < '4.0'",
+            "version": "==1.29.79"
+        },
         "iniconfig": {
             "hashes": [
                 "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
@@ -64,6 +82,13 @@
             "index": "pypi",
             "version": "==2.5.1"
         },
+        "mypy-boto3-dynamodb": {
+            "hashes": [
+                "sha256:41fc3303eb9b7153e2726f948a952cf7a1344cad5e44e0c225e3c6c3decfe2a6",
+                "sha256:94d059f692189fa3f1315df4e16f02b94018b09b61e3a54bd56c9715f5bfe418"
+            ],
+            "version": "==1.26.24"
+        },
         "packaging": {
             "hashes": [
                 "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
@@ -121,7 +146,7 @@
                 "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
                 "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==2.8.2"
         },
         "s3transfer": {
@@ -137,9 +162,33 @@
                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==1.16.0"
         },
+        "types-awscrt": {
+            "hashes": [
+                "sha256:35e27f667e882ef7d02f1829499df30c0881d4c3ba12ff698c796b79e87ce09e",
+                "sha256:8df494aff24bdce188381304d6e762b797e2e3e67f610dcb7c2a3725f08cbf67"
+            ],
+            "markers": "python_version >= '3.7' and python_version < '4.0'",
+            "version": "==0.16.10"
+        },
+        "types-s3transfer": {
+            "hashes": [
+                "sha256:2cf1e07cf4d1a5a2a68d89c654f45d9c3b678d39f7fe03a6f36903b6dbd3bcbc",
+                "sha256:cfcbee11c16d60af3feb3dbffea3a85b32129235b562912dece310a45ae83a2c"
+            ],
+            "markers": "python_version >= '3.7' and python_version < '4.0'",
+            "version": "==0.6.0.post5"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+                "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==4.5.0"
+        },
         "urllib3": {
             "hashes": [
                 "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
@@ -350,6 +399,38 @@
             "markers": "python_version >= '3.7'",
             "version": "==2.1.2"
         },
+        "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",
@@ -407,6 +488,7 @@
                 "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"
             ],
             "index": "pypi",
+            "markers": null,
             "version": "==6.3.0"
         },
         "pytest": {
@@ -455,6 +537,14 @@
             ],
             "index": "pypi",
             "version": "==22.12"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+                "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==4.5.0"
         }
     }
 }
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/1127.break.rst b/newsfragments/1127.break.rst
new file mode 100644
index 00000000..0ec03b49
--- /dev/null
+++ b/newsfragments/1127.break.rst
@@ -0,0 +1,1 @@
+Dropped support for Python 3.7 due to using TypeDict for configuration.
\ No newline at end of file
diff --git a/newsfragments/1127.feature.rst b/newsfragments/1127.feature.rst
new file mode 100644
index 00000000..9c802951
--- /dev/null
+++ b/newsfragments/1127.feature.rst
@@ -0,0 +1,2 @@
+Added typing and check code with mypy.
+Also configuration is being TypeChecked with TypeDict.
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 4480ecaf..5e6302f6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,7 +17,6 @@ classifiers = [
     "Operating System :: OS Independent",
     "Programming Language :: Python",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
@@ -32,8 +31,9 @@ dependencies = [
     "port-for>=0.6.0",
     "mirakuru",
     "boto3",
+    "boto3-stubs[dynamodb]",
 ]
-requires-python = ">= 3.7"
+requires-python = ">= 3.8"
 
 [project.urls]
 "Source" = "https://github.com/ClearcodeHQ/pytest-dynamodb"
@@ -51,6 +51,9 @@ build-backend = "setuptools.build_meta"
 zip-safe = true
 packages = ["pytest_dynamodb"]
 
+[tool.setuptools.package-data]
+pytest_dynamodb = ["py.typed"]
+
 [tool.pytest.ini_options]
 xfail_strict=true
 addopts = "--max-worker-restart=0 --cov"


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3e60936a..0e0a5430 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_dynamodb/factories.py b/pytest_dynamodb/factories.py
index c703601d..e64c6f5b 100644
--- a/pytest_dynamodb/factories.py
+++ b/pytest_dynamodb/factories.py
@@ -17,13 +17,29 @@
 # along with pytest-dynamodb. If not, see <http://www.gnu.org/licenses/>.
 """Module containing factories for pytest-dynamodb."""
 import os
+from pathlib import Path
+from typing import TypedDict, Optional, Any, Callable, Generator
 
 import pytest
 import boto3
+from mypy_boto3_dynamodb import DynamoDBServiceResource
+from pytest import FixtureRequest
 from mirakuru import TCPExecutor, ProcessExitedWithError
 from port_for import get_port
 
 
+class PytestDynamoDBConfigType(TypedDict):
+    """Configuration type dict."""
+
+    dir: Path
+    host: str
+    port: str
+    delay: str
+    aws_access_key: str
+    aws_secret_key: str
+    aws_region: str
+
+
 class JarPathException(Exception):
     """
     Exception thrown, i ncase we can't locate dynamodb's dir to run dynamodb.
@@ -33,28 +49,33 @@ class JarPathException(Exception):
     """
 
 
-def get_config(request):
+def get_config(request: FixtureRequest) -> PytestDynamoDBConfigType:
     """Return a dictionary with config options."""
-    config = {}
-    options = [
-        "dir",
-        "host",
-        "port",
-        "delay",
-        "aws_access_key",
-        "aws_secret_key",
-        "aws_region",
-    ]
-    for option in options:
+
+    def get_conf_option(option: str) -> Any:
         option_name = "dynamodb_" + 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: PytestDynamoDBConfigType = {
+        "dir": get_conf_option("dir"),
+        "host": get_conf_option("host"),
+        "port": get_conf_option("port"),
+        "delay": get_conf_option("delay"),
+        "aws_access_key": get_conf_option("aws_access_key"),
+        "aws_secret_key": get_conf_option("aws_secret_key"),
+        "aws_region": get_conf_option("aws_region"),
+    }
     return config
 
 
-def dynamodb_proc(dynamodb_dir=None, host="localhost", port=None, delay=False):
+def dynamodb_proc(
+    dynamodb_dir: Optional[str] = None,
+    host: str = "localhost",
+    port: Optional[int] = None,
+    delay: bool = False,
+) -> Callable[[FixtureRequest], Any]:
     """
     Process fixture factory for DynamoDB.
 
@@ -72,7 +93,9 @@ def dynamodb_proc(dynamodb_dir=None, host="localhost", port=None, delay=False):
     """
 
     @pytest.fixture(scope="session")
-    def dynamodb_proc_fixture(request):
+    def dynamodb_proc_fixture(
+        request: FixtureRequest,
+    ) -> Generator[TCPExecutor, None, None]:
         """
         Process fixture for DynamoDB.
 
@@ -94,6 +117,7 @@ def dynamodb_proc_fixture(request):
             )
 
         dynamodb_port = get_port(port or config["port"])
+        assert dynamodb_port
         dynamodb_delay = (
             "-delayTransientStatuses" if delay or config["delay"] else ""
         )
@@ -118,8 +142,11 @@ def dynamodb_proc_fixture(request):
 
 
 def dynamodb(
-    process_fixture_name, access_key=None, secret_key=None, region=None
-):
+    process_fixture_name: str,
+    access_key: Optional[str] = None,
+    secret_key: Optional[str] = None,
+    region: Optional[str] = None,
+) -> Callable[[FixtureRequest], Any]:
     """
     Fixture factory for DynamoDB resource.
 
@@ -132,7 +159,9 @@ def dynamodb(
     """
 
     @pytest.fixture
-    def dynamodb_factory(request):
+    def dynamodb_factory(
+        request: FixtureRequest,
+    ) -> Generator[DynamoDBServiceResource, None, None]:
         """
         Fixture for DynamoDB resource.
 
diff --git a/pytest_dynamodb/plugin.py b/pytest_dynamodb/plugin.py
index 3bf39bea..d0bb0ac6 100644
--- a/pytest_dynamodb/plugin.py
+++ b/pytest_dynamodb/plugin.py
@@ -16,6 +16,8 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with pytest-dynamodb.  If not, see <http://www.gnu.org/licenses/>.
 """Plugin module of pytest-dynamodb."""
+from pytest import Parser
+
 from pytest_dynamodb import factories
 
 
@@ -29,7 +31,7 @@
 _help_aws_region = "AWS region name."
 
 
-def pytest_addoption(parser):
+def pytest_addoption(parser: Parser) -> None:
     """Configure options for pytest-dynamodb."""
     parser.addini(name="dynamodb_dir", help=_help_dir, default="/tmp/dynamodb")
 
diff --git a/pytest_dynamodb/py.typed b/pytest_dynamodb/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/test_dynamodb.py b/tests/test_dynamodb.py
index 17be56e1..b4403eb3 100644
--- a/tests/test_dynamodb.py
+++ b/tests/test_dynamodb.py
@@ -3,9 +3,10 @@
 
 import pytest
 from botocore.exceptions import ClientError
+from mypy_boto3_dynamodb import DynamoDBServiceResource
 
 
-def test_dynamodb(dynamodb):
+def test_dynamodb(dynamodb: DynamoDBServiceResource) -> None:
     """
     Simple test for DynamoDB.
 
@@ -42,7 +43,7 @@ def test_dynamodb(dynamodb):
     assert item["Item"]["test_key"] == "test_value"
 
 
-def test_if_tables_does_not_exist(dynamodb):
+def test_if_tables_does_not_exist(dynamodb: DynamoDBServiceResource) -> None:
     """
     We should clear this fixture (remove all tables).
 
@@ -53,7 +54,11 @@ def test_if_tables_does_not_exist(dynamodb):
     assert not list(dynamodb.tables.all())
 
 
-def test_different_credentials(dynamodb_diff, dynamodb_same, dynamodb):
+def test_different_credentials(
+    dynamodb_diff: DynamoDBServiceResource,
+    dynamodb_same: DynamoDBServiceResource,
+    dynamodb: DynamoDBServiceResource,
+) -> None:
     """
     Check error when accessing table with different credentials.