Skip to content

Commit 3666c0c

Browse files
typing improvements (#6)
* WIP * Adding more typing * pin pyflakes * typing
1 parent 3928c05 commit 3666c0c

23 files changed

Lines changed: 223 additions & 132 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ test = [
5353
"freezegun",
5454
"mypy",
5555
"nox",
56-
"pyflakes",
56+
"pyflakes == 3.2.0", # 3.3.0 causes some grief with a new warning.
5757
"pylint",
5858
# "pylint[spelling]", ## TODO
5959
"pytest",

src/planet_auth/auth_client.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def _get_typename_map(cls):
122122
return cls._typename_map
123123

124124
@classmethod
125-
def from_dict(cls, config_data: dict) -> AuthClientConfig:
125+
def from_dict(cls, config_data: Dict) -> AuthClientConfig:
126126
"""
127127
Create a AuthClientConfig from a configuration dictionary.
128128
Returns:
@@ -158,7 +158,7 @@ def from_file(file_path, storage_provider: Optional[ObjectStorageProvider] = Non
158158

159159
@classmethod
160160
@abstractmethod
161-
def meta(cls) -> dict:
161+
def meta(cls) -> Dict:
162162
"""
163163
Return a dictionary of metadata.
164164
The meta dictionary provides a place to store information that is
@@ -269,7 +269,7 @@ def login(
269269
implementations should raise an exception for all login errors.
270270
"""
271271

272-
def device_login_initiate(self, **kwargs) -> dict:
272+
def device_login_initiate(self, **kwargs) -> Dict:
273273
"""
274274
Initiate the process to login a device with limited UI capabilities.
275275
The returned dictionary should contain information for the application
@@ -286,7 +286,7 @@ def device_login_initiate(self, **kwargs) -> dict:
286286
"""
287287
raise AuthClientException(message="Device login is not supported for the current authentication mechanism")
288288

289-
def device_login_complete(self, initiated_login_data: dict) -> Credential:
289+
def device_login_complete(self, initiated_login_data: Dict) -> Credential:
290290
"""
291291
Complete a login process that was initiated by a call to `device_login_initiate()`.
292292
@@ -319,7 +319,7 @@ def refresh(self, refresh_token: str, requested_scopes: List[str]) -> Credential
319319
"""
320320
raise AuthClientException(message="Refresh not implemented for the current authentication mechanism")
321321

322-
def validate_access_token_remote(self, access_token: str) -> dict:
322+
def validate_access_token_remote(self, access_token: str) -> Dict:
323323
"""
324324
Validate an access token with the authorization server.
325325
Parameters:
@@ -337,7 +337,7 @@ def validate_access_token_remote(self, access_token: str) -> dict:
337337

338338
def validate_access_token_local(
339339
self, access_token: str, required_audience: str = None, scopes_anyof: list = None
340-
) -> dict:
340+
) -> Dict:
341341
"""
342342
Validate an access token locally. While the validation is local,
343343
the authorization server may still may contacted to obtain signing
@@ -382,7 +382,7 @@ def validate_access_token_local(
382382
message="Access token validation is not implemented for the current authentication mechanism"
383383
)
384384

385-
def validate_id_token_remote(self, id_token: str) -> dict:
385+
def validate_id_token_remote(self, id_token: str) -> Dict:
386386
"""
387387
Validate an ID token with the authorization server.
388388
Parameters:
@@ -394,7 +394,7 @@ def validate_id_token_remote(self, id_token: str) -> dict:
394394
message="ID token validation is not implemented for the current authentication mechanism"
395395
)
396396

397-
def validate_id_token_local(self, id_token: str) -> dict:
397+
def validate_id_token_local(self, id_token: str) -> Dict:
398398
"""
399399
Validate an ID token locally. The authorization server may still be
400400
called to obtain signing keys for validation. Signing keys will be
@@ -408,7 +408,7 @@ def validate_id_token_local(self, id_token: str) -> dict:
408408
message="ID token validation is not implemented for the current authentication mechanism"
409409
)
410410

411-
def validate_refresh_token_remote(self, refresh_token: str) -> dict:
411+
def validate_refresh_token_remote(self, refresh_token: str) -> Dict:
412412
"""
413413
Validate a refresh token with the authorization server.
414414
Parameters:
@@ -440,7 +440,7 @@ def revoke_refresh_token(self, refresh_token: str):
440440
message="Refresh token revocation is not implemented for the current authentication mechanism"
441441
)
442442

443-
def userinfo_from_access_token(self, access_token: str) -> dict:
443+
def userinfo_from_access_token(self, access_token: str) -> Dict:
444444
"""
445445
Look up user information from the auth server using the access token.
446446
Parameters:
@@ -450,7 +450,7 @@ def userinfo_from_access_token(self, access_token: str) -> dict:
450450
message="User information lookup is not implemented for the current authentication mechanism"
451451
)
452452

453-
def oidc_discovery(self) -> dict:
453+
def oidc_discovery(self) -> Dict:
454454
"""
455455
Query the authorization server's OIDC discovery endpoint for server information.
456456
Returns:
@@ -460,7 +460,7 @@ def oidc_discovery(self) -> dict:
460460
message="OIDC discovery is not implemented for the current authentication mechanism."
461461
)
462462

