Repository: burnash/gspread (language: python)

Issue:
Add missing types
## Overview

Add missing typing in code, all function arguments + return values should be typed.

## Details

We found some parts of the code that is not typed, all arguments and all returned values should be typed.

If possible we can try to type the tests too ! it would help while developing tests to have some auto-completion too.


Discussion / hints:
add JSON typing using: https://github.com/kevinheavey/jsonalias
I tried to type the low level HTTP methods using the JSON type provided and it does not work. 
Any time we return some JSON type, we try to access the data as a dict or a list etc and mypy complains and says:
the return value could be a str, or a dict or a list and str object have no method `__getitem__` (which is true).
So it can't make the link: we return some value with multiple types possible we use as one of the possible type. 

let's keep the Any for now. we don't have a better solution.

I'll type the test so it helps any rich test editor to auto-complete and that would do for now I suppose. 

Fix patch:
diff --git a/tox.ini b/tox.ini
index c1a5f6e5..8e235edc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -32,7 +32,7 @@ commands = black --check --diff --extend-exclude "./env" .
         codespell --skip=".tox,.git,./docs/build,.mypy_cache,./env" .
         flake8 .
         isort --check-only .
-        mypy --install-types --non-interactive --ignore-missing-imports ./gspread
+        mypy --install-types --non-interactive --ignore-missing-imports ./gspread ./tests
 
 # Used by developers to format code, best advised to be run before commit
 [testenv:format]


Test patch:
diff --git a/tests/cell_test.py b/tests/cell_test.py
index 77242b1e..41519baa 100644
--- a/tests/cell_test.py
+++ b/tests/cell_test.py
@@ -1,6 +1,12 @@
+from typing import Generator
+
 import pytest
+from pytest import FixtureRequest
 
 import gspread
+from gspread.client import Client
+from gspread.spreadsheet import Spreadsheet
+from gspread.worksheet import Worksheet
 
 from .conftest import GspreadTest
 
@@ -8,8 +14,13 @@
 class CellTest(GspreadTest):
     """Test for gspread.Cell."""
 
+    spreadsheet: Spreadsheet
+    sheet: Worksheet
+
     @pytest.fixture(scope="function", autouse=True)
-    def init(self, client, request):
+    def init(
+        self: "CellTest", client: Client, request: FixtureRequest
+    ) -> Generator[None, None, None]:
         # User current test name in spreadsheet name
         name = self.get_temporary_spreadsheet_title(request.node.name)
         CellTest.spreadsheet = client.create(name)
@@ -70,7 +81,12 @@ def test_a1_value(self):
         cell = self.sheet.cell(4, 4)
         self.assertEqual(cell.address, "D4")
         self.sheet.update_acell("B1", "Dummy")
-        cell = self.sheet.find("Dummy")
+        result = self.sheet.find("Dummy")
+        if result is None:
+            self.fail("did not find cell with matching text 'Dummy'")
+        else:
+            cell = result
+
         self.assertEqual(cell.address, "B1")
         self.assertEqual(cell.value, "Dummy")
         cell = gspread.cell.Cell(1, 2, "Foo Bar")
diff --git a/tests/client_test.py b/tests/client_test.py
index bf909266..b27cd49a 100644
--- a/tests/client_test.py
+++ b/tests/client_test.py
@@ -1,8 +1,12 @@
 import time
+from typing import Generator
 
 import pytest
+from pytest import FixtureRequest
 
 import gspread
+from gspread.client import Client
+from gspread.spreadsheet import Spreadsheet
 
 from .conftest import GspreadTest
 
@@ -10,8 +14,13 @@
 class ClientTest(GspreadTest):
     """Test for gspread.client."""
 
+    gc: Client
+    spreadsheet: Spreadsheet
+
     @pytest.fixture(scope="function", autouse=True)
-    def init(self, client, request):
+    def init(
+        self: "ClientTest", client: Client, request: FixtureRequest
+    ) -> Generator[None, None, None]:
         ClientTest.gc = client
         name = self.get_temporary_spreadsheet_title(request.node.name)
         ClientTest.spreadsheet = client.create(name)
