haystack-ml-stack 0.2.4__tar.gz → 0.2.5__tar.gz
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.
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/PKG-INFO +1 -1
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/pyproject.toml +1 -1
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/__init__.py +1 -1
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/app.py +56 -26
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/cache.py +2 -2
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/dynamo.py +22 -22
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/PKG-INFO +1 -1
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/README.md +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/setup.cfg +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/model_store.py +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/settings.py +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack/utils.py +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/SOURCES.txt +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/dependency_links.txt +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/requires.txt +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/top_level.txt +0 -0
- {haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/tests/test_utils.py +0 -0
|
@@ -5,8 +5,10 @@ import sys
|
|
|
5
5
|
from http import HTTPStatus
|
|
6
6
|
from typing import Any, Dict, List, Optional
|
|
7
7
|
import time
|
|
8
|
+
from contextlib import asynccontextmanager, AsyncExitStack
|
|
8
9
|
|
|
9
10
|
import aiobotocore.session
|
|
11
|
+
from aiobotocore.config import AioConfig
|
|
10
12
|
from fastapi import FastAPI, HTTPException, Request, Response
|
|
11
13
|
from fastapi.encoders import jsonable_encoder
|
|
12
14
|
import newrelic.agent
|
|
@@ -25,7 +27,7 @@ logging.basicConfig(
|
|
|
25
27
|
)
|
|
26
28
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
|
-
|
|
30
|
+
MAX_POOL_CONNECTIONS = int(os.environ.get("MAX_POOL_CONNECTIONS", 50))
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
def create_app(
|
|
@@ -39,12 +41,6 @@ def create_app(
|
|
|
39
41
|
"""
|
|
40
42
|
cfg = settings or Settings()
|
|
41
43
|
|
|
42
|
-
app = FastAPI(
|
|
43
|
-
title="ML Stream Scorer",
|
|
44
|
-
description="Scores video streams using a pre-trained ML model and DynamoDB features.",
|
|
45
|
-
version="1.0.0",
|
|
46
|
-
)
|
|
47
|
-
|
|
48
44
|
# Mutable state: cache + model
|
|
49
45
|
features_cache = make_features_cache(cfg.cache_maxsize)
|
|
50
46
|
state: Dict[str, Any] = {
|
|
@@ -55,24 +51,59 @@ def create_app(
|
|
|
55
51
|
),
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
@
|
|
59
|
-
async def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
@asynccontextmanager
|
|
55
|
+
async def lifespan(app_server: FastAPI):
|
|
56
|
+
"""
|
|
57
|
+
Handles startup and shutdown logic.
|
|
58
|
+
Everything before 'yield' runs on startup.
|
|
59
|
+
Everything after 'yield' runs on shutdown.
|
|
60
|
+
"""
|
|
61
|
+
async with AsyncExitStack() as stack:
|
|
62
|
+
# 1. Initialize DynamoDB Client (Persistent Pool)
|
|
63
|
+
session = state["session"]
|
|
64
|
+
state["dynamo_client"] = await stack.enter_async_context(
|
|
65
|
+
session.create_client(
|
|
66
|
+
"dynamodb",
|
|
67
|
+
# Ensure the pool is large enough for ML concurrency
|
|
68
|
+
config=AioConfig(max_pool_connections=MAX_POOL_CONNECTIONS),
|
|
69
|
+
)
|
|
71
70
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
logger.info("DynamoDB persistent client initialized.")
|
|
72
|
+
|
|
73
|
+
# 2. Load ML Model
|
|
74
|
+
if state["model"] is None:
|
|
75
|
+
if not cfg.s3_model_path:
|
|
76
|
+
logger.critical("S3_MODEL_PATH not set; service will be unhealthy.")
|
|
77
|
+
else:
|
|
78
|
+
try:
|
|
79
|
+
# Pass the persistent session/client if needed
|
|
80
|
+
state["model"] = await download_and_load_model(
|
|
81
|
+
cfg.s3_model_path, aio_session=state["session"]
|
|
82
|
+
)
|
|
83
|
+
state["stream_features"] = state["model"].get(
|
|
84
|
+
"stream_features", []
|
|
85
|
+
)
|
|
86
|
+
state["user_features"] = state["model"].get("user_features", [])
|
|
87
|
+
|
|
88
|
+
newrelic.agent.add_custom_attribute(
|
|
89
|
+
"total_stream_features", len(state["stream_features"])
|
|
90
|
+
)
|
|
91
|
+
logger.info("Model loaded successfully.")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.critical("Failed to load model: %s", e)
|
|
94
|
+
|
|
95
|
+
yield
|
|
96
|
+
|
|
97
|
+
# 3. Shutdown Logic
|
|
98
|
+
# The AsyncExitStack automatically closes the DynamoDB client pool here
|
|
99
|
+
logger.info("Shutting down: Connection pools closed.")
|
|
100
|
+
|
|
101
|
+
app = FastAPI(
|
|
102
|
+
title="ML Stream Scorer",
|
|
103
|
+
description="Scores video streams using a pre-trained ML model and DynamoDB features.",
|
|
104
|
+
version="1.0.0",
|
|
105
|
+
lifespan=lifespan,
|
|
106
|
+
)
|
|
76
107
|
|
|
77
108
|
@app.get("/health", status_code=HTTPStatus.OK)
|
|
78
109
|
async def health():
|
|
@@ -130,7 +161,7 @@ def create_app(
|
|
|
130
161
|
)
|
|
131
162
|
if stream_features:
|
|
132
163
|
retrieval_meta = await set_stream_features(
|
|
133
|
-
|
|
164
|
+
dynamo_client=state["dynamo_client"],
|
|
134
165
|
streams=streams,
|
|
135
166
|
stream_features=stream_features,
|
|
136
167
|
features_cache=features_cache,
|
|
@@ -168,7 +199,6 @@ def create_app(
|
|
|
168
199
|
newrelic.agent.record_custom_event(
|
|
169
200
|
"Inference",
|
|
170
201
|
{
|
|
171
|
-
"app_name": APP_NAME,
|
|
172
202
|
"cache_misses": retrieval_meta.cache_misses,
|
|
173
203
|
"retrieval_success": int(retrieval_meta.success),
|
|
174
204
|
"cache_delay_minutes": retrieval_meta.cache_delay_minutes,
|
|
@@ -5,14 +5,14 @@ from cachetools import TLRUCache
|
|
|
5
5
|
|
|
6
6
|
def _ttu(_, value: Any, now: float) -> float:
|
|
7
7
|
"""Time-To-Use policy: allow per-item TTL via 'cache_ttl_in_seconds' or fallback."""
|
|
8
|
-
|
|
8
|
+
ONE_WEEK = 7 * 24 * 60 * 60
|
|
9
9
|
try:
|
|
10
10
|
ttl = int(value.get("cache_ttl_in_seconds", -1))
|
|
11
11
|
if ttl > 0:
|
|
12
12
|
return now + ttl
|
|
13
13
|
except Exception:
|
|
14
14
|
pass
|
|
15
|
-
return now +
|
|
15
|
+
return now + ONE_WEEK
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def make_features_cache(maxsize: int) -> TLRUCache:
|
|
@@ -2,7 +2,6 @@ from typing import Any, Dict, List, NamedTuple
|
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
4
|
import datetime
|
|
5
|
-
import aiobotocore.session
|
|
6
5
|
from boto3.dynamodb.types import TypeDeserializer
|
|
7
6
|
import newrelic.agent
|
|
8
7
|
import asyncio
|
|
@@ -38,10 +37,14 @@ async def async_batch_get(
|
|
|
38
37
|
"""
|
|
39
38
|
# DynamoDB's BatchGetItem has a 100-item limit per request.
|
|
40
39
|
CHUNK_SIZE = 100
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
|
|
41
|
+
if len(keys) <= CHUNK_SIZE:
|
|
42
|
+
all_items = await _fetch_chunk(dynamo_client, table_name, keys)
|
|
43
|
+
else:
|
|
44
|
+
chunks = [keys[i : i + CHUNK_SIZE] for i in range(0, len(keys), CHUNK_SIZE)]
|
|
45
|
+
tasks = [_fetch_chunk(dynamo_client, table_name, chunk) for chunk in chunks]
|
|
46
|
+
results = await asyncio.gather(*tasks)
|
|
47
|
+
all_items = [item for batch in results for item in batch]
|
|
45
48
|
return all_items
|
|
46
49
|
|
|
47
50
|
|
|
@@ -76,7 +79,6 @@ async def _fetch_chunk(dynamo_client, table_name: str, chunk_keys):
|
|
|
76
79
|
return items
|
|
77
80
|
|
|
78
81
|
|
|
79
|
-
@newrelic.agent.function_trace()
|
|
80
82
|
def parse_dynamo_item(item: Dict[str, Any]) -> Dict[str, Any]:
|
|
81
83
|
"""Parse a DynamoDB attribute map (low-level) to Python types."""
|
|
82
84
|
# out: Dict[str, Any] = {}
|
|
@@ -92,7 +94,7 @@ async def set_stream_features(
|
|
|
92
94
|
features_table: str,
|
|
93
95
|
stream_pk_prefix: str,
|
|
94
96
|
cache_sep: str,
|
|
95
|
-
|
|
97
|
+
dynamo_client,
|
|
96
98
|
) -> FeatureRetrievalMeta:
|
|
97
99
|
time_start = time.perf_counter_ns()
|
|
98
100
|
"""Fetch missing features for streams from DynamoDB and fill them into streams."""
|
|
@@ -145,22 +147,20 @@ async def set_stream_features(
|
|
|
145
147
|
pk = f"{stream_pk_prefix}{stream_url}"
|
|
146
148
|
keys.append({"pk": {"S": pk}, "sk": {"S": sk}})
|
|
147
149
|
|
|
148
|
-
session = aio_session or aiobotocore.session.get_session()
|
|
149
150
|
dynamo_start = time.perf_counter_ns()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)
|
|
151
|
+
try:
|
|
152
|
+
items = await async_batch_get(dynamo_client, features_table, keys)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error("DynamoDB batch_get failed: %s", e)
|
|
155
|
+
end_time = time.perf_counter_ns()
|
|
156
|
+
return FeatureRetrievalMeta(
|
|
157
|
+
cache_misses=cache_misses,
|
|
158
|
+
retrieval_ms=(end_time - time_start) * 1e-6,
|
|
159
|
+
success=False,
|
|
160
|
+
cache_delay_minutes=cache_delay / 60,
|
|
161
|
+
dynamo_ms=(end_time - dynamo_start) * 1e-6,
|
|
162
|
+
parsing_ms=0,
|
|
163
|
+
)
|
|
164
164
|
dynamo_end = time.perf_counter_ns()
|
|
165
165
|
updated_keys = set()
|
|
166
166
|
for item in items:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/requires.txt
RENAMED
|
File without changes
|
{haystack_ml_stack-0.2.4 → haystack_ml_stack-0.2.5}/src/haystack_ml_stack.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|