463-
# def oauth_discovery(self) -> dict:
463+
# def oauth_discovery(self) -> Dict:
464464
# """
465465
# Query the authorization server's OAuth2 discovery endpoint for server information.
466466
# Returns:

src/planet_auth/oidc/api_clients/api_client.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@
1313
# limitations under the License.
1414

1515
from abc import ABC
16-
from requests import Session
16+
from requests import Session, Response
1717
from requests.adapters import HTTPAdapter
1818
from requests.auth import AuthBase
19+
from typing import Callable, Dict, Optional, Tuple
1920
from urllib3.util.retry import Retry
2021

2122
from planet_auth.auth_client import AuthClientException
2223
from planet_auth.constants import X_PLANET_APP_HEADER, X_PLANET_APP
2324
from planet_auth.util import parse_content_type
2425

26+
EnricherPayloadType = Dict
27+
# EnricherAudType = str
28+
EnricherReturnType = Tuple[Dict, Optional[AuthBase]]
29+
EnricherFuncType = Callable[[EnricherPayloadType, str], EnricherReturnType]
30+
31+
_RequestAuthType = AuthBase
32+
_RequestParamsType = Dict # Requests allows a lot more, but constrain our use.
33+
_RequestResponseType = Response
34+
2535

2636
class OidcApiClient(ABC):
2737
"""
@@ -34,6 +44,8 @@ class OidcApiClient(ABC):
3444
# Generally, this will be some combination of the client ID and secret
3545
# and may be a header or payload adjustment. But sometimes, we just
3646
# need to use an Authorization header.
47+
# TODO: dog-food - use our own RequestAuthenticator like we do for the
48+
# static API key auth client
3749
class TokenBearerAuth(AuthBase):
3850
def __init__(self, token):
3951
self._token = token
@@ -42,7 +54,7 @@ def __call__(self, r):
4254
r.headers["Authorization"] = "Bearer " + self._token
4355
return r
4456

45-
def __init__(self, endpoint_uri):
57+
def __init__(self, endpoint_uri: str):
4658
self._endpoint_uri = endpoint_uri
4759

4860
retry_strategy = Retry(total=3, backoff_factor=1, status_forcelist=[429], allowed_methods=["POST", "GET"])
@@ -51,7 +63,7 @@ def __init__(self, endpoint_uri):
5163
self._session.mount("https://", adapter)
5264
# self._session.mount("http://", adapter)
5365

54-
def __check_http_error(self, response):
66+
def __check_http_error(self, response: _RequestResponseType) -> None:
5567
if not response.ok:
5668
raise OidcApiClientException(
5769
message="HTTP error from OIDC endpoint at {}: {}: {}".format(
@@ -60,7 +72,7 @@ def __check_http_error(self, response):
6072
raw_response=response,
6173
)
6274

63-
def __check_oidc_payload_json_error(self, response):
75+
def __check_oidc_payload_json_error(self, response: _RequestResponseType) -> None:
6476
if response.content:
6577
ct = parse_content_type(response.headers.get("content-type"))
6678
if not ct["content-type"] == "application/json":
@@ -89,7 +101,7 @@ def __check_oidc_payload_json_error(self, response):
89101
)
90102

91103
@staticmethod
92-
def __checked_json_response(response):
104+
def __checked_json_response(response: _RequestResponseType) -> Dict:
93105
json_response = None
94106
if response.content:
95107
ct = parse_content_type(response.headers.get("content-type"))
@@ -106,13 +118,15 @@ def __checked_json_response(response):
106118
)
107119
return json_response
108120

109-
def __check_response(self, response):
121+
def __check_response(self, response: _RequestResponseType) -> None:
110122
# Check for the json error first so we throw a more specific parsed
111123
# error if we understand it, regardless of HTTP status code.
112124
self.__check_oidc_payload_json_error(response)
113125
self.__check_http_error(response)
114126

115-
def _checked_get(self, params, request_auth):
127+
def _checked_get(
128+
self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType]
129+
) -> _RequestResponseType:
116130
response = self._session.get(
117131
self._endpoint_uri,
118132
params=params,
@@ -122,7 +136,9 @@ def _checked_get(self, params, request_auth):
122136
self.__check_response(response)
123137
return response
124138

125-
def _checked_post(self, params, request_auth):
139+
def _checked_post(
140+
self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType]
141+
) -> _RequestResponseType:
126142
response = self._session.post(
127143
self._endpoint_uri,
128144
# Note: is the data/params crossing confusing? This was born out
@@ -139,10 +155,14 @@ def _checked_post(self, params, request_auth):
139155
self.__check_response(response)
140156
return response
141157

142-
def _checked_post_json_response(self, params, request_auth):
158+
def _checked_post_json_response(
159+
self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType]
160+
) -> Dict:
143161
return self.__checked_json_response(self._checked_post(params, request_auth))
144162

145-
def _checked_get_json_response(self, params, request_auth):
163+
def _checked_get_json_response(
164+
self, params: Optional[_RequestParamsType], request_auth: Optional[_RequestAuthType]
165+
) -> Dict:
146166
return self.__checked_json_response(self._checked_get(params, request_auth))
147167

148168

src/planet_auth/oidc/api_clients/authorization_api_client.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from http import HTTPStatus
2121
from urllib.parse import urlparse, parse_qs, urlencode
22-
from typing import List
22+
from typing import Dict, List, Optional
2323
from webbrowser import open_new
2424

2525
import planet_auth.logging.auth_logger
@@ -125,7 +125,9 @@ class AuthorizationApiClient:
125125
interactive authentication can be performed.
126126
"""
127127

