Refs #27753 -- Favored force/smart_str() over force/smart_text().

This commit is contained in:
Aymeric Augustin 2017-01-26 10:08:08 +01:00 committed by Tim Graham
parent 24b82cd201
commit 3bb6a4390c
21 changed files with 84 additions and 101 deletions

View File

@ -40,7 +40,7 @@ from django.contrib.gis.gdal.driver import Driver
from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.layer import Layer from django.contrib.gis.gdal.layer import Layer
from django.contrib.gis.gdal.prototypes import ds as capi from django.contrib.gis.gdal.prototypes import ds as capi
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
@ -117,4 +117,4 @@ class DataSource(GDALBase):
def name(self): def name(self):
"Return the name of the data source." "Return the name of the data source."
name = capi.get_ds_name(self._ptr) name = capi.get_ds_name(self._ptr)
return force_text(name, self.encoding, strings_only=True) return force_str(name, self.encoding, strings_only=True)

View File

@ -3,7 +3,7 @@ from ctypes import c_void_p
from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import ds as vcapi, raster as rcapi from django.contrib.gis.gdal.prototypes import ds as vcapi, raster as rcapi
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
class Driver(GDALBase): class Driver(GDALBase):
@ -94,4 +94,4 @@ class Driver(GDALBase):
""" """
Return description/name string for this driver. Return description/name string for this driver.
""" """
return force_text(rcapi.get_driver_description(self.ptr)) return force_str(rcapi.get_driver_description(self.ptr))

View File

