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
« prev ^ index » next coverage.py v7.6.7, created at 2025-01-25 16:43 +0000
1from __future__ import annotations as _annotations
3import sys
4import types
5from datetime import datetime, timezone
6from typing import Annotated, Any, TypeVar, Union, get_args, get_origin
8import typing_extensions
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__
17 origin = get_origin(tp)
18 if origin_is_union(origin):
19 return get_args(tp)
20 else:
21 return (tp,)
24def unpack_annotated(tp: Any) -> tuple[Any, list[Any]]:
25 """Strip `Annotated` from the type if present.
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, []
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
48# same as `pydantic_ai_slim/pydantic_ai/_result.py:origin_is_union`
49if sys.version_info < (3, 10):
51 def origin_is_union(tp: type[Any] | None) -> bool:
52 return tp is Union
54else:
56 def origin_is_union(tp: type[Any] | None) -> bool:
57 return tp is Union or tp is types.UnionType
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]
69def get_parent_namespace(frame: types.FrameType | None) -> dict[str, Any] | None:
70 """Attempt to get the namespace where the graph was defined.
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
83def now_utc() -> datetime:
84 return datetime.now(tz=timezone.utc)
87class Unset:
88 """A singleton to represent an unset value.
90 Copied from pydantic_ai/_utils.py.
91 """
93 pass
96UNSET = Unset()
97T = TypeVar('T')
100def is_set(t_or_unset: T | Unset) -> typing_extensions.TypeGuard[T]:
101 return t_or_unset is not UNSET