Coverage for pydantic_graph/pydantic_graph/_utils.py: 91.18%

48 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-01-25 16:43 +0000

1from __future__ import annotations as _annotations 

2 

3import sys 

4import types 

5from datetime import datetime, timezone 

6from typing import Annotated, Any, TypeVar, Union, get_args, get_origin 

7 

8import typing_extensions 

9 

10 

11def get_union_args(tp: Any) -> tuple[Any, ...]: 

12 """Extract the arguments of a Union type if `response_type` is a union, otherwise return the original type.""" 

13 # similar to `pydantic_ai_slim/pydantic_ai/_result.py:get_union_args` 

14 if isinstance(tp, typing_extensions.TypeAliasType): 14 ↛ 15line 14 didn't jump to line 15 because the condition on line 14 was never true

15 tp = tp.__value__ 

16 

17 origin = get_origin(tp) 

18 if origin_is_union(origin): 

19 return get_args(tp) 

20 else: 

21 return (tp,) 

22 

23 

24def unpack_annotated(tp: Any) -> tuple[Any, list[Any]]: 

25 """Strip `Annotated` from the type if present. 

26 

27 Returns: 

28 `(tp argument, ())` if not annotated, otherwise `(stripped type, annotations)`. 

29 """ 

30 origin = get_origin(tp) 

31 if origin is Annotated or origin is typing_extensions.Annotated: 

32 inner_tp, *args = get_args(tp) 

33 return inner_tp, args 

34 else: 

35 return tp, [] 

36 

37 

38def is_never(tp: Any) -> bool: 

39 """Check if a type is `Never`.""" 

40 if tp is typing_extensions.Never: 

41 return True 

42 elif typing_never := getattr(typing_extensions, 'Never', None): 42 ↛ 45line 42 didn't jump to line 45 because the condition on line 42 was always true

43 return tp is typing_never 

44 else: 

45 return False 

46 

47 

48# same as `pydantic_ai_slim/pydantic_ai/_result.py:origin_is_union` 

49if sys.version_info < (3, 10): 

50 

51 def origin_is_union(tp: type[Any] | None) -> bool: 

52 return tp is Union 

53 

54else: 

55 

56 def origin_is_union(tp: type[Any] | None) -> bool: 

57 return tp is Union or tp is types.UnionType 

58 

59 

60def comma_and(items: list[str]) -> str: 

61 """Join with a comma and 'and' for the last item.""" 

62 if len(items) == 1: 

63 return items[0] 

64 else: 

65 # oxford comma ¯\_(ツ)_/¯ 

66 return ', '.join(items[:-1]) + ', and ' + items[-1] 

67 

68 

69def get_parent_namespace(frame: types.FrameType | None) -> dict[str, Any] | None: 

70 """Attempt to get the namespace where the graph was defined. 

71 

72 If the graph is defined with generics `Graph[a, b]` then another frame is inserted, and we have to skip that 

73 to get the correct namespace. 

74 """ 

75 if frame is not None: 75 ↛ exitline 75 didn't return from function 'get_parent_namespace' because the condition on line 75 was always true

76 if back := frame.f_back: 76 ↛ exitline 76 didn't return from function 'get_parent_namespace' because the condition on line 76 was always true

77 if back.f_code.co_filename.endswith('/typing.py'): 

78 return get_parent_namespace(back) 

79 else: 

80 return back.f_locals 

81 

82 

83def now_utc() -> datetime: 

84 return datetime.now(tz=timezone.utc) 

85 

86 

87class Unset: 

88 """A singleton to represent an unset value. 

89 

90 Copied from pydantic_ai/_utils.py. 

91 """ 

92 

93 pass 

94 

95 

96UNSET = Unset() 

97T = TypeVar('T') 

98 

99 

100def is_set(t_or_unset: T | Unset) -> typing_extensions.TypeGuard[T]: 

101 return t_or_unset is not UNSET