diff --git a/tests/conftest.py b/tests/conftest.py
index 2e806841..676f5482 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,9 +1,10 @@
 import itertools
 import os
 import unittest
-from typing import Any
+from typing import Any, Dict, Generator, Optional
 
 import pytest
+from google.auth.credentials import Credentials
 from google.oauth2.credentials import Credentials as UserCredentials
 from google.oauth2.service_account import Credentials as ServiceAccountCredentials
 from requests import Response
@@ -26,21 +27,23 @@
 I18N_STR = "Iñtërnâtiônàlizætiøn"  # .encode('utf8')
 
 
-def read_credentials(filename):
+def read_credentials(filename: str) -> Credentials:
     return ServiceAccountCredentials.from_service_account_file(filename, scopes=SCOPE)
 
 
-def prefixed_counter(prefix, start=1):
+def prefixed_counter(prefix: str, start: int = 1) -> Generator[str, None, None]:
     c = itertools.count(start)
     for value in c:
         yield "{} {}".format(prefix, value)
 
 
-def get_method_name(self_id):
+def get_method_name(self_id: str) -> str:
     return self_id.split(".")[-1]
 
 
-def ignore_retry_requests(response):
+def ignore_retry_requests(
+    response: Dict[str, Dict[str, int]]
+) -> Optional[Dict[str, Dict[str, int]]]:
     SKIP_RECORD = [408, 429]
     if response["status"]["code"] in SKIP_RECORD:
         return None  # do not record
@@ -72,14 +75,14 @@ class DummyCredentials(UserCredentials):
 
 class GspreadTest(unittest.TestCase):
     @classmethod
-    def get_temporary_spreadsheet_title(cls, suffix=""):
+    def get_temporary_spreadsheet_title(cls, suffix: str = "") -> str:
         return "Test {} {}".format(cls.__name__, suffix)
 
     @classmethod
-    def get_cassette_name(cls):
+    def get_cassette_name(cls) -> str:
         return cls.__name__
 
-    def _sequence_generator(self):
+    def _sequence_generator(self) -> Generator[str, None, None]:
         return prefixed_counter(get_method_name(self.id()))
 
 
@@ -105,8 +108,8 @@ def request(self, *args: Any, **kwargs: Any) -> Response:
 
 
 @pytest.fixture(scope="module")
-def client():
-    if CREDS_FILENAME:
+def client() -> Client:
+    if CREDS_FILENAME is not None:
         auth_credentials = read_credentials(CREDS_FILENAME)
     else:
         auth_credentials = DummyCredentials(DUMMY_ACCESS_TOKEN)
diff --git a/tests/worksheet_test.py b/tests/worksheet_test.py
index 51568ad3..ba430aa1 100644
--- a/tests/worksheet_test.py
+++ b/tests/worksheet_test.py
@@ -2,12 +2,17 @@
 import random
 import re
 from inspect import signature
+from typing import Generator
 
 import pytest
+from pytest import FixtureRequest
 
 import gspread
 from gspread import utils
+from gspread.client import Client
 from gspread.exceptions import APIError, GSpreadException
+from gspread.spreadsheet import Spreadsheet
+from gspread.worksheet import Worksheet
 
 from .conftest import I18N_STR, GspreadTest
 
@@ -15,13 +20,16 @@
 class WorksheetTest(GspreadTest):
     """Test for gspread.Worksheet."""
 
+    spreadsheet: Spreadsheet
+    sheet: Worksheet
+
     @pytest.fixture(scope="function", autouse=True)
-    def init(self, client, request):
+    def init(
+        self: "WorksheetTest", client: Client, request: FixtureRequest
+    ) -> Generator[None, None, None]:
         name = self.get_temporary_spreadsheet_title(request.node.name)
         WorksheetTest.spreadsheet = client.create(name)
-        WorksheetTest.sheet: gspread.worksheet.Worksheet = (
-            WorksheetTest.spreadsheet.sheet1
-        )
+        WorksheetTest.sheet = WorksheetTest.spreadsheet.sheet1
 
         yield