22# Released subject to the New BSD License
33# Please see http://en.wikipedia.org/wiki/BSD_licenses
44
5+ import argparse
56import configparser
67import json
8+ import os
79import ssl
810import urllib .parse
911import urllib .request
10- from os import environ , path
12+ from typing import Any , Callable , Dict , Optional , Tuple , TYPE_CHECKING , TypeVar
1113
1214import imapclient
1315
1416
15- def getenv (name , default ) :
16- return environ .get ("imapclient_" + name , default )
17+ def getenv (name : str , default : Optional [ str ]) -> Optional [ str ] :
18+ return os . environ .get ("imapclient_" + name , default )
1719
1820
19- def get_config_defaults ():
21+ def get_config_defaults () -> Dict [ str , Any ] :
2022 return {
2123 "username" : getenv ("username" , None ),
2224 "password" : getenv ("password" , None ),
@@ -35,7 +37,7 @@ def get_config_defaults():
3537 }
3638
3739
38- def parse_config_file (filename ) :
40+ def parse_config_file (filename : str ) -> argparse . Namespace :
3941 """Parse INI files containing IMAP connection details.
4042
4143 Used by livetest.py and interact.py
@@ -50,12 +52,13 @@ def parse_config_file(filename):
5052
5153 conf .alternates = {}
5254 for section in parser .sections ():
55+ # pylint: disable=no-member
5356 conf .alternates [section ] = _read_config_section (parser , section )
5457
5558 return conf
5659
5760
58- def get_string_config_defaults ():
61+ def get_string_config_defaults () -> Dict [ str , str ] :
5962 out = {}
6063 for k , v in get_config_defaults ().items ():
6164 if v is True :
@@ -68,14 +71,19 @@ def get_string_config_defaults():
6871 return out
6972
7073
71- def _read_config_section (parser , section ):
72- def get (name ):
74+ T = TypeVar ("T" )
75+
76+
77+ def _read_config_section (
78+ parser : configparser .ConfigParser , section : str
79+ ) -> argparse .Namespace :
80+ def get (name : str ) -> str :
7381 return parser .get (section , name )
7482
75- def getboolean (name ) :
83+ def getboolean (name : str ) -> bool :
7684 return parser .getboolean (section , name )
7785
78- def get_allowing_none (name , typefunc ) :
86+ def get_allowing_none (name : str , typefunc : Callable [[ str ], T ]) -> Optional [ T ] :
7987 try :
8088 v = parser .get (section , name )
8189 except configparser .NoOptionError :
@@ -84,17 +92,17 @@ def get_allowing_none(name, typefunc):
8492 return None
8593 return typefunc (v )
8694
87- def getint (name ) :
95+ def getint (name : str ) -> Optional [ int ] :
8896 return get_allowing_none (name , int )
8997
90- def getfloat (name ) :
98+ def getfloat (name : str ) -> Optional [ float ] :
9199 return get_allowing_none (name , float )
92100
93101 ssl_ca_file = get ("ssl_ca_file" )
94102 if ssl_ca_file :
95- ssl_ca_file = path .expanduser (ssl_ca_file )
103+ ssl_ca_file = os . path .expanduser (ssl_ca_file )
96104
97- return Bunch (
105+ return argparse . Namespace (
98106 host = get ("host" ),
99107 port = getint ("port" ),
100108 ssl = getboolean ("ssl" ),
@@ -120,7 +128,9 @@ def getfloat(name):
120128}
121129
122130
123- def refresh_oauth2_token (hostname , client_id , client_secret , refresh_token ):
131+ def refresh_oauth2_token (
132+ hostname : str , client_id : str , client_secret : str , refresh_token : str
133+ ) -> str :
124134 url = OAUTH2_REFRESH_URLS .get (hostname )
125135 if not url :
126136 raise ValueError ("don't know where to refresh OAUTH2 token for %r" % hostname )
@@ -135,14 +145,19 @@ def refresh_oauth2_token(hostname, client_id, client_secret, refresh_token):
135145 url , urllib .parse .urlencode (post ).encode ("ascii" )
136146 ) as request :
137147 response = request .read ()
138- return json .loads (response .decode ("ascii" ))["access_token" ]
148+ result = json .loads (response .decode ("ascii" ))["access_token" ]
149+ if TYPE_CHECKING :
150+ assert isinstance (result , str )
151+ return result
139152
140153
141154# Tokens are expensive to refresh so use the same one for the duration of the process.
142- _oauth2_cache = {}
155+ _oauth2_cache : Dict [ Tuple [ str , str , str , str ], str ] = {}
143156
144157
145- def get_oauth2_token (hostname , client_id , client_secret , refresh_token ):
158+ def get_oauth2_token (
159+ hostname : str , client_id : str , client_secret : str , refresh_token : str
160+ ) -> str :
146161 cache_key = (hostname , client_id , client_secret , refresh_token )
147162 token = _oauth2_cache .get (cache_key )
148163 if token :
@@ -153,7 +168,9 @@ def get_oauth2_token(hostname, client_id, client_secret, refresh_token):
153168 return token
154169
155170
156- def create_client_from_config (conf , login = True ):
171+ def create_client_from_config (
172+ conf : argparse .Namespace , login : bool = True
173+ ) -> imapclient .IMAPClient :
157174 assert conf .host , "missing host"
158175
159176 ssl_context = None
@@ -200,14 +217,3 @@ def create_client_from_config(conf, login=True):
200217 except : # noqa: E722
201218 client .shutdown ()
202219 raise
203-
204-
205- class Bunch (dict ):
206- def __getattr__ (self , k ):
207- try :
208- return self [k ]
209- except KeyError :
210- raise AttributeError
211-
212- def __setattr__ (self , k , v ):
213- self [k ] = v
0 commit comments