Skip to content

Commit

Permalink
chore(helpers): privatize helper methods intended only for internal u…
Browse files Browse the repository at this point in the history
…se (#2341)

* chore(helpers): privatize internal helper methods

* chore(helpers): privatize ASGI _header_property even more

* chore(newsfragments): use the correct issue number

* chore: clean up newsfragment for privatization of helpers
  • Loading branch information
vytas7 authored Sep 26, 2024
1 parent 08ee3c0 commit f843b4f
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 73 deletions.
25 changes: 25 additions & 0 deletions docs/_newsfragments/1457.breakingchange.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
A number of undocumented internal helpers were renamed to start with an
underscore, indicating they are private methods intended to be used only by the
framework itself:

* ``falcon.request_helpers.header_property`` →
``falcon.request_helpers._header_property``
* ``falcon.request_helpers.parse_cookie_header`` →
``falcon.request_helpers._parse_cookie_header``
* ``falcon.response_helpers.format_content_disposition`` →
``falcon.response_helpers._format_content_disposition``
* ``falcon.response_helpers.format_etag_header`` →
``falcon.response_helpers._format_etag_header``
* ``falcon.response_helpers.format_header_value_list`` →
``falcon.response_helpers._format_header_value_list``
* ``falcon.response_helpers.format_range`` →
``falcon.response_helpers._format_range``
* ``falcon.response_helpers.header_property`` →
``falcon.response_helpers._header_property``
* ``falcon.response_helpers.is_ascii_encodable`` →
``falcon.response_helpers._is_ascii_encodable``

If you were relying on these internal helpers, you can either copy the
implementation into your codebase, or switch to the underscored variants.
(Needless to say, though, we strongly recommend against referencing private
methods, as we provide no SemVer guarantees for them.)
8 changes: 8 additions & 0 deletions docs/_newsfragments/1853.breakingchange.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ removed:
* The ``falcon-print-routes`` command-line utility is no longer supported;
``falcon-inspect-app`` is a direct replacement.

* :class:`falcon.stream.BoundedStream` is no longer re-imported via
``falcon.request_helpers``.
If needed, import it directly as :class:`falcon.stream.BoundedStream`.

* A deprecated alias of :class:`falcon.stream.BoundedStream`,
``falcon.stream.Body``, was removed. Use :class:`falcon.stream.BoundedStream`
instead.

* A deprecated utility function, ``falcon.get_http_status()``, was removed.
Please use :meth:`falcon.code_to_http_status` instead.

Expand Down
2 changes: 1 addition & 1 deletion falcon/asgi/_request_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from falcon.asgi import Request


def header_property(header_name: str) -> Any:
def _header_property(header_name: str) -> Any:
"""Create a read-only header property.
Args:
Expand Down
10 changes: 5 additions & 5 deletions falcon/asgi/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,11 @@ def __init__(
# trouble.
# ------------------------------------------------------------------------

auth: Optional[str] = asgi_helpers.header_property('Authorization')
expect: Optional[str] = asgi_helpers.header_property('Expect')
if_range: Optional[str] = asgi_helpers.header_property('If-Range')
referer: Optional[str] = asgi_helpers.header_property('Referer')
user_agent: Optional[str] = asgi_helpers.header_property('User-Agent')
auth: Optional[str] = asgi_helpers._header_property('Authorization')
expect: Optional[str] = asgi_helpers._header_property('Expect')
if_range: Optional[str] = asgi_helpers._header_property('If-Range')
referer: Optional[str] = asgi_helpers._header_property('Referer')
user_agent: Optional[str] = asgi_helpers._header_property('User-Agent')

@property
def accept(self) -> str:
Expand Down
14 changes: 7 additions & 7 deletions falcon/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,15 +345,15 @@ def __repr__(self) -> str:
# Properties
# ------------------------------------------------------------------------

user_agent: Optional[str] = helpers.header_property('HTTP_USER_AGENT')
user_agent: Optional[str] = helpers._header_property('HTTP_USER_AGENT')
"""Value of the User-Agent header, or ``None`` if the header is missing."""
auth: Optional[str] = helpers.header_property('HTTP_AUTHORIZATION')
auth: Optional[str] = helpers._header_property('HTTP_AUTHORIZATION')
"""Value of the Authorization header, or ``None`` if the header is missing."""
expect: Optional[str] = helpers.header_property('HTTP_EXPECT')
expect: Optional[str] = helpers._header_property('HTTP_EXPECT')
"""Value of the Expect header, or ``None`` if the header is missing."""
if_range: Optional[str] = helpers.header_property('HTTP_IF_RANGE')
if_range: Optional[str] = helpers._header_property('HTTP_IF_RANGE')
"""Value of the If-Range header, or ``None`` if the header is missing."""
referer: Optional[str] = helpers.header_property('HTTP_REFERER')
referer: Optional[str] = helpers._header_property('HTTP_REFERER')
"""Value of the Referer header, or ``None`` if the header is missing."""

@property
Expand Down Expand Up @@ -921,7 +921,7 @@ def cookies(self) -> Mapping[str, str]:
if self._cookies is None:
header_value = self.get_header('Cookie')
if header_value:
self._cookies = helpers.parse_cookie_header(header_value)
self._cookies = helpers._parse_cookie_header(header_value)
else:
self._cookies = {}

Expand Down Expand Up @@ -1365,7 +1365,7 @@ def get_cookie_values(self, name: str) -> Optional[List[str]]:
# point.
header_value = self.get_header('Cookie')
if header_value:
self._cookies = helpers.parse_cookie_header(header_value)
self._cookies = helpers._parse_cookie_header(header_value)
else:
self._cookies = {}

Expand Down
8 changes: 2 additions & 6 deletions falcon/request_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
import re
from typing import Any, Dict, List, Literal, Optional, TYPE_CHECKING, Union

# TODO: Body, BoundedStream import here is for backwards-compatibility
# and it should be removed in Falcon 4.0
from falcon.stream import Body # NOQA
from falcon.stream import BoundedStream # NOQA
from falcon.util import ETag

if TYPE_CHECKING:
Expand All @@ -46,7 +42,7 @@
_ENTITY_TAG_PATTERN = re.compile(r'([Ww]/)?"([^"]*)"')


def parse_cookie_header(header_value: str) -> Dict[str, List[str]]:
def _parse_cookie_header(header_value: str) -> Dict[str, List[str]]:
"""Parse a Cookie header value into a dict of named values.
(See also: RFC 6265, Section 5.4)
Expand Down Expand Up @@ -107,7 +103,7 @@ def parse_cookie_header(header_value: str) -> Dict[str, List[str]]:
return cookies


def header_property(wsgi_name: str) -> Any:
def _header_property(wsgi_name: str) -> Any:
"""Create a read-only header property.
Args:
Expand Down
56 changes: 28 additions & 28 deletions falcon/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@
from falcon.constants import DEFAULT_MEDIA_TYPE
from falcon.errors import HeaderNotSupported
from falcon.media import Handlers
from falcon.response_helpers import format_content_disposition
from falcon.response_helpers import format_etag_header
from falcon.response_helpers import format_header_value_list
from falcon.response_helpers import format_range
from falcon.response_helpers import header_property
from falcon.response_helpers import is_ascii_encodable
from falcon.response_helpers import _format_content_disposition
from falcon.response_helpers import _format_etag_header
from falcon.response_helpers import _format_header_value_list
from falcon.response_helpers import _format_range
from falcon.response_helpers import _header_property
from falcon.response_helpers import _is_ascii_encodable
from falcon.typing import Headers
from falcon.typing import MISSING
from falcon.typing import MissingOr
Expand Down Expand Up @@ -471,9 +471,9 @@ def set_cookie( # noqa: C901
"""

if not is_ascii_encodable(name):
if not _is_ascii_encodable(name):
raise KeyError('name is not ascii encodable')
if not is_ascii_encodable(value):
if not _is_ascii_encodable(value):
raise ValueError('value is not ascii encodable')

value = str(value)
Expand Down Expand Up @@ -975,15 +975,15 @@ def add_link(self) -> NoReturn:
'Please use append_link() instead.'
)

