Coverage for promplate/prompt/utils.py: 69%
102 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-09 22:54 +0800
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-09 22:54 +0800
1from functools import cache, cached_property, wraps
2from inspect import currentframe, isclass
3from pathlib import Path
4from re import compile
5from typing import Any, Callable, ParamSpec, TypeVar
7split_template_tokens = compile(
8 r"((?:\s{%-|{%).*?(?:%}|-%}\s))|((?:\s{{-|{{)[\s\S]*?(?:}}|-}}\s))|((?:\s{#-|{#)[\s\S]*?(?:#}|-#}\s))"
9).split
12var_name_checker = compile(r"[_a-zA-Z]\w*$")
14is_message_start = compile(r"<\|\s?(user|system|assistant)\s?(\w{1,64})?\s?\|>")
17def is_not_valid(name: str):
18 return not var_name_checker.match(name)
21def ensure_valid(name):
22 if is_not_valid(name):
23 raise NameError(name)
26class AutoNaming:
27 def __new__(cls, *args, **kwargs):
28 obj = super().__new__(cls)
29 obj._bind_frame()
30 return obj
32 def _bind_frame(self):
33 self._frame = currentframe()
35 @cached_property
36 def _name(self):
37 f = self._frame
38 if f and f.f_back and (frame := f.f_back.f_back): 38 ↛ exitline 38 didn't return from function '_name' because the condition on line 38 was always true
39 for name, var in frame.f_locals.items():
40 if var is self:
41 return name
43 @property
44 def class_name(self):
45 return self.__class__.__name__
47 fallback_name = class_name
49 @property
50 def name(self):
51 return self._name or self.fallback_name
53 @name.setter
54 def name(self, name):
55 self._name = name
56 self._frame = None
58 @name.deleter
59 def name(self):
60 del self._name
62 def __repr__(self):
63 if self._name:
64 return f"<{self.class_name} {self.name}>"
65 else:
66 return f"<{self.class_name}>"
68 def __str__(self):
69 return f"<{self.name}>"
72P = ParamSpec("P")
73T = TypeVar("T")
76def cache_once(func: Callable[P, T]) -> Callable[P, T]:
77 result = None
79 @wraps(func)
80 def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
81 nonlocal result
82 if result is None:
83 result = func(*args, **kwargs)
84 return result
86 return wrapper
89@cache_once
90def get_builtins() -> dict[str, Any]:
91 return __builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__
94@cache
95def version(package: str):
96 from importlib.metadata import PackageNotFoundError, version
98 try:
99 return version(package)
100 except PackageNotFoundError:
101 return None
104@cache_once
105def get_user_agent(self, *additional_packages: tuple[str, str]):
106 from sys import version as py_version
108 return " ".join(
109 (
110 f"Promplate/{version('promplate')} ({self.__name__ if isclass(self) else self.__class__.__name__})",
111 *(f"{name}/{v}" for name, v in additional_packages),
112 f"HTTPX/{version('httpx') or '-'}",
113 f"Python/{py_version.split()[0]}",
114 )
115 )
118@cache_once
119def _is_http2_available():
120 try:
121 import h2 # type: ignore
123 return True
124 except ImportError:
125 return False
128@cache_once
129def _get_client():
130 from httpx import Client
132 return Client(follow_redirects=True, http2=_is_http2_available())
135@cache_once
136def _get_aclient():
137 from httpx import AsyncClient
139 return AsyncClient(follow_redirects=True, http2=_is_http2_available())
142def add_linecache(filename: str, source_getter: Callable[[], str]):
143 import linecache
145 linecache.cache[filename] = (source_getter,)
148def save_tempfile(filename: str, source: str, auto_deletion: bool):
149 from tempfile import gettempdir, mkdtemp
151 for i in Path(gettempdir()).glob(f"promplate-*/{filename}"):
152 i.unlink()
154 file = Path(mkdtemp(prefix="promplate-")) / filename
155 file.write_text(source)
157 if auto_deletion: 157 ↛ 158line 157 didn't jump to line 158 because the condition on line 157 was never true
158 import atexit
160 @atexit.register
161 def _():
162 file.unlink(missing_ok=True)
163 file.parent.rmdir()
165 return file