@ -3,7 +3,7 @@ from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.field import Field from django.contrib.gis.gdal.field import Field
from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType
from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
@ -67,7 +67,7 @@ class Feature(GDALBase):
def layer_name(self): def layer_name(self):
"Return the name of the layer for the feature." "Return the name of the layer for the feature."
name = capi.get_feat_name(self._layer._ldefn) name = capi.get_feat_name(self._layer._ldefn)
return force_text(name, self.encoding, strings_only=True) return force_str(name, self.encoding, strings_only=True)
@property @property
def num_fields(self): def num_fields(self):
@ -78,7 +78,7 @@ class Feature(GDALBase):
def fields(self): def fields(self):
"Return a list of fields in the Feature." "Return a list of fields in the Feature."
return [ return [
force_text( force_str(
capi.get_field_name(capi.get_field_defn(self._layer._ldefn, i)), capi.get_field_name(capi.get_field_defn(self._layer._ldefn, i)),
self.encoding, self.encoding,
strings_only=True strings_only=True

View File

@ -4,7 +4,7 @@ from datetime import date, datetime, time
from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import ds as capi from django.contrib.gis.gdal.prototypes import ds as capi
from django.utils.encoding import force_text from django.utils.encoding import force_str
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
@ -55,7 +55,7 @@ class Field(GDALBase):
if not self.is_set: if not self.is_set:
return None return None
string = capi.get_field_as_string(self._feat.ptr, self._index) string = capi.get_field_as_string(self._feat.ptr, self._index)
return force_text(string, encoding=self._feat.encoding, strings_only=True) return force_str(string, encoding=self._feat.encoding, strings_only=True)
def as_datetime(self): def as_datetime(self):
"Retrieve the Field's value as a tuple of date & time components." "Retrieve the Field's value as a tuple of date & time components."
@ -80,7 +80,7 @@ class Field(GDALBase):
def name(self): def name(self):
"Return the name of this Field." "Return the name of this Field."
name = capi.get_field_name(self.ptr) name = capi.get_field_name(self.ptr)
return force_text(name, encoding=self._feat.encoding, strings_only=True) return force_str(name, encoding=self._feat.encoding, strings_only=True)
@property @property
def precision(self): def precision(self):

View File

@ -11,7 +11,7 @@ from django.contrib.gis.gdal.prototypes import (
ds as capi, geom as geom_api, srs as srs_api, ds as capi, geom as geom_api, srs as srs_api,
) )
from django.contrib.gis.gdal.srs import SpatialReference from django.contrib.gis.gdal.srs import SpatialReference
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
@ -101,7 +101,7 @@ class Layer(GDALBase):
def name(self): def name(self):
"Return the name of this layer in the Data Source." "Return the name of this layer in the Data Source."
name = capi.get_fd_name(self._ldefn) name = capi.get_fd_name(self._ldefn)
return force_text(name, self._ds.encoding, strings_only=True) return force_str(name, self._ds.encoding, strings_only=True)
@property @property
def num_feat(self, force=1): def num_feat(self, force=1):
@ -133,9 +133,10 @@ class Layer(GDALBase):
Return a list of string names corresponding to each of the Fields Return a list of string names corresponding to each of the Fields
available in this Layer. available in this Layer.
""" """
return [force_text(capi.get_field_name(capi.get_field_defn(self._ldefn, i)), return [force_str(
self._ds.encoding, strings_only=True) capi.get_field_name(capi.get_field_defn(self._ldefn, i)),
for i in range(self.num_fields)] self._ds.encoding, strings_only=True,
) for i in range(self.num_fields)]
@property @property
def field_types(self): def field_types(self):

View File

@ -4,7 +4,7 @@ from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import raster as capi from django.contrib.gis.gdal.prototypes import raster as capi
from django.contrib.gis.gdal.raster.base import GDALRasterBase from django.contrib.gis.gdal.raster.base import GDALRasterBase
from django.contrib.gis.shortcuts import numpy from django.contrib.gis.shortcuts import numpy
from django.utils.encoding import force_text from django.utils.encoding import force_str
from .const import ( from .const import (
GDAL_COLOR_TYPES, GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES, GDAL_COLOR_TYPES, GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES,
@ -32,7 +32,7 @@ class GDALBand(GDALRasterBase):
""" """
Return the description string of the band. Return the description string of the band.
""" """
return force_text(capi.get_band_description(self._ptr)) return force_str(capi.get_band_description(self._ptr))
@property @property
def width(self): def width(self):

View File

@ -17,7 +17,7 @@ from django.contrib.gis.gdal.raster.const import (
) )
from django.contrib.gis.gdal.srs import SpatialReference, SRSException from django.contrib.gis.gdal.srs import SpatialReference, SRSException
from django.contrib.gis.geometry import json_regex from django.contrib.gis.geometry import json_regex
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -233,7 +233,7 @@ class GDALRaster(GDALRasterBase):
Return the name of this raster. Corresponds to filename Return the name of this raster. Corresponds to filename
for file-based rasters. for file-based rasters.
""" """
return force_text(capi.get_ds_description(self._ptr)) return force_str(capi.get_ds_description(self._ptr))
@cached_property @cached_property
def driver(self): def driver(self):

View File

@ -31,7 +31,7 @@ from ctypes import byref, c_char_p, c_int
from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import SRSException from django.contrib.gis.gdal.error import SRSException
from django.contrib.gis.gdal.prototypes import srs as capi from django.contrib.gis.gdal.prototypes import srs as capi
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
class SpatialReference(GDALBase): class SpatialReference(GDALBase):
@ -222,7 +222,7 @@ class SpatialReference(GDALBase):
elif self.geographic: elif self.geographic:
units, name = capi.angular_units(self.ptr, byref(c_char_p())) units, name = capi.angular_units(self.ptr, byref(c_char_p()))
if name is not None: if name is not None:
name = force_text(name) name = force_str(name)
return (units, name) return (units, name)
# #### Spheroid/Ellipsoid Properties #### # #### Spheroid/Ellipsoid Properties ####

View File

@ -18,7 +18,7 @@ from django.contrib.gis.geos.prototypes.io import (
ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w, ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w,
) )
from django.utils.deconstruct import deconstructible from django.utils.deconstruct import deconstructible
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
class GEOSGeometryBase(GEOSBase): class GEOSGeometryBase(GEOSBase):
@ -695,7 +695,7 @@ class GEOSGeometry(GEOSGeometryBase, ListMixin):
""" """
input_srid = None input_srid = None
if isinstance(geo_input, bytes): if isinstance(geo_input, bytes):
geo_input = force_text(geo_input) geo_input = force_str(geo_input)
if isinstance(geo_input, str): if isinstance(geo_input, str):
wkt_m = wkt_regex.match(geo_input) wkt_m = wkt_regex.match(geo_input)
if wkt_m: if wkt_m:

View File

@ -20,7 +20,7 @@ from django.contrib.gis.gdal.field import (
) )
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
from django.db import connections, models, router, transaction from django.db import connections, models, router, transaction
from django.utils.encoding import force_text from django.utils.encoding import force_str
# LayerMapping exceptions. # LayerMapping exceptions.
@ -348,7 +348,7 @@ class LayerMapping:
if self.encoding and ogr_field.value is not None: if self.encoding and ogr_field.value is not None:
# The encoding for OGR data sources may be specified here # The encoding for OGR data sources may be specified here
# (e.g., 'cp437' for Census Bureau boundary files). # (e.g., 'cp437' for Census Bureau boundary files).
val = force_text(ogr_field.value, self.encoding) val = force_str(ogr_field.value, self.encoding)
else: else:
val = ogr_field.value val = ogr_field.value
if model_field.max_length and val is not None and len(val) > model_field.max_length: if model_field.max_length and val is not None and len(val) > model_field.max_length:

View File

@ -16,7 +16,7 @@ from pathlib import Path
from django.conf import settings from django.conf import settings
from django.core.mail.utils import DNS_NAME from django.core.mail.utils import DNS_NAME
from django.utils.encoding import force_text from django.utils.encoding import force_str
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
# some spam filters. # some spam filters.
@ -375,7 +375,7 @@ class EmailMessage:
elif not isinstance(content, Message): elif not isinstance(content, Message):
# For compatibility with existing code, parse the message # For compatibility with existing code, parse the message
# into an email.Message object if it is not one already. # into an email.Message object if it is not one already.
content = message_from_string(force_text(content)) content = message_from_string(force_str(content))
attachment = SafeMIMEMessage(content, subtype) attachment = SafeMIMEMessage(content, subtype)
else: else:

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.db import NotSupportedError, transaction from django.db import NotSupportedError, transaction
from django.db.backends import utils from django.db.backends import utils
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_str
class BaseDatabaseOperations: class BaseDatabaseOperations:
@ -235,7 +235,7 @@ class BaseDatabaseOperations:
""" """
# Convert params to contain string values. # Convert params to contain string values.
def to_string(s): def to_string(s):
return force_text(s, strings_only=True, errors='replace') return force_str(s, strings_only=True, errors='replace')
if isinstance(params, (list, tuple)): if isinstance(params, (list, tuple)):
u_params = tuple(to_string(val) for val in params) u_params = tuple(to_string(val) for val in params)
elif params is None: elif params is None:

View File

@ -13,7 +13,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import utils from django.db import utils
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_str
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -342,7 +342,7 @@ class OracleParam:
else: else:
# To transmit to the database, we need Unicode if supported # To transmit to the database, we need Unicode if supported
# To get size right, we must consider bytes. # To get size right, we must consider bytes.
self.force_bytes = force_text(param, cursor.charset, strings_only) self.force_bytes = force_str(param, cursor.charset, strings_only)
if isinstance(self.force_bytes, str): if isinstance(self.force_bytes, str):
# We could optimize by only converting up to 4000 bytes here # We could optimize by only converting up to 4000 bytes here
string_size = len(force_bytes(param, cursor.charset, strings_only)) string_size = len(force_bytes(param, cursor.charset, strings_only))

View File

@ -17,7 +17,7 @@ from django.core.files.uploadhandler import (
SkipFile, StopFutureHandlers, StopUpload, SkipFile, StopFutureHandlers, StopUpload,
) )
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.text import unescape_entities from django.utils.text import unescape_entities
__all__ = ('MultiPartParser', 'MultiPartParserError', 'InputStreamExhausted') __all__ = ('MultiPartParser', 'MultiPartParserError', 'InputStreamExhausted')
@ -164,7 +164,7 @@ class MultiPartParser:
transfer_encoding = meta_data.get('content-transfer-encoding') transfer_encoding = meta_data.get('content-transfer-encoding')
if transfer_encoding is not None: if transfer_encoding is not None:
transfer_encoding = transfer_encoding[0].strip() transfer_encoding = transfer_encoding[0].strip()
field_name = force_text(field_name, encoding, errors='replace') field_name = force_str(field_name, encoding, errors='replace')
if item_type == FIELD: if item_type == FIELD:
# Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS. # Avoid storing more than DATA_UPLOAD_MAX_NUMBER_FIELDS.
@ -199,12 +199,12 @@ class MultiPartParser:
num_bytes_read > settings.DATA_UPLOAD_MAX_MEMORY_SIZE): num_bytes_read > settings.DATA_UPLOAD_MAX_MEMORY_SIZE):
raise RequestDataTooBig('Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.') raise RequestDataTooBig('Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.')
self._post.appendlist(field_name, force_text(data, encoding, errors='replace')) self._post.appendlist(field_name, force_str(data, encoding, errors='replace'))
elif item_type == FILE: elif item_type == FILE:
# This is a file, use the handler... # This is a file, use the handler...
file_name = disposition.get('filename') file_name = disposition.get('filename')
if file_name: if file_name:
file_name = force_text(file_name, encoding, errors='replace') file_name = force_str(file_name, encoding, errors='replace')
file_name = self.IE_sanitize(unescape_entities(file_name)) file_name = self.IE_sanitize(unescape_entities(file_name))
if not file_name: if not file_name:
continue continue
@ -290,7 +290,7 @@ class MultiPartParser:
file_obj = handler.file_complete(counters[i]) file_obj = handler.file_complete(counters[i])
if file_obj: if file_obj:
# If it returns a file object, then set the files dict. # If it returns a file object, then set the files dict.
self._files.appendlist(force_text(old_field_name, self._encoding, errors='replace'), file_obj) self._files.appendlist(force_str(old_field_name, self._encoding, errors='replace'), file_obj)
break break
def IE_sanitize(self, filename): def IE_sanitize(self, filename):