128-
def __init__(self, authorization_uri=None, authorization_callback_acknowledgement_response_body=None):
128+
def __init__(
129+
self, authorization_uri: str, authorization_callback_acknowledgement_response_body: Optional[str] = None
130+
):
129131
"""
130132
Create a new Authorization API Client.
131133
"""
@@ -146,8 +148,8 @@ def prep_pkce_auth_payload(
146148
requested_scopes: List[str],
147149
requested_audiences: List[str],
148150
pkce_code_challenge: str,
149-
extra: dict,
150-
) -> dict:
151+
extra: Dict,
152+
) -> Dict:
151153
"""
152154
Prepare the payload needed to make an authorization request to an
153155
OAuth authorization endpoint. This will usually be used to construct
@@ -218,7 +220,7 @@ def authcode_from_pkce_auth_request_with_browser_and_callback_listener(
218220
requested_scopes: List[str],
219221
requested_audiences: List[str],
220222
pkce_code_challenge: str,
221-
extra: dict,
223+
extra: Dict,
222224
) -> str:
223225
"""
224226
Request an authorization code by launching a web browser directed to the
@@ -309,7 +311,7 @@ def authcode_from_pkce_auth_request_with_tty_input(
309311
requested_scopes: List[str],
310312
requested_audiences: List[str],
311313
pkce_code_challenge: str,
312-
extra: dict,
314+
extra: Dict,
313315
) -> str:
314316
"""
315317
Request an authorization code by prompting the user to visit a

src/planet_auth/oidc/api_clients/device_authorization_api_client.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from planet_auth.oidc.api_clients.api_client import OidcApiClient, OidcApiClientException
15+
from typing import Dict, List, Optional
16+
17+
from planet_auth.oidc.api_clients.api_client import (
18+
OidcApiClient,
19+
OidcApiClientException,
20+
EnricherFuncType,
21+
_RequestParamsType,
22+
_RequestAuthType,
23+
)
1624

1725

1826
class DeviceAuthorizationApiException(OidcApiClientException):
@@ -28,11 +36,16 @@ class DeviceAuthorizationApiClient(OidcApiClient):
2836
All invalid responses or error responses will result in an exception.
2937
"""
3038

31-
def __init__(self, device_authorization_uri=None):
39+
def __init__(self, device_authorization_uri: str):
3240
super().__init__(endpoint_uri=device_authorization_uri)
3341

3442
@staticmethod
35-
def _prep_device_code_request_payload(client_id, requested_scopes, requested_audiences, extra):
43+
def _prep_device_code_request_payload(
44+
client_id,
45+
requested_scopes: Optional[List[str]],
46+
requested_audiences: Optional[List[str]],
47+
extra: Optional[Dict],
48+
) -> Dict:
3649
if extra is None:
3750
extra = {}
3851
# "None" is pythonic, and does not mean anything to OAuth APIs.
@@ -50,7 +63,7 @@ def _prep_device_code_request_payload(client_id, requested_scopes, requested_aud
5063
return data
5164

5265
@staticmethod
53-
def _check_device_auth_response(json_response):
66+
def _check_device_auth_response(json_response: Dict) -> Dict:
5467
# Protocol endpoint specific response checks
5568
if not json_response.get("device_code"):
5669
raise DeviceAuthorizationApiException(
@@ -71,11 +84,20 @@ def _check_device_auth_response(json_response):
7184
# verification_uri_complete and interval are optional under the spec, so we don't force them to be present.
7285
return json_response
7386

74-
def _checked_request_device_code_call(self, request_params, request_auth):
87+
def _checked_request_device_code_call(
88+
self, request_params: _RequestParamsType, request_auth: Optional[_RequestAuthType]
89+
) -> Dict:
7590
json_response = self._checked_post_json_response(request_params, request_auth)
7691
return self._check_device_auth_response(json_response)
7792

78-
def request_device_code(self, client_id: str, requested_scopes, requested_audiences, auth_enricher, extra):
93+
def request_device_code(
94+
self,
95+
client_id: str,
96+
requested_scopes: Optional[List[str]],
97+
requested_audiences: Optional[List[str]],
98+
auth_enricher: Optional[EnricherFuncType],
99+
extra,
100+
) -> Dict:
79101
request_params = self._prep_device_code_request_payload(
80102
client_id=client_id,
81103
requested_scopes=requested_scopes,

0 commit comments

Comments
 (0)