_query.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """Query string handling."""
  2. import math
  3. from collections.abc import Iterable, Mapping, Sequence
  4. from typing import Any, SupportsInt, Union
  5. from multidict import istr
  6. from ._quoters import QUERY_PART_QUOTER, QUERY_QUOTER
  7. SimpleQuery = Union[str, SupportsInt, float]
  8. QueryVariable = Union[SimpleQuery, Sequence[SimpleQuery]]
  9. Query = Union[
  10. None, str, Mapping[str, QueryVariable], Sequence[tuple[str, QueryVariable]]
  11. ]
  12. def query_var(v: SimpleQuery) -> str:
  13. """Convert a query variable to a string."""
  14. cls = type(v)
  15. if cls is int: # Fast path for non-subclassed int
  16. return str(v)
  17. if isinstance(v, str):
  18. return v
  19. if isinstance(v, float):
  20. if math.isinf(v):
  21. raise ValueError("float('inf') is not supported")
  22. if math.isnan(v):
  23. raise ValueError("float('nan') is not supported")
  24. return str(float(v))
  25. if cls is not bool and isinstance(v, SupportsInt):
  26. return str(int(v))
  27. raise TypeError(
  28. "Invalid variable type: value "
  29. "should be str, int or float, got {!r} "
  30. "of type {}".format(v, cls)
  31. )
  32. def get_str_query_from_sequence_iterable(
  33. items: Iterable[tuple[Union[str, istr], QueryVariable]],
  34. ) -> str:
  35. """Return a query string from a sequence of (key, value) pairs.
  36. value is a single value or a sequence of values for the key
  37. The sequence of values must be a list or tuple.
  38. """
  39. quoter = QUERY_PART_QUOTER
  40. pairs = [
  41. f"{quoter(k)}={quoter(v if type(v) is str else query_var(v))}"
  42. for k, val in items
  43. for v in (
  44. val if type(val) is not str and isinstance(val, (list, tuple)) else (val,)
  45. )
  46. ]
  47. return "&".join(pairs)
  48. def get_str_query_from_iterable(
  49. items: Iterable[tuple[Union[str, istr], SimpleQuery]]
  50. ) -> str:
  51. """Return a query string from an iterable.
  52. The iterable must contain (key, value) pairs.
  53. The values are not allowed to be sequences, only single values are
  54. allowed. For sequences, use `_get_str_query_from_sequence_iterable`.
  55. """
  56. quoter = QUERY_PART_QUOTER
  57. # A listcomp is used since listcomps are inlined on CPython 3.12+ and
  58. # they are a bit faster than a generator expression.
  59. pairs = [
  60. f"{quoter(k)}={quoter(v if type(v) is str else query_var(v))}" for k, v in items
  61. ]
  62. return "&".join(pairs)
  63. def get_str_query(*args: Any, **kwargs: Any) -> Union[str, None]:
  64. """Return a query string from supported args."""
  65. query: Union[str, Mapping[str, QueryVariable], None]
  66. if kwargs:
  67. if args:
  68. msg = "Either kwargs or single query parameter must be present"
  69. raise ValueError(msg)
  70. query = kwargs
  71. elif len(args) == 1:
  72. query = args[0]
  73. else:
  74. raise ValueError("Either kwargs or single query parameter must be present")
  75. if query is None:
  76. return None
  77. if not query:
  78. return ""
  79. if type(query) is dict:
  80. return get_str_query_from_sequence_iterable(query.items())
  81. if type(query) is str or isinstance(query, str):
  82. return QUERY_QUOTER(query)
  83. if isinstance(query, Mapping):
  84. return get_str_query_from_sequence_iterable(query.items())
  85. if isinstance(query, (bytes, bytearray, memoryview)): # type: ignore[unreachable]
  86. msg = "Invalid query type: bytes, bytearray and memoryview are forbidden"
  87. raise TypeError(msg)
  88. if isinstance(query, Sequence):
  89. # We don't expect sequence values if we're given a list of pairs
  90. # already; only mappings like builtin `dict` which can't have the
  91. # same key pointing to multiple values are allowed to use
  92. # `_query_seq_pairs`.
  93. return get_str_query_from_iterable(query)
  94. raise TypeError(
  95. "Invalid query type: only str, mapping or "
  96. "sequence of (key, value) pairs is allowed"
  97. )