Coverage for src/gentrie/validation.py: 100%

1# -*- coding: utf-8 -*-

2

3"""Validation functions for the gentrie package."""

4

5 from collections.abc import Sequence

6 from typing import Any

7 from warnings import warn

8

9 from .protocols import TrieKeyToken

10

11

12 def is_triekeytoken(token: Any) -> bool:

13 """Tests token for whether it is a valid :class:`TrieKeyToken`.

14

15 A valid :class:`TrieKeyToken` is a hashable object (implements both ``__eq__()`` and ``__hash__()`` methods).

16

17 Examples:

18 :class:`bool`, :class:`bytes`, :class:`float`, :class:`frozenset`,

19 :class:`int`, :class:`str`, :class:`None`, :class:`tuple`.

20

21 Args:

22 token (Any): Object for testing.

23

24 Returns:

25 :class:`bool`: ``True`` if a valid :class:`TrieKeyToken`, ``False`` otherwise.

26 """

27 return isinstance(token, TrieKeyToken)

28

29

30 def is_hashable(token: Any) -> bool:

31 """is_hashable is deprecated and will be removed in a future version.

32

33 This function is a wrapper for :func:`is_triekeytoken` and is only provided for backward compatibility.

34

35 Use :func:`is_triekeytoken` instead.

36 """

37 warn(

38 "is_hashable is deprecated and will be removed in a future version. Use is_triekeytoken instead.",

39 DeprecationWarning,

40 stacklevel=2

41 )

42 return is_triekeytoken(token)

43

44

45 def is_generalizedkey(key: Any) -> bool:

46 """Tests key for whether it is a valid `GeneralizedKey`.

47

48 A valid :class:`GeneralizedKey` is a :class:`Sequence` that returns

49 :class:`TrieKeyToken` protocol conformant objects when

50 iterated. It must have at least one token.

51

52 Parameters:

53 key (Any): Key for testing.

54

55 Returns:

56 :class:`bool`: ``True`` if a valid :class:`GeneralizedKey`, ``False`` otherwise.

57 """

58 # This complex logic makes this hotpath code MUCH faster (as much as 50 or 60 times) by performing

59 # very fast dedicated checks for common types before performing much slower per-token generalized

60 # protocol based checks.

61

62 # Fast path 1: A non-empty string or bytes is a valid key.

63 if isinstance(key, (str, bytes)) and key:

64 return True

65

66 # General check: Must be a sequence.

67 if not isinstance(key, Sequence):

68 return False

69

70 # Now that we know it's a sequence, we can safely check its length.

71 # An empty sequence is not a valid key.

72 if not key:

73 return False

74

75 # Fast path 2: Check for sequences of common, simple built-in types.

76 # This is much faster than the general protocol check.

77 if all(isinstance(t, (int, float, complex, frozenset, tuple, bool, str, bytes)

78 ) for t in key): # pyright: ignore[reportUnknownVariableType]

79 return True

80

81 # Fallback/Cold path: Perform the slower, general protocol check.

82 # This only runs if the fast path fails.

83 return all(isinstance(t, TrieKeyToken) for t in key) # pyright: ignore[reportUnknownVariableType]