View File

@ -537,7 +537,7 @@ class QueryDict(MultiValueDict):
# It's neither necessary nor appropriate to use # It's neither necessary nor appropriate to use
# django.utils.encoding.force_text for parsing URLs and form inputs. Thus, # django.utils.encoding.force_str() for parsing URLs and form inputs. Thus,
# this slightly more restricted function, used by QueryDict. # this slightly more restricted function, used by QueryDict.
def bytes_to_text(s, encoding): def bytes_to_text(s, encoding):
""" """

View File

@ -16,7 +16,7 @@ class DjangoUnicodeDecodeError(UnicodeDecodeError):
return '%s. You passed in %r (%s)' % (super().__str__(), self.obj, type(self.obj)) return '%s. You passed in %r (%s)' % (super().__str__(), self.obj, type(self.obj))
def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'): def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
""" """
Return a string representing 's'. Treat bytestrings using the 'encoding' Return a string representing 's'. Treat bytestrings using the 'encoding'
codec. codec.
@ -26,7 +26,7 @@ def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
if isinstance(s, Promise): if isinstance(s, Promise):
# The input is the result of a gettext_lazy() call. # The input is the result of a gettext_lazy() call.
return s return s
return force_text(s, encoding, strings_only, errors) return force_str(s, encoding, strings_only, errors)
_PROTECTED_TYPES = ( _PROTECTED_TYPES = (
@ -38,14 +38,14 @@ def is_protected_type(obj):
"""Determine if the object instance is of a protected type. """Determine if the object instance is of a protected type.
Objects of protected types are preserved as-is when passed to Objects of protected types are preserved as-is when passed to
force_text(strings_only=True). force_str(strings_only=True).
""" """
return isinstance(obj, _PROTECTED_TYPES) return isinstance(obj, _PROTECTED_TYPES)
def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): def force_str(s, encoding='utf-8', strings_only=False, errors='strict'):
""" """
Similar to smart_text, except that lazy instances are resolved to Similar to smart_str(), except that lazy instances are resolved to
strings, rather than kept as lazy objects. strings, rather than kept as lazy objects.
If strings_only is True, don't convert (some) non-string-like objects. If strings_only is True, don't convert (some) non-string-like objects.
@ -97,18 +97,8 @@ def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
return str(s).encode(encoding, errors) return str(s).encode(encoding, errors)
smart_str = smart_text smart_text = smart_str
force_str = force_text force_text = force_str
smart_str.__doc__ = """
Apply smart_text in Python 3 and smart_bytes in Python 2.
This is suitable for writing to sys.stdout (for instance).
"""
force_str.__doc__ = """
Apply force_text in Python 3 and force_bytes in Python 2.
"""
def iri_to_uri(iri): def iri_to_uri(iri):

View File

@ -11,7 +11,7 @@ from django.template.defaultfilters import pprint
from django.urls import Resolver404, resolve from django.urls import Resolver404, resolve
from django.utils import timezone from django.utils import timezone
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.utils.version import get_docs_version from django.utils.version import get_docs_version
@ -280,7 +280,7 @@ class ExceptionReporter:
end = getattr(self.exc_value, 'end', None) end = getattr(self.exc_value, 'end', None)
if start is not None and end is not None: if start is not None and end is not None:
unicode_str = self.exc_value.args[1] unicode_str = self.exc_value.args[1]
unicode_hint = force_text( unicode_hint = force_str(
unicode_str[max(start - 5, 0):min(end + 5, len(unicode_str))], unicode_str[max(start - 5, 0):min(end + 5, len(unicode_str))],
'ascii', errors='replace' 'ascii', errors='replace'
) )

View File

@ -2079,7 +2079,7 @@ unpredictable value.
randomly-generated ``SECRET_KEY`` to each new project. randomly-generated ``SECRET_KEY`` to each new project.
Uses of the key shouldn't assume that it's text or bytes. Every use should go Uses of the key shouldn't assume that it's text or bytes. Every use should go
through :func:`~django.utils.encoding.force_text` or through :func:`~django.utils.encoding.force_str` or
:func:`~django.utils.encoding.force_bytes` to convert it to the desired type. :func:`~django.utils.encoding.force_bytes` to convert it to the desired type.
Django will refuse to start if :setting:`SECRET_KEY` is not set. Django will refuse to start if :setting:`SECRET_KEY` is not set.

View File

@ -108,7 +108,7 @@ Conversion functions
The ``django.utils.encoding`` module contains a few functions that are handy The ``django.utils.encoding`` module contains a few functions that are handy
for converting back and forth between strings and bytestrings. for converting back and forth between strings and bytestrings.
* ``smart_text(s, encoding='utf-8', strings_only=False, errors='strict')`` * ``smart_str(s, encoding='utf-8', strings_only=False, errors='strict')``
converts its input to a string. The ``encoding`` parameter converts its input to a string. The ``encoding`` parameter
specifies the input encoding. (For example, Django uses this internally specifies the input encoding. (For example, Django uses this internally
when processing form input data, which might not be UTF-8 encoded.) The when processing form input data, which might not be UTF-8 encoded.) The
@ -118,24 +118,23 @@ for converting back and forth between strings and bytestrings.
that are accepted by Python's ``str()`` function for its error that are accepted by Python's ``str()`` function for its error
handling. handling.
* ``force_text(s, encoding='utf-8', strings_only=False, * ``force_str(s, encoding='utf-8', strings_only=False, errors='strict')`` is
errors='strict')`` is identical to ``smart_text()`` in almost all identical to ``smart_str()`` in almost all cases. The difference is when the
cases. The difference is when the first argument is a :ref:`lazy first argument is a :ref:`lazy translation <lazy-translations>` instance.
translation <lazy-translations>` instance. While ``smart_text()`` While ``smart_str()`` preserves lazy translations, ``force_str()`` forces
preserves lazy translations, ``force_text()`` forces those objects to a those objects to a string (causing the translation to occur). Normally,
string (causing the translation to occur). Normally, you'll want you'll want to use ``smart_str()``. However, ``force_str()`` is useful in
to use ``smart_text()``. However, ``force_text()`` is useful in template tags and filters that absolutely *must* have a string to work with,
template tags and filters that absolutely *must* have a string to work not just something that can be converted to a string.
with, not just something that can be converted to a string.
* ``smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')`` * ``smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')``
is essentially the opposite of ``smart_text()``. It forces the first is essentially the opposite of ``smart_str()``. It forces the first
argument to a bytestring. The ``strings_only`` parameter has the same argument to a bytestring. The ``strings_only`` parameter has the same
behavior as for ``smart_text()`` and ``force_text()``. This is behavior as for ``smart_str()`` and ``force_str()``. This is
slightly different semantics from Python's builtin ``str()`` function, slightly different semantics from Python's builtin ``str()`` function,
but the difference is needed in a few places within Django's internals. but the difference is needed in a few places within Django's internals.
Normally, you'll only need to use ``force_text()``. Call it as early as Normally, you'll only need to use ``force_str()``. Call it as early as
possible on any input data that might be either a string or a bytestring, and possible on any input data that might be either a string or a bytestring, and
from then on, you can treat the result as always being a string. from then on, you can treat the result as always being a string.
@ -280,7 +279,7 @@ A couple of tips to remember when writing your own template tags and filters:
* Always return strings from a template tag's ``render()`` method * Always return strings from a template tag's ``render()`` method
and from template filters. and from template filters.
* Use ``force_text()`` in preference to ``smart_text()`` in these * Use ``force_str()`` in preference to ``smart_str()`` in these
places. Tag rendering and filter calls occur as the template is being places. Tag rendering and filter calls occur as the template is being
rendered, so there is no advantage to postponing the conversion of lazy rendered, so there is no advantage to postponing the conversion of lazy
translation objects into strings. It's easier to work solely with translation objects into strings. It's easier to work solely with