cache_control: Union[str, Iterable[str], None] = header_property(
cache_control: Union[str, Iterable[str], None] = _header_property(
'Cache-Control',
"""Set the Cache-Control header.
Used to set a list of cache directives to use as the value of the
Cache-Control header. The list will be joined with ", " to produce
the value for the header.
""",
format_header_value_list,
_format_header_value_list,
)
"""Set the Cache-Control header.
Expand All @@ -992,7 +992,7 @@ def add_link(self) -> NoReturn:
the value for the header.
"""

content_location: Optional[str] = header_property(
content_location: Optional[str] = _header_property(
'Content-Location',
"""Set the Content-Location header.
Expand All @@ -1009,7 +1009,7 @@ def add_link(self) -> NoReturn:
header should be set manually using the set_header method.
"""

content_length: Union[str, int, None] = header_property(
content_length: Union[str, int, None] = _header_property(
'Content-Length',
"""Set the Content-Length header.
Expand Down Expand Up @@ -1047,7 +1047,7 @@ def add_link(self) -> NoReturn:
"""

content_range: Union[str, RangeSetHeader, None] = header_property(
content_range: Union[str, RangeSetHeader, None] = _header_property(
'Content-Range',
"""A tuple to use in constructing a value for the Content-Range header.
Expand All @@ -1065,7 +1065,7 @@ def add_link(self) -> NoReturn:
(See also: RFC 7233, Section 4.2)
""",
format_range,
_format_range,
)
"""A tuple to use in constructing a value for the Content-Range header.
Expand All @@ -1084,7 +1084,7 @@ def add_link(self) -> NoReturn:
(See also: RFC 7233, Section 4.2)
"""

content_type: Optional[str] = header_property(
content_type: Optional[str] = _header_property(
'Content-Type',
"""Sets the Content-Type header.
Expand All @@ -1108,7 +1108,7 @@ def add_link(self) -> NoReturn:
and ``falcon.MEDIA_GIF``.
"""

downloadable_as: Optional[str] = header_property(
downloadable_as: Optional[str] = _header_property(
'Content-Disposition',
"""Set the Content-Disposition header using the given filename.
Expand All @@ -1121,7 +1121,7 @@ def add_link(self) -> NoReturn:
``filename*`` directive, whereas ``filename`` will contain the US
ASCII fallback.
""",
functools.partial(format_content_disposition, disposition_type='attachment'),
functools.partial(_format_content_disposition, disposition_type='attachment'),
)
"""Set the Content-Disposition header using the given filename.
Expand All @@ -1135,7 +1135,7 @@ def add_link(self) -> NoReturn:
ASCII fallback.
"""

viewable_as: Optional[str] = header_property(
viewable_as: Optional[str] = _header_property(
'Content-Disposition',
"""Set an inline Content-Disposition header using the given filename.
Expand All @@ -1150,7 +1150,7 @@ def add_link(self) -> NoReturn:
.. versionadded:: 3.1
""",
functools.partial(format_content_disposition, disposition_type='inline'),
functools.partial(_format_content_disposition, disposition_type='inline'),
)
"""Set an inline Content-Disposition header using the given filename.
Expand All @@ -1166,22 +1166,22 @@ def add_link(self) -> NoReturn:
.. versionadded:: 3.1
"""

etag: Optional[str] = header_property(
etag: Optional[str] = _header_property(
'ETag',
"""Set the ETag header.
The ETag header will be wrapped with double quotes ``"value"`` in case
the user didn't pass it.
""",
format_etag_header,
_format_etag_header,
)
"""Set the ETag header.
The ETag header will be wrapped with double quotes ``"value"`` in case
the user didn't pass it.
"""

expires: Union[str, datetime, None] = header_property(
expires: Union[str, datetime, None] = _header_property(
'Expires',
"""Set the Expires header. Set to a ``datetime`` (UTC) instance.
Expand All @@ -1196,7 +1196,7 @@ def add_link(self) -> NoReturn:
Falcon will format the ``datetime`` as an HTTP date string.
"""

last_modified: Union[str, datetime, None] = header_property(
last_modified: Union[str, datetime, None] = _header_property(
'Last-Modified',
"""Set the Last-Modified header. Set to a ``datetime`` (UTC) instance.
Expand All @@ -1211,7 +1211,7 @@ def add_link(self) -> NoReturn:
Falcon will format the ``datetime`` as an HTTP date string.
"""

location: Optional[str] = header_property(
location: Optional[str] = _header_property(
'Location',
"""Set the Location header.
Expand All @@ -1228,7 +1228,7 @@ def add_link(self) -> NoReturn:
header should be set manually using the set_header method.
"""

retry_after: Union[int, str, None] = header_property(
retry_after: Union[int, str, None] = _header_property(
'Retry-After',
"""Set the Retry-After header.
Expand All @@ -1242,7 +1242,7 @@ def add_link(self) -> NoReturn:
value for the header. The HTTP-date syntax is not supported.
"""

vary: Union[str, Iterable[str], None] = header_property(
vary: Union[str, Iterable[str], None] = _header_property(
'Vary',
"""Value to use for the Vary header.
Expand All @@ -1259,7 +1259,7 @@ def add_link(self) -> NoReturn:
(See also: RFC 7231, Section 7.1.4)
""",
format_header_value_list,
_format_header_value_list,
)
"""Value to use for the Vary header.
Expand All @@ -1277,7 +1277,7 @@ def add_link(self) -> NoReturn:
(See also: RFC 7231, Section 7.1.4)
"""

accept_ranges: Optional[str] = header_property(
accept_ranges: Optional[str] = _header_property(
'Accept-Ranges',
"""Set the Accept-Ranges header.
Expand Down
14 changes: 8 additions & 6 deletions falcon/response_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from falcon import Response


def header_property(
def _header_property(
name: str, doc: str, transform: Optional[Callable[[Any], str]] = None
) -> Any:
"""Create a header getter/setter.
Expand Down Expand Up @@ -76,7 +76,7 @@ def fdel(self: Response) -> None:
return property(fget, fset, fdel, doc)


def format_range(value: RangeSetHeader) -> str:
def _format_range(value: RangeSetHeader) -> str:
"""Format a range header tuple per the HTTP spec.
Args:
Expand All @@ -90,7 +90,9 @@ def format_range(value: RangeSetHeader) -> str:
return result


def format_content_disposition(value: str, disposition_type: str = 'attachment') -> str:
def _format_content_disposition(
value: str, disposition_type: str = 'attachment'
) -> str:
"""Format a Content-Disposition header given a filename."""

# NOTE(vytas): RFC 6266, Appendix D.
Expand All @@ -117,7 +119,7 @@ def format_content_disposition(value: str, disposition_type: str = 'attachment')
)


def format_etag_header(value: str) -> str:
def _format_etag_header(value: str) -> str:
"""Format an ETag header, wrap it with " " in case of need."""

if value[-1] != '"':
Expand All @@ -126,12 +128,12 @@ def format_etag_header(value: str) -> str:
return value


def format_header_value_list(iterable: Iterable[str]) -> str:
def _format_header_value_list(iterable: Iterable[str]) -> str:
"""Join an iterable of strings with commas."""
return ', '.join(iterable)


def is_ascii_encodable(s: str) -> bool:
def _is_ascii_encodable(s: str) -> bool:
"""Check if argument encodes to ascii without error."""
try:
s.encode('ascii')
Expand Down
Loading

0 comments on commit f843b4f

Please sign in to comment.