Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typing): type util package #2170

Merged
merged 13 commits into from
Aug 21, 2023
4 changes: 2 additions & 2 deletions falcon/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from http import cookies as http_cookies
import sys
from types import ModuleType

# Hoist misc. utils
from falcon.constants import PYTHON_VERSION
Expand Down Expand Up @@ -77,7 +78,7 @@
)


def __getattr__(name):
def __getattr__(name: str) -> ModuleType:
if name == 'json':
import warnings
import json # NOQA
Expand All @@ -86,7 +87,6 @@ def __getattr__(name):
'Importing json from "falcon.util" is deprecated.', DeprecatedWarning
)
return json
from types import ModuleType

# fallback to the default implementation
mod = sys.modules[__name__]
Expand Down
19 changes: 13 additions & 6 deletions falcon/util/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"""

import functools
from typing import Any
from typing import Callable
from typing import Optional
import warnings


Expand All @@ -41,7 +44,9 @@ class DeprecatedWarning(UserWarning):
pass


def deprecated(instructions, is_property=False, method_name=None):
def deprecated(
instructions: str, is_property: bool = False, method_name: Optional[str] = None
) -> Callable[[Callable[..., Any]], Any]:
"""Flag a method as deprecated.

This function returns a decorator which can be used to mark deprecated
Expand All @@ -60,7 +65,7 @@ def deprecated(instructions, is_property=False, method_name=None):

"""

def decorator(func):
def decorator(func: Callable[..., Any]) -> Callable[[Callable[..., Any]], Any]:
copalco marked this conversation as resolved.
Show resolved Hide resolved

object_name = 'property' if is_property else 'function'
post_name = '' if is_property else '(...)'
Expand All @@ -69,7 +74,7 @@ def decorator(func):
)

@functools.wraps(func)
def wrapper(*args, **kwargs):
def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
warnings.warn(message, category=DeprecatedWarning, stacklevel=2)

return func(*args, **kwargs)
Expand All @@ -79,7 +84,9 @@ def wrapper(*args, **kwargs):
return decorator


def deprecated_args(*, allowed_positional, is_method=True):
def deprecated_args(
*, allowed_positional: int, is_method: bool = True
) -> Callable[..., Callable[..., Any]]:
"""Flag a method call with positional args as deprecated.

Keyword Args:
Expand All @@ -98,9 +105,9 @@ def deprecated_args(*, allowed_positional, is_method=True):
if is_method:
allowed_positional += 1

def deprecated_args(fn):
def deprecated_args(fn: Callable[..., Any]) -> Callable[..., Callable[..., Any]]:
@functools.wraps(fn)
def wraps(*args, **kwargs):
def wraps(*args: Any, **kwargs: Any) -> Callable[..., Any]:
if len(args) > allowed_positional:
warnings.warn(
warn_text.format(fn=fn.__qualname__),
Expand Down
35 changes: 21 additions & 14 deletions falcon/util/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@

now = falcon.http_now()
"""

import datetime
import functools
import http
import inspect
import re
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Tuple
from typing import Union
import unicodedata

from falcon import status_codes
Expand Down Expand Up @@ -69,18 +74,18 @@
_UNSAFE_CHARS = re.compile(r'[^a-zA-Z0-9.-]')

# PERF(kgriffs): Avoid superfluous namespace lookups
strptime = datetime.datetime.strptime
utcnow = datetime.datetime.utcnow
strptime: Callable[[str, str], datetime.datetime] = datetime.datetime.strptime
utcnow: Callable[[], datetime.datetime] = datetime.datetime.utcnow


# NOTE(kgriffs,vytas): This is tested in the PyPy gate but we do not want devs
# to have to install PyPy to check coverage on their workstations, so we use
# the nocover pragma here.
def _lru_cache_nop(*args, **kwargs): # pragma: nocover
def decorator(func):
def _lru_cache_nop(maxsize: int) -> Callable[[Callable], Callable]: # pragma: nocover
def decorator(func: Callable) -> Callable:
# NOTE(kgriffs): Partially emulate the lru_cache protocol; only add
# cache_info() later if/when it becomes necessary.
func.cache_clear = lambda: None
func.cache_clear = lambda: None # type: ignore

return func

Expand All @@ -95,7 +100,7 @@ def decorator(func):
_lru_cache_for_simple_logic = functools.lru_cache # type: ignore


def is_python_func(func):
def is_python_func(func: Union[Callable, Any]) -> bool:
"""Determine if a function or method uses a standard Python type.

This helper can be used to check a function or method to determine if it
Expand Down Expand Up @@ -251,7 +256,7 @@ def to_query_str(
return query_str[:-1]


def get_bound_method(obj, method_name):
def get_bound_method(obj: object, method_name: str) -> Union[None, Callable[..., Any]]:
"""Get a bound method of the given object by name.

Args:
Expand All @@ -278,7 +283,7 @@ def get_bound_method(obj, method_name):
return method


def get_argnames(func):
def get_argnames(func: Callable) -> List[str]:
"""Introspect the arguments of a callable.

Args:
Expand Down Expand Up @@ -308,7 +313,9 @@ def get_argnames(func):


@deprecated('Please use falcon.code_to_http_status() instead.')
def get_http_status(status_code, default_reason=_DEFAULT_HTTP_REASON):
def get_http_status(
status_code: Union[str, int], default_reason: str = _DEFAULT_HTTP_REASON
) -> str:
"""Get both the http status code and description from just a code.

Warning:
Expand Down Expand Up @@ -387,7 +394,7 @@ def secure_filename(filename: str) -> str:


@_lru_cache_for_simple_logic(maxsize=64)
def http_status_to_code(status):
def http_status_to_code(status: Union[http.HTTPStatus, int, bytes, str]) -> int:
"""Normalize an HTTP status to an integer code.

This function takes a member of :class:`http.HTTPStatus`, an HTTP status
Expand Down Expand Up @@ -425,7 +432,7 @@ def http_status_to_code(status):


@_lru_cache_for_simple_logic(maxsize=64)
def code_to_http_status(status):
def code_to_http_status(status: Union[int, http.HTTPStatus, bytes, str]) -> str:
"""Normalize an HTTP status to an HTTP status line string.

This function takes a member of :class:`http.HTTPStatus`, an ``int`` status
Expand Down Expand Up @@ -473,7 +480,7 @@ def code_to_http_status(status):
return '{} {}'.format(code, _DEFAULT_HTTP_REASON)


def _encode_items_to_latin1(data):
def _encode_items_to_latin1(data: Dict[str, str]) -> List[Tuple[bytes, bytes]]:
"""Decode all key/values of a dict to Latin-1.

Args:
Expand All @@ -491,7 +498,7 @@ def _encode_items_to_latin1(data):
return result


def _isascii(string: str):
def _isascii(string: str) -> bool:
"""Return ``True`` if all characters in the string are ASCII.

ASCII characters have code points in the range U+0000-U+007F.
Expand Down
Loading
Loading