Skip to content

Commit cd36140

Browse files
committed
Remove Python 2 compatibility helpers: to_bytes, to_native, to_unicode
Replace all call sites with direct .encode()/.decode() calls, as these functions are trivial wrappers on Python 3. Closes #197
1 parent 9139cda commit cd36140

10 files changed

Lines changed: 28 additions & 65 deletions

File tree

emails/loader/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import urllib.parse as urlparse
55

6-
from ..utils import to_unicode, to_native
76
from ..message import Message
87
from ..utils import fetch_url
98
from .local_store import (FileSystemLoader, ZipLoader, MsgLoader, FileNotFound)
@@ -77,7 +76,7 @@ def _extract_base_url(url):
7776
# Load html page
7877
r = fetch_url(url, requests_args=requests_params)
7978
html = r.content
80-
html = to_unicode(html, charset=guess_charset(r.headers, html))
79+
html = html.decode(guess_charset(r.headers, html) or 'utf-8')
8180
html = html.replace('\r\n', '\n') # Remove \r
8281

8382
return from_html(html,

emails/loader/helpers.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
except ImportError:
1313
import chardet
1414

15-
from ..utils import to_native, to_unicode
1615

1716
# HTML page charset stuff
1817

@@ -29,7 +28,7 @@ def __init__(self, conv=None):
2928
if k.startswith('re_'):
3029
setattr(self, k, re.compile(conv(getattr(self, k)), re.I + re.S + re.M))
3130

32-
RULES_U = ReRules(conv=to_unicode)
31+
RULES_U = ReRules(conv=lambda x: x.decode())
3332
RULES_B = ReRules()
3433

3534

@@ -40,13 +39,13 @@ def guess_text_charset(text, is_html=False):
4039
if rules.re_is_http_equiv.findall(meta):
4140
for content in rules.re_parse_http_equiv.findall(meta):
4241
for charset in rules.re_charset.findall(content):
43-
return to_native(charset)
42+
return charset.decode() if isinstance(charset, bytes) else charset
4443
else:
4544
for charset in rules.re_charset.findall(meta):
46-
return to_native(charset)
45+
return charset.decode() if isinstance(charset, bytes) else charset
4746
# guess by chardet
4847
if isinstance(text, bytes):
49-
return to_native(chardet.detect(text)['encoding'])
48+
return chardet.detect(text)['encoding']
5049

5150

5251
def guess_html_charset(html):
@@ -68,7 +67,7 @@ def guess_charset(headers, html):
6867
# guess by html content
6968
charset = guess_html_charset(html)
7069
if charset:
71-
return to_unicode(charset)
70+
return charset
7271

7372
COMMON_CHARSETS = ('ascii', 'utf-8', 'utf-16', 'windows-1251', 'windows-1252', 'cp850')
7473

@@ -100,7 +99,7 @@ def decode_text(text,
10099
_last_exc = None
101100
for enc in _charsets:
102101
try:
103-
return to_unicode(text, charset=enc), enc
102+
return text.decode(enc), enc
104103
except UnicodeDecodeError as exc:
105104
_last_exc = exc
106105

emails/loader/local_store.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from zipfile import ZipFile
99
import email
1010

11-
from ..utils import to_unicode, to_native, formataddr, decode_header
11+
from ..utils import formataddr, decode_header
1212
from ..loader.helpers import decode_text
1313
from ..message import Message
1414

@@ -183,7 +183,7 @@ def __init__(self, file, encoding='utf-8', base_path=None):
183183
def _decode_filename(self, name):
184184
for enc in self.common_filename_charsets:
185185
try:
186-
return to_unicode(name, enc)
186+
return name.decode(enc) if isinstance(name, bytes) else name
187187
except UnicodeDecodeError:
188188
pass
189189
return name
@@ -203,7 +203,7 @@ def get_file(self, name):
203203
self._unpack()
204204

205205
if isinstance(name, str):
206-
name = to_unicode(name, 'utf-8')
206+
name = name.decode('utf-8') if isinstance(name, bytes) else name
207207

208208
if name not in self._original_filenames:
209209
name = self._decoded_filenames.get(name)
@@ -229,7 +229,7 @@ def __init__(self, msg, base_path=None):
229229
if isinstance(msg, str):
230230
self.msg = email.message_from_string(msg)
231231
elif isinstance(msg, bytes):
232-
self.msg = email.message_from_string(to_native(msg))
232+
self.msg = email.message_from_string(msg.decode())
233233
else:
234234
self.msg = msg
235235
self.base_path = base_path

emails/store/file.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import urllib.parse as urlparse
1111

12-
from ..utils import fetch_url, encode_header, to_bytes
12+
from ..utils import fetch_url, encode_header
1313

1414

1515
MIMETYPE_UNKNOWN = 'application/unknown'
@@ -143,7 +143,12 @@ def mime(self) -> MIMEBase | None:
143143
if p is None:
144144
filename_header = encode_header(self.filename)
145145
p = MIMEBase(*self.mime_type.split('/', 1), name=filename_header)
146-
payload = to_bytes(self.data) or b''
146+
if isinstance(self.data, str):
147+
payload = self.data.encode()
148+
elif self.data is not None:
149+
payload = bytes(self.data)
150+
else:
151+
payload = b''
147152
p.set_payload(payload)
148153
encode_base64(p)
149154
if 'content-disposition' not in self._headers:

emails/testsuite/loader/test_rfc822_loader.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import email
33
import datetime
44
import os.path
5-
from emails.utils import to_native
65
import emails.loader
76
from emails.loader.local_store import MsgLoader
87

@@ -79,7 +78,7 @@ def test_msgloader():
7978
def _try_decode(s, charsets=('utf-8', 'koi8-r', 'cp1251')):
8079
for charset in charsets:
8180
try:
82-
return to_native(s, charset), charset
81+
return s.decode(charset), charset
8382
except UnicodeDecodeError:
8483
pass
8584
return None, None

emails/testsuite/message/test_dkim.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from emails import Message
66
from io import StringIO
77

8-
from emails.utils import to_bytes, to_native
98
from emails.exc import DKIMException
109
from emails.utils import load_email_charsets
1110
import emails.packages.dkim
@@ -40,7 +39,7 @@ def _generate_key(length=1024):
4039
try:
4140
from Crypto.PublicKey import RSA
4241
key = RSA.generate(length)
43-
return to_bytes(key.exportKey()), to_bytes(key.publickey().exportKey())
42+
return key.exportKey(), key.publickey().exportKey()
4443
except ImportError:
4544
return PRIV_KEY, PUB_KEY
4645

@@ -49,15 +48,15 @@ def _check_dkim(message, pub_key=PUB_KEY):
4948
def _plain_public_key(s):
5049
return b"".join([l for l in s.split(b'\n') if not l.startswith(b'---')])
5150
message = message.as_string()
52-
o = emails.packages.dkim.DKIM(message=to_bytes(message))
51+
o = emails.packages.dkim.DKIM(message=message.encode())
5352
return o.verify(dnsfunc=lambda name: b"".join([b"v=DKIM1; p=", _plain_public_key(pub_key)]))
5453

5554

5655
def test_dkim():
5756

5857
priv_key, pub_key = _generate_key(length=1024)
5958

60-
DKIM_PARAMS = [dict(key=StringIO(to_native(priv_key)),
59+
DKIM_PARAMS = [dict(key=StringIO(priv_key.decode()),
6160
selector='_dkim',
6261
domain='somewhere1.net'),
6362

@@ -150,7 +149,7 @@ def test_dkim_sign_twice():
150149

151150
priv_key, pub_key = _generate_key(length=1024)
152151
message = Message(**common_email_data())
153-
message.dkim(key=StringIO(to_native(priv_key)), selector='_dkim', domain='somewhere.net')
152+
message.dkim(key=StringIO(priv_key.decode()), selector='_dkim', domain='somewhere.net')
154153
for n in range(2):
155154
message.subject = 'Test %s' % n
156155
assert _check_dkim(message, pub_key)

emails/testsuite/message/test_message.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import emails.exc
99
from io import StringIO
1010

11-
from emails.utils import to_unicode
1211
from emails.utils import decode_header, MessageID
1312
from emails.backend.inmemory import InMemoryBackend
1413

@@ -73,7 +72,7 @@ def my_after_build(original_message, built_message):
7372

7473
s = m.as_string()
7574
print("type of message.as_string() is {0}".format(type(s)))
76-
assert AFTER_BUILD_HEADER in to_unicode(s, 'utf-8')
75+
assert AFTER_BUILD_HEADER in s
7776

7877

7978
def test_before_build():

emails/testsuite/smtp_servers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import datetime
44
import random
55
import time
6-
from emails.utils import to_unicode
76

87
DEFAULT_FROM = os.environ.get('SMTP_TEST_FROM_EMAIL') or 'python-emails@lavr.me'
98
SUBJECT_SUFFIX = os.environ.get('SMTP_TEST_SUBJECT_SUFFIX')
@@ -88,7 +87,7 @@ def patch_message(self, message):
8887
message.mail_to = self.to_email
8988

9089
# TODO: this code breaks template in subject; fix it
91-
if not to_unicode(message.subject).startswith(self.subject_prefix) :
90+
if not message.subject.startswith(self.subject_prefix) :
9291
message.subject = " ".join([self.subject_prefix, message.subject,
9392
'// %s' % SUBJECT_SUFFIX])
9493

emails/transformer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import urllib.parse as urlparse
1414

15-
from .utils import to_unicode
1615
from .loader.local_store import FileNotFound
1716
from .store import MemoryFileStore, LazyHTTPFile
1817
from .template.base import BaseTemplate
@@ -125,7 +124,8 @@ def _apply_to_style_uri(style_text, func):
125124
dirty = True
126125
value.uri = new_uri
127126
if dirty:
128-
return to_unicode(parser.cssText, 'utf-8')
127+
css_text = parser.cssText
128+
return css_text.decode('utf-8') if isinstance(css_text, bytes) else css_text
129129
else:
130130
return style_text
131131

emails/utils.py

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import sys
43
import os
54
import socket
65
from time import mktime
@@ -9,7 +8,7 @@
98
from functools import wraps
109
from io import StringIO, BytesIO
1110
from collections.abc import Callable
12-
from typing import Any, TypeVar, cast, overload
11+
from typing import Any, TypeVar, cast
1312

1413
import email.charset
1514
from email import generator
@@ -25,41 +24,6 @@
2524
F = TypeVar('F', bound=Callable[..., Any])
2625

2726

28-
def to_native(x: str | bytes | None, charset: str = sys.getdefaultencoding(),
29-
errors: str = 'strict') -> str | None:
30-
if x is None or isinstance(x, str):
31-
return x
32-
return x.decode(charset, errors)
33-
34-
35-
@overload
36-
def to_unicode(x: None, charset: str = ..., errors: str = ...) -> None: ...
37-
@overload
38-
def to_unicode(x: str | bytes, charset: str = ..., errors: str = ...) -> str: ...
39-
@overload
40-
def to_unicode(x: Any, charset: str = ..., errors: str = ...) -> str | None: ...
41-
42-
def to_unicode(x: Any, charset: str = sys.getdefaultencoding(),
43-
errors: str = 'strict') -> str | None:
44-
if x is None:
45-
return None
46-
if not isinstance(x, bytes):
47-
return str(x)
48-
return x.decode(charset, errors)
49-
50-
51-
def to_bytes(x: str | bytes | bytearray | memoryview | None,
52-
charset: str = sys.getdefaultencoding(),
53-
errors: str = 'strict') -> bytes | None:
54-
if x is None:
55-
return None
56-
if isinstance(x, (bytes, bytearray, memoryview)):
57-
return bytes(x)
58-
if isinstance(x, str):
59-
return x.encode(charset, errors)
60-
raise TypeError('Expected bytes')
61-
62-
6327
def formataddr(pair: tuple[str | None, str]) -> str:
6428
"""
6529
Takes a 2-tuple of the form (realname, email_address) and returns RFC2822-like string.

0 commit comments

Comments
 (0)