Skip to content

Commit

Permalink
feat(typing): type util package (#2170)
Browse files Browse the repository at this point in the history
* feat: Add typing to util misc module

* feat: Add typing to util reader module

* feat: Type util structures module

* feat: Added typing to util.sync

* feat: Added typing to util.uri

* feat: Added typing util deprecation module

* feat: Add typing to util package

* feat: Disallow untyped defs in utils package

* feat: Simplify typing of is_python_func

* feat: Added future annotations to reader module

* feat: Simplified CaseInsensitiveDict init typing

* feat: Join imports

* feat: Use Result type var instead of Any
  • Loading branch information
copalco authored Aug 21, 2023
1 parent f00722a commit 32207fe
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 118 deletions.
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]:

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

0 comments on commit 32207fe

Please sign in to comment.