notpixel 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notpixel/__init__.py +71 -0
- notpixel/cache.py +91 -0
- notpixel/client.py +844 -0
- notpixel/compose.py +22 -0
- notpixel/embedding.py +123 -0
- notpixel/mock.py +51 -0
- notpixel/py.typed +0 -0
- notpixel/sanitize.py +19 -0
- notpixel/types.py +185 -0
- notpixel-0.1.0.dist-info/METADATA +113 -0
- notpixel-0.1.0.dist-info/RECORD +13 -0
- notpixel-0.1.0.dist-info/WHEEL +4 -0
- notpixel-0.1.0.dist-info/licenses/LICENSE +21 -0
notpixel/__init__.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from .cache import AdCache, generate_cache_key
|
|
2
|
+
from .client import Ads, AsyncAds
|
|
3
|
+
from .compose import compose_text, format_ad_block
|
|
4
|
+
from .embedding import (
|
|
5
|
+
DEFAULT_MODEL,
|
|
6
|
+
LocalEmbeddingResult,
|
|
7
|
+
generate_local_embedding,
|
|
8
|
+
get_model_info,
|
|
9
|
+
is_model_loaded,
|
|
10
|
+
preload_embedding_model,
|
|
11
|
+
unload_model,
|
|
12
|
+
)
|
|
13
|
+
from .mock import MOCK_ADS
|
|
14
|
+
from .sanitize import sanitize_and_truncate, sha256
|
|
15
|
+
from .types import (
|
|
16
|
+
AD_EXPIRATION_MS,
|
|
17
|
+
Ad,
|
|
18
|
+
AdCTA,
|
|
19
|
+
AdEmptyEvent,
|
|
20
|
+
AdEmptyReason,
|
|
21
|
+
AdErrorEvent,
|
|
22
|
+
AdFetchedEvent,
|
|
23
|
+
AdsBatchResult,
|
|
24
|
+
AnalyticsHooks,
|
|
25
|
+
CacheConfig,
|
|
26
|
+
FeedbackResponse,
|
|
27
|
+
PrefetchResult,
|
|
28
|
+
PrivacyConfig,
|
|
29
|
+
RespondResult,
|
|
30
|
+
TrackClickResponse,
|
|
31
|
+
TrackConversionResponse,
|
|
32
|
+
is_ad_expired,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.0"
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"AD_EXPIRATION_MS",
|
|
39
|
+
"DEFAULT_MODEL",
|
|
40
|
+
"MOCK_ADS",
|
|
41
|
+
"Ad",
|
|
42
|
+
"AdCTA",
|
|
43
|
+
"AdCache",
|
|
44
|
+
"AdEmptyEvent",
|
|
45
|
+
"AdEmptyReason",
|
|
46
|
+
"AdErrorEvent",
|
|
47
|
+
"AdFetchedEvent",
|
|
48
|
+
"Ads",
|
|
49
|
+
"AdsBatchResult",
|
|
50
|
+
"AnalyticsHooks",
|
|
51
|
+
"AsyncAds",
|
|
52
|
+
"CacheConfig",
|
|
53
|
+
"FeedbackResponse",
|
|
54
|
+
"LocalEmbeddingResult",
|
|
55
|
+
"PrefetchResult",
|
|
56
|
+
"PrivacyConfig",
|
|
57
|
+
"RespondResult",
|
|
58
|
+
"TrackClickResponse",
|
|
59
|
+
"TrackConversionResponse",
|
|
60
|
+
"compose_text",
|
|
61
|
+
"format_ad_block",
|
|
62
|
+
"generate_cache_key",
|
|
63
|
+
"generate_local_embedding",
|
|
64
|
+
"get_model_info",
|
|
65
|
+
"is_ad_expired",
|
|
66
|
+
"is_model_loaded",
|
|
67
|
+
"preload_embedding_model",
|
|
68
|
+
"sanitize_and_truncate",
|
|
69
|
+
"sha256",
|
|
70
|
+
"unload_model",
|
|
71
|
+
]
|
notpixel/cache.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Generic, TypeVar
|
|
7
|
+
|
|
8
|
+
from .sanitize import sha256
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class CacheEntry(Generic[T]):
|
|
15
|
+
value: T
|
|
16
|
+
expires_at: float
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AdCache(Generic[T]):
|
|
20
|
+
def __init__(self, ttl_ms: int = 5 * 60 * 1000, max_size: int = 100) -> None:
|
|
21
|
+
self._cache: dict[str, CacheEntry[T]] = {}
|
|
22
|
+
self.ttl_ms = ttl_ms
|
|
23
|
+
self.max_size = max_size
|
|
24
|
+
|
|
25
|
+
def get(self, key: str) -> T | None:
|
|
26
|
+
entry = self._cache.get(key)
|
|
27
|
+
if entry is None:
|
|
28
|
+
return None
|
|
29
|
+
if time.time() * 1000 > entry.expires_at:
|
|
30
|
+
self._cache.pop(key, None)
|
|
31
|
+
return None
|
|
32
|
+
return entry.value
|
|
33
|
+
|
|
34
|
+
def set(self, key: str, value: T) -> None:
|
|
35
|
+
if len(self._cache) >= self.max_size:
|
|
36
|
+
self.cleanup()
|
|
37
|
+
if len(self._cache) >= self.max_size and self._cache:
|
|
38
|
+
oldest_key = next(iter(self._cache))
|
|
39
|
+
self._cache.pop(oldest_key, None)
|
|
40
|
+
self._cache[key] = CacheEntry(
|
|
41
|
+
value=value,
|
|
42
|
+
expires_at=time.time() * 1000 + self.ttl_ms,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def delete(self, key: str) -> bool:
|
|
46
|
+
return self._cache.pop(key, None) is not None
|
|
47
|
+
|
|
48
|
+
def clear(self) -> None:
|
|
49
|
+
self._cache.clear()
|
|
50
|
+
|
|
51
|
+
def cleanup(self) -> int:
|
|
52
|
+
now = time.time() * 1000
|
|
53
|
+
expired = [k for k, v in self._cache.items() if now > v.expires_at]
|
|
54
|
+
for k in expired:
|
|
55
|
+
self._cache.pop(k, None)
|
|
56
|
+
return len(expired)
|
|
57
|
+
|
|
58
|
+
def stats(self) -> dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"size": len(self._cache),
|
|
61
|
+
"maxSize": self.max_size,
|
|
62
|
+
"ttlMs": self.ttl_ms,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def generate_cache_key(
|
|
67
|
+
publisher_id: str | None = None,
|
|
68
|
+
input_text: str | None = None,
|
|
69
|
+
mode: str | None = None,
|
|
70
|
+
embedding: list[float] | None = None,
|
|
71
|
+
context: dict[str, Any] | None = None,
|
|
72
|
+
request_id: str | None = None,
|
|
73
|
+
) -> str:
|
|
74
|
+
embedding_key = (
|
|
75
|
+
sha256(",".join(f"{n:.6f}" for n in embedding))
|
|
76
|
+
if embedding
|
|
77
|
+
else ""
|
|
78
|
+
)
|
|
79
|
+
parts = [
|
|
80
|
+
publisher_id or "",
|
|
81
|
+
mode or "standard",
|
|
82
|
+
input_text or embedding_key or "",
|
|
83
|
+
]
|
|
84
|
+
if context and len(context) > 0:
|
|
85
|
+
sorted_ctx = sorted(context.items(), key=lambda item: item[0])
|
|
86
|
+
parts.append(sha256(json.dumps(sorted_ctx, separators=(",", ":"))))
|
|
87
|
+
else:
|
|
88
|
+
parts.append("")
|
|
89
|
+
parts.append(sha256(request_id) if request_id else "")
|
|
90
|
+
# Fixed positions distinguish context, opportunity IDs and delimiters in input.
|
|
91
|
+
return json.dumps(parts, separators=(",", ":"))
|