View File

@ -191,7 +191,7 @@ The functions defined in this module share the following properties:
.. module:: django.utils.encoding .. module:: django.utils.encoding
:synopsis: A series of helper functions to manage character encoding. :synopsis: A series of helper functions to manage character encoding.
.. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict') .. function:: smart_str(s, encoding='utf-8', strings_only=False, errors='strict')
Returns a ``str`` object representing arbitrary object ``s``. Treats Returns a ``str`` object representing arbitrary object ``s``. Treats
bytestrings using the ``encoding`` codec. bytestrings using the ``encoding`` codec.
@ -204,11 +204,11 @@ The functions defined in this module share the following properties:
Determine if the object instance is of a protected type. Determine if the object instance is of a protected type.
Objects of protected types are preserved as-is when passed to Objects of protected types are preserved as-is when passed to
``force_text(strings_only=True)``. ``force_str(strings_only=True)``.
.. function:: force_text(s, encoding='utf-8', strings_only=False, errors='strict') .. function:: force_str(s, encoding='utf-8', strings_only=False, errors='strict')
Similar to ``smart_text``, except that lazy instances are resolved to Similar to ``smart_str()``, except that lazy instances are resolved to
strings, rather than kept as lazy objects. strings, rather than kept as lazy objects.
If ``strings_only`` is ``True``, don't convert (some) non-string-like If ``strings_only`` is ``True``, don't convert (some) non-string-like
@ -230,22 +230,15 @@ The functions defined in this module share the following properties:
If ``strings_only`` is ``True``, don't convert (some) non-string-like If ``strings_only`` is ``True``, don't convert (some) non-string-like
objects. objects.
.. function:: smart_str(s, encoding='utf-8', strings_only=False, errors='strict') .. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict')
Alias of :func:`smart_text`. This function returns a ``str`` or a lazy Alias of :func:`force_str` for backwards compatibility, especially in code
string. that supports Python 2.
For instance, this is suitable for writing to :data:`sys.stdout`. .. function:: force_text(s, encoding='utf-8', strings_only=False, errors='strict')
Alias of :func:`smart_bytes` on Python 2 (in older versions of Django that Alias of :func:`force_str` for backwards compatibility, especially in code
support it). that supports Python 2.
.. function:: force_str(s, encoding='utf-8', strings_only=False, errors='strict')
Alias of :func:`force_text`. This function always returns a ``str``.
Alias of :func:`force_bytes` on Python 2 (in older versions of Django that
support it).
.. function:: iri_to_uri(iri) .. function:: iri_to_uri(iri)
@ -622,7 +615,7 @@ escaping HTML.
interpolation, some of the formatting options provided by ``str.format()`` interpolation, some of the formatting options provided by ``str.format()``
(e.g. number formatting) will not work, since all arguments are passed (e.g. number formatting) will not work, since all arguments are passed
through :func:`conditional_escape` which (ultimately) calls through :func:`conditional_escape` which (ultimately) calls
:func:`~django.utils.encoding.force_text` on the values. :func:`~django.utils.encoding.force_str` on the values.
.. function:: format_html_join(sep, format_string, args_generator) .. function:: format_html_join(sep, format_string, args_generator)

