GeezerVison is showing github.com — gh-94172: Remove keyfile, certfile and check_hostname parameters (#94… · python/cpython@ef0e72b
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Doc/whatsnew/3.12.rst +10Lines changed: 10 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -540,6 +540,16 @@ Removed | 540 | 540 | <https://github.com/sphinx-contrib/sphinx-lint>`_. | 541 | 541 | (Contributed by Julien Palard in :gh:`98179`.) | 542 | 542 |
| | 543 | +* Remove the *keyfile*, *certfile* and *check_hostname* parameters, deprecated | | 544 | + since Python 3.6, in modules: :mod:`ftplib`, :mod:`http.client`, | | 545 | + :mod:`imaplib`, :mod:`poplib` and :mod:`smtplib`. Use the *context* parameter | | 546 | + (*ssl_context* in :mod:`imaplib`) instead. | | 547 | + (Contributed by Victor Stinner in :gh:`94172`.) | | 548 | + | | 549 | +* :mod:`ftplib`: Remove the ``FTP_TLS.ssl_version`` class attribute: use the | | 550 | + *context* parameter instead. | | 551 | + (Contributed by Victor Stinner in :gh:`94172`.) | | 552 | + | 543 | 553 |
| 544 | 554 | Porting to Python 3.12 | 545 | 555 | ====================== | |
Collapse file Copy file name to clipboardExpand all lines: Lib/ftplib.py +4-20Lines changed: 4 additions & 20 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -713,28 +713,12 @@ class FTP_TLS(FTP): | 713 | 713 | '221 Goodbye.' | 714 | 714 | >>> | 715 | 715 | ''' | 716 | | - ssl_version = ssl.PROTOCOL_TLS_CLIENT | 717 | 716 |
| 718 | 717 | def __init__(self, host='', user='', passwd='', acct='', | 719 | | - keyfile=None, certfile=None, context=None, | 720 | | - timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *, | 721 | | - encoding='utf-8'): | 722 | | - if context is not None and keyfile is not None: | 723 | | - raise ValueError("context and keyfile arguments are mutually " | 724 | | - "exclusive") | 725 | | - if context is not None and certfile is not None: | 726 | | - raise ValueError("context and certfile arguments are mutually " | 727 | | - "exclusive") | 728 | | - if keyfile is not None or certfile is not None: | 729 | | - import warnings | 730 | | - warnings.warn("keyfile and certfile are deprecated, use a " | 731 | | - "custom context instead", DeprecationWarning, 2) | 732 | | - self.keyfile = keyfile | 733 | | - self.certfile = certfile | | 718 | + *, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT, | | 719 | + source_address=None, encoding='utf-8'): | 734 | 720 | if context is None: | 735 | | - context = ssl._create_stdlib_context(self.ssl_version, | 736 | | - certfile=certfile, | 737 | | - keyfile=keyfile) | | 721 | + context = ssl._create_stdlib_context() | 738 | 722 | self.context = context | 739 | 723 | self._prot_p = False | 740 | 724 | super().__init__(host, user, passwd, acct, | @@ -749,7 +733,7 @@ def auth(self): | 749 | 733 | '''Set up secure control connection by using TLS/SSL.''' | 750 | 734 | if isinstance(self.sock, ssl.SSLSocket): | 751 | 735 | raise ValueError("Already using TLS") | 752 | | - if self.ssl_version >= ssl.PROTOCOL_TLS: | | 736 | + if self.context.protocol >= ssl.PROTOCOL_TLS: | 753 | 737 | resp = self.voidcmd('AUTH TLS') | 754 | 738 | else: | 755 | 739 | resp = self.voidcmd('AUTH SSL') | |
Collapse file Copy file name to clipboardExpand all lines: Lib/http/client.py +3-22Lines changed: 3 additions & 22 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1414,33 +1414,14 @@ class HTTPSConnection(HTTPConnection): | 1414 | 1414 |
| 1415 | 1415 | default_port = HTTPS_PORT | 1416 | 1416 |
| 1417 | | - # XXX Should key_file and cert_file be deprecated in favour of context? | 1418 | | - | 1419 | | - def __init__(self, host, port=None, key_file=None, cert_file=None, | 1420 | | - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, | 1421 | | - source_address=None, *, context=None, | 1422 | | - check_hostname=None, blocksize=8192): | | 1417 | + def __init__(self, host, port=None, | | 1418 | + *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, | | 1419 | + source_address=None, context=None, blocksize=8192): | 1423 | 1420 | super(HTTPSConnection, self).__init__(host, port, timeout, | 1424 | 1421 | source_address, | 1425 | 1422 | blocksize=blocksize) | 1426 | | - if (key_file is not None or cert_file is not None or | 1427 | | - check_hostname is not None): | 1428 | | - import warnings | 1429 | | - warnings.warn("key_file, cert_file and check_hostname are " | 1430 | | - "deprecated, use a custom context instead.", | 1431 | | - DeprecationWarning, 2) | 1432 | | - self.key_file = key_file | 1433 | | - self.cert_file = cert_file | 1434 | 1423 | if context is None: | 1435 | 1424 | context = _create_https_context(self._http_vsn) | 1436 | | - if check_hostname is not None: | 1437 | | - context.check_hostname = check_hostname | 1438 | | - if key_file or cert_file: | 1439 | | - context.load_cert_chain(cert_file, key_file) | 1440 | | - # cert and key file means the user wants to authenticate. | 1441 | | - # enable TLS 1.3 PHA implicitly even for custom contexts. | 1442 | | - if context.post_handshake_auth is not None: | 1443 | | - context.post_handshake_auth = True | 1444 | 1425 | self._context = context | 1445 | 1426 |
| 1446 | 1427 | def connect(self): | |
Collapse file Copy file name to clipboardExpand all lines: Lib/imaplib.py +4-21Lines changed: 4 additions & 21 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1285,40 +1285,23 @@ class IMAP4_SSL(IMAP4): | 1285 | 1285 |
| 1286 | 1286 | """IMAP4 client class over SSL connection | 1287 | 1287 |
| 1288 | | - Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context[, timeout=None]]]]]]) | | 1288 | + Instantiate with: IMAP4_SSL([host[, port[, ssl_context[, timeout=None]]]]) | 1289 | 1289 |
| 1290 | 1290 | host - host's name (default: localhost); | 1291 | 1291 | port - port number (default: standard IMAP4 SSL port); | 1292 | | - keyfile - PEM formatted file that contains your private key (default: None); | 1293 | | - certfile - PEM formatted certificate chain file (default: None); | 1294 | 1292 | ssl_context - a SSLContext object that contains your certificate chain | 1295 | 1293 | and private key (default: None) | 1296 | | - Note: if ssl_context is provided, then parameters keyfile or | 1297 | | - certfile should not be set otherwise ValueError is raised. | 1298 | 1294 | timeout - socket timeout (default: None) If timeout is not given or is None, | 1299 | 1295 | the global default socket timeout is used | 1300 | 1296 |
| 1301 | 1297 | for more documentation see the docstring of the parent class IMAP4. | 1302 | 1298 | """ | 1303 | 1299 |
| 1304 | 1300 |
| 1305 | | - def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, | 1306 | | - certfile=None, ssl_context=None, timeout=None): | 1307 | | - if ssl_context is not None and keyfile is not None: | 1308 | | - raise ValueError("ssl_context and keyfile arguments are mutually " | 1309 | | - "exclusive") | 1310 | | - if ssl_context is not None and certfile is not None: | 1311 | | - raise ValueError("ssl_context and certfile arguments are mutually " | 1312 | | - "exclusive") | 1313 | | - if keyfile is not None or certfile is not None: | 1314 | | - import warnings | 1315 | | - warnings.warn("keyfile and certfile are deprecated, use a " | 1316 | | - "custom ssl_context instead", DeprecationWarning, 2) | 1317 | | - self.keyfile = keyfile | 1318 | | - self.certfile = certfile | | 1301 | + def __init__(self, host='', port=IMAP4_SSL_PORT, | | 1302 | + *, ssl_context=None, timeout=None): | 1319 | 1303 | if ssl_context is None: | 1320 | | - ssl_context = ssl._create_stdlib_context(certfile=certfile, | 1321 | | - keyfile=keyfile) | | 1304 | + ssl_context = ssl._create_stdlib_context() | 1322 | 1305 | self.ssl_context = ssl_context | 1323 | 1306 | IMAP4.__init__(self, host, port, timeout) | 1324 | 1307 |
| |
Collapse file Copy file name to clipboardExpand all lines: Lib/poplib.py +5-21Lines changed: 5 additions & 21 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -419,35 +419,19 @@ def stls(self, context=None): | 419 | 419 | class POP3_SSL(POP3): | 420 | 420 | """POP3 client class over SSL connection | 421 | 421 |
| 422 | | - Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None, | 423 | | - context=None) | | 422 | + Instantiate with: POP3_SSL(hostname, port=995, context=None) | 424 | 423 |
| 425 | 424 | hostname - the hostname of the pop3 over ssl server | 426 | 425 | port - port number | 427 | | - keyfile - PEM formatted file that contains your private key | 428 | | - certfile - PEM formatted certificate chain file | 429 | 426 | context - a ssl.SSLContext | 430 | 427 |
| 431 | 428 | See the methods of the parent class POP3 for more documentation. | 432 | 429 | """ | 433 | 430 |
| 434 | | - def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None, | 435 | | - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None): | 436 | | - if context is not None and keyfile is not None: | 437 | | - raise ValueError("context and keyfile arguments are mutually " | 438 | | - "exclusive") | 439 | | - if context is not None and certfile is not None: | 440 | | - raise ValueError("context and certfile arguments are mutually " | 441 | | - "exclusive") | 442 | | - if keyfile is not None or certfile is not None: | 443 | | - import warnings | 444 | | - warnings.warn("keyfile and certfile are deprecated, use a " | 445 | | - "custom context instead", DeprecationWarning, 2) | 446 | | - self.keyfile = keyfile | 447 | | - self.certfile = certfile | | 431 | + def __init__(self, host, port=POP3_SSL_PORT, | | 432 | + *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None): | 448 | 433 | if context is None: | 449 | | - context = ssl._create_stdlib_context(certfile=certfile, | 450 | | - keyfile=keyfile) | | 434 | + context = ssl._create_stdlib_context() | 451 | 435 | self.context = context | 452 | 436 | POP3.__init__(self, host, port, timeout) | 453 | 437 |
| @@ -457,7 +441,7 @@ def _create_socket(self, timeout): | 457 | 441 | server_hostname=self.host) | 458 | 442 | return sock | 459 | 443 |
| 460 | | - def stls(self, keyfile=None, certfile=None, context=None): | | 444 | + def stls(self, context=None): | 461 | 445 | """The method unconditionally raises an exception since the | 462 | 446 | STLS command doesn't make any sense on an already established | 463 | 447 | SSL/TLS session. | |
Collapse file Copy file name to clipboardExpand all lines: Lib/smtplib.py +7-35Lines changed: 7 additions & 35 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -749,14 +749,14 @@ def login(self, user, password, *, initial_response_ok=True): | 749 | 749 | # We could not login successfully. Return result of last attempt. | 750 | 750 | raise last_exception | 751 | 751 |
| 752 | | - def starttls(self, keyfile=None, certfile=None, context=None): | | 752 | + def starttls(self, *, context=None): | 753 | 753 | """Puts the connection to the SMTP server into TLS mode. | 754 | 754 |
| 755 | 755 | If there has been no previous EHLO or HELO command this session, this | 756 | 756 | method tries ESMTP EHLO first. | 757 | 757 |
| 758 | 758 | If the server supports TLS, this will encrypt the rest of the SMTP | 759 | | - session. If you provide the keyfile and certfile parameters, | | 759 | + session. If you provide the context parameter, | 760 | 760 | the identity of the SMTP server and client can be checked. This, | 761 | 761 | however, depends on whether the socket module really checks the | 762 | 762 | certificates. | @@ -774,19 +774,8 @@ def starttls(self, keyfile=None, certfile=None, context=None): | 774 | 774 | if resp == 220: | 775 | 775 | if not _have_ssl: | 776 | 776 | raise RuntimeError("No SSL support included in this Python") | 777 | | - if context is not None and keyfile is not None: | 778 | | - raise ValueError("context and keyfile arguments are mutually " | 779 | | - "exclusive") | 780 | | - if context is not None and certfile is not None: | 781 | | - raise ValueError("context and certfile arguments are mutually " | 782 | | - "exclusive") | 783 | | - if keyfile is not None or certfile is not None: | 784 | | - import warnings | 785 | | - warnings.warn("keyfile and certfile are deprecated, use a " | 786 | | - "custom context instead", DeprecationWarning, 2) | 787 | 777 | if context is None: | 788 | | - context = ssl._create_stdlib_context(certfile=certfile, | 789 | | - keyfile=keyfile) | | 778 | + context = ssl._create_stdlib_context() | 790 | 779 | self.sock = context.wrap_socket(self.sock, | 791 | 780 | server_hostname=self._host) | 792 | 781 | self.file = None | @@ -1017,35 +1006,18 @@ class SMTP_SSL(SMTP): | 1017 | 1006 | compiled with SSL support). If host is not specified, '' (the local | 1018 | 1007 | host) is used. If port is omitted, the standard SMTP-over-SSL port | 1019 | 1008 | (465) is used. local_hostname and source_address have the same meaning | 1020 | | - as they do in the SMTP class. keyfile and certfile are also optional - | 1021 | | - they can contain a PEM formatted private key and certificate chain file | 1022 | | - for the SSL connection. context also optional, can contain a | 1023 | | - SSLContext, and is an alternative to keyfile and certfile; If it is | 1024 | | - specified both keyfile and certfile must be None. | | 1009 | + as they do in the SMTP class. context also optional, can contain a | | 1010 | + SSLContext. | 1025 | 1011 |
| 1026 | 1012 | """ | 1027 | 1013 |
| 1028 | 1014 | default_port = SMTP_SSL_PORT | 1029 | 1015 |
| 1030 | 1016 | def __init__(self, host='', port=0, local_hostname=None, | 1031 | | - keyfile=None, certfile=None, | 1032 | | - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, | | 1017 | + *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, | 1033 | 1018 | source_address=None, context=None): | 1034 | | - if context is not None and keyfile is not None: | 1035 | | - raise ValueError("context and keyfile arguments are mutually " | 1036 | | - "exclusive") | 1037 | | - if context is not None and certfile is not None: | 1038 | | - raise ValueError("context and certfile arguments are mutually " | 1039 | | - "exclusive") | 1040 | | - if keyfile is not None or certfile is not None: | 1041 | | - import warnings | 1042 | | - warnings.warn("keyfile and certfile are deprecated, use a " | 1043 | | - "custom context instead", DeprecationWarning, 2) | 1044 | | - self.keyfile = keyfile | 1045 | | - self.certfile = certfile | 1046 | 1019 | if context is None: | 1047 | | - context = ssl._create_stdlib_context(certfile=certfile, | 1048 | | - keyfile=keyfile) | | 1020 | + context = ssl._create_stdlib_context() | 1049 | 1021 | self.context = context | 1050 | 1022 | SMTP.__init__(self, host, port, local_hostname, timeout, | 1051 | 1023 | source_address) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_ftplib.py +3-3Lines changed: 3 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -984,11 +984,11 @@ def test_context(self): | 984 | 984 | ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | 985 | 985 | ctx.check_hostname = False | 986 | 986 | ctx.verify_mode = ssl.CERT_NONE | 987 | | - self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE, | | 987 | + self.assertRaises(TypeError, ftplib.FTP_TLS, keyfile=CERTFILE, | 988 | 988 | context=ctx) | 989 | | - self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, | | 989 | + self.assertRaises(TypeError, ftplib.FTP_TLS, certfile=CERTFILE, | 990 | 990 | context=ctx) | 991 | | - self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, | | 991 | + self.assertRaises(TypeError, ftplib.FTP_TLS, certfile=CERTFILE, | 992 | 992 | keyfile=CERTFILE, context=ctx) | 993 | 993 |
| 994 | 994 | self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_httplib.py +12-31Lines changed: 12 additions & 31 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1978,7 +1978,7 @@ def test_local_unknown_cert(self): | 1978 | 1978 | self.assertEqual(exc_info.exception.reason, 'CERTIFICATE_VERIFY_FAILED') | 1979 | 1979 |
| 1980 | 1980 | def test_local_good_hostname(self): | 1981 | | - # The (valid) cert validates the HTTP hostname | | 1981 | + # The (valid) cert validates the HTTPS hostname | 1982 | 1982 | import ssl | 1983 | 1983 | server = self.make_server(CERT_localhost) | 1984 | 1984 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | @@ -1991,46 +1991,29 @@ def test_local_good_hostname(self): | 1991 | 1991 | self.assertEqual(resp.status, 404) | 1992 | 1992 |
| 1993 | 1993 | def test_local_bad_hostname(self): | 1994 | | - # The (valid) cert doesn't validate the HTTP hostname | | 1994 | + # The (valid) cert doesn't validate the HTTPS hostname | 1995 | 1995 | import ssl | 1996 | 1996 | server = self.make_server(CERT_fakehostname) | 1997 | 1997 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | 1998 | 1998 | context.load_verify_locations(CERT_fakehostname) | 1999 | 1999 | h = client.HTTPSConnection('localhost', server.port, context=context) | 2000 | 2000 | with self.assertRaises(ssl.CertificateError): | 2001 | 2001 | h.request('GET', '/') | 2002 | | - # Same with explicit check_hostname=True | 2003 | | - with warnings_helper.check_warnings(('', DeprecationWarning)): | 2004 | | - h = client.HTTPSConnection('localhost', server.port, | 2005 | | - context=context, check_hostname=True) | | 2002 | + | | 2003 | + # Same with explicit context.check_hostname=True | | 2004 | + context.check_hostname = True | | 2005 | + h = client.HTTPSConnection('localhost', server.port, context=context) | 2006 | 2006 | with self.assertRaises(ssl.CertificateError): | 2007 | 2007 | h.request('GET', '/') | 2008 | | - # With check_hostname=False, the mismatching is ignored | 2009 | | - context.check_hostname = False | 2010 | | - with warnings_helper.check_warnings(('', DeprecationWarning)): | 2011 | | - h = client.HTTPSConnection('localhost', server.port, | 2012 | | - context=context, check_hostname=False) | 2013 | | - h.request('GET', '/nonexistent') | 2014 | | - resp = h.getresponse() | 2015 | | - resp.close() | 2016 | | - h.close() | 2017 | | - self.assertEqual(resp.status, 404) | 2018 | | - # The context's check_hostname setting is used if one isn't passed to | 2019 | | - # HTTPSConnection. | | 2008 | + | | 2009 | + # With context.check_hostname=False, the mismatching is ignored | 2020 | 2010 | context.check_hostname = False | 2021 | 2011 | h = client.HTTPSConnection('localhost', server.port, context=context) | 2022 | 2012 | h.request('GET', '/nonexistent') | 2023 | 2013 | resp = h.getresponse() | 2024 | | - self.assertEqual(resp.status, 404) | 2025 | 2014 | resp.close() | 2026 | 2015 | h.close() | 2027 | | - # Passing check_hostname to HTTPSConnection should override the | 2028 | | - # context's setting. | 2029 | | - with warnings_helper.check_warnings(('', DeprecationWarning)): | 2030 | | - h = client.HTTPSConnection('localhost', server.port, | 2031 | | - context=context, check_hostname=True) | 2032 | | - with self.assertRaises(ssl.CertificateError): | 2033 | | - h.request('GET', '/') | | 2016 | + self.assertEqual(resp.status, 404) | 2034 | 2017 |
| 2035 | 2018 | @unittest.skipIf(not hasattr(client, 'HTTPSConnection'), | 2036 | 2019 | 'http.client.HTTPSConnection not available') | @@ -2066,11 +2049,9 @@ def test_tls13_pha(self): | 2066 | 2049 | self.assertIs(h._context, context) | 2067 | 2050 | self.assertFalse(h._context.post_handshake_auth) | 2068 | 2051 |
| 2069 | | - with warnings.catch_warnings(): | 2070 | | - warnings.filterwarnings('ignore', 'key_file, cert_file and check_hostname are deprecated', | 2071 | | - DeprecationWarning) | 2072 | | - h = client.HTTPSConnection('localhost', 443, context=context, | 2073 | | - cert_file=CERT_localhost) | | 2052 | + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT, cert_file=CERT_localhost) | | 2053 | + context.post_handshake_auth = True | | 2054 | + h = client.HTTPSConnection('localhost', 443, context=context) | 2074 | 2055 | self.assertTrue(h._context.post_handshake_auth) | 2075 | 2056 |
| 2076 | 2057 |
| |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_imaplib.py -21Lines changed: 0 additions & 21 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -573,15 +573,6 @@ def test_ssl_verified(self): | 573 | 573 | ssl_context=ssl_context) | 574 | 574 | client.shutdown() | 575 | 575 |
| 576 | | - # Mock the private method _connect(), so mark the test as specific | 577 | | - # to CPython stdlib | 578 | | - @cpython_only | 579 | | - def test_certfile_arg_warn(self): | 580 | | - with warnings_helper.check_warnings(('', DeprecationWarning)): | 581 | | - with mock.patch.object(self.imap_class, 'open'): | 582 | | - with mock.patch.object(self.imap_class, '_connect'): | 583 | | - self.imap_class('localhost', 143, certfile=CERTFILE) | 584 | | - | 585 | 576 | class ThreadedNetworkedTests(unittest.TestCase): | 586 | 577 | server_class = socketserver.TCPServer | 587 | 578 | imap_class = imaplib.IMAP4 | @@ -1070,18 +1061,6 @@ def test_logout(self): | 1070 | 1061 | rs = _server.logout() | 1071 | 1062 | self.assertEqual(rs[0], 'BYE', rs) | 1072 | 1063 |
| 1073 | | - def test_ssl_context_certfile_exclusive(self): | 1074 | | - with socket_helper.transient_internet(self.host): | 1075 | | - self.assertRaises( | 1076 | | - ValueError, self.imap_class, self.host, self.port, | 1077 | | - certfile=CERTFILE, ssl_context=self.create_ssl_context()) | 1078 | | - | 1079 | | - def test_ssl_context_keyfile_exclusive(self): | 1080 | | - with socket_helper.transient_internet(self.host): | 1081 | | - self.assertRaises( | 1082 | | - ValueError, self.imap_class, self.host, self.port, | 1083 | | - keyfile=CERTFILE, ssl_context=self.create_ssl_context()) | 1084 | | - | 1085 | 1064 |
| 1086 | 1065 | if __name__ == "__main__": | 1087 | 1066 | unittest.main() |
|