View File

@ -6,7 +6,7 @@ from urllib.parse import quote_plus
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.utils.encoding import ( from django.utils.encoding import (
DjangoUnicodeDecodeError, escape_uri_path, filepath_to_uri, force_bytes, DjangoUnicodeDecodeError, escape_uri_path, filepath_to_uri, force_bytes,
force_text, get_system_encoding, iri_to_uri, smart_bytes, smart_text, force_str, get_system_encoding, iri_to_uri, smart_bytes, smart_str,
uri_to_iri, uri_to_iri,
) )
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
@ -14,7 +14,7 @@ from django.utils.translation import gettext_lazy
class TestEncodingUtils(SimpleTestCase): class TestEncodingUtils(SimpleTestCase):
def test_force_text_exception(self): def test_force_str_exception(self):
""" """
Broken __str__ actually raises an error. Broken __str__ actually raises an error.
""" """
@ -24,19 +24,19 @@ class TestEncodingUtils(SimpleTestCase):
# str(s) raises a TypeError if the result is not a text type. # str(s) raises a TypeError if the result is not a text type.
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
force_text(MyString()) force_str(MyString())
def test_force_text_lazy(self): def test_force_str_lazy(self):
s = SimpleLazyObject(lambda: 'x') s = SimpleLazyObject(lambda: 'x')
self.assertIs(type(force_text(s)), str) self.assertIs(type(force_str(s)), str)
def test_force_text_DjangoUnicodeDecodeError(self): def test_force_str_DjangoUnicodeDecodeError(self):
msg = ( msg = (
"'utf-8' codec can't decode byte 0xff in position 0: invalid " "'utf-8' codec can't decode byte 0xff in position 0: invalid "
"start byte. You passed in b'\\xff' (<class 'bytes'>)" "start byte. You passed in b'\\xff' (<class 'bytes'>)"
) )
with self.assertRaisesMessage(DjangoUnicodeDecodeError, msg): with self.assertRaisesMessage(DjangoUnicodeDecodeError, msg):
force_text(b'\xff') force_str(b'\xff')
def test_force_bytes_exception(self): def test_force_bytes_exception(self):
""" """
@ -75,16 +75,16 @@ class TestEncodingUtils(SimpleTestCase):
self.assertEqual(smart_bytes(1), b'1') self.assertEqual(smart_bytes(1), b'1')
self.assertEqual(smart_bytes('foo'), b'foo') self.assertEqual(smart_bytes('foo'), b'foo')
def test_smart_text(self): def test_smart_str(self):
class Test: class Test:
def __str__(self): def __str__(self):
return 'ŠĐĆŽćžšđ' return 'ŠĐĆŽćžšđ'
lazy_func = gettext_lazy('x') lazy_func = gettext_lazy('x')
self.assertIs(smart_text(lazy_func), lazy_func) self.assertIs(smart_str(lazy_func), lazy_func)
self.assertEqual(smart_text(Test()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') self.assertEqual(smart_str(Test()), '\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
self.assertEqual(smart_text(1), '1') self.assertEqual(smart_str(1), '1')
self.assertEqual(smart_text('foo'), 'foo') self.assertEqual(smart_str('foo'), 'foo')
def test_get_default_encoding(self): def test_get_default_encoding(self):
with mock.patch('locale.getdefaultlocale', side_effect=Exception): with mock.patch('locale.getdefaultlocale', side_effect=Exception):