aws-python-helper 1.7.0__tar.gz → 1.8.0__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.
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/PKG-INFO +1 -1
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/__init__.py +5 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/base.py +7 -3
- aws_python_helper-1.8.0/aws_python_helper/monitoring/__init__.py +3 -0
- aws_python_helper-1.8.0/aws_python_helper/monitoring/run.py +147 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/permissions/__init__.py +2 -1
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/permissions/catalog.py +13 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/PKG-INFO +1 -1
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/SOURCES.txt +2 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/pyproject.toml +1 -1
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/README.md +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/auth_middleware.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/auth_validators.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/dispatcher.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/exceptions.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/fetcher.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/handler.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/context/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/context/session.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/context/state_validator.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/database_proxy.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/external_database_proxy.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/mongo_manager.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/fargate/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/fargate/executor.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/fargate/fetcher.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/fargate/handler.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/fargate/task_base.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/invoker/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/invoker/api_client.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/invoker/exceptions.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/invoker/lambda_invoker.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/base.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_index_sync/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_index_sync/base.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_query/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_query/base.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/repository/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/repository/audit.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/repository/base.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/sns/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/sns/publisher.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/sqs/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/sqs/consumer_base.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/sqs/fetcher.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/sqs/handler.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/utils/__init__.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/utils/json_encoder.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/utils/response.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/utils/serializer.py +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/requires.txt +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/top_level.txt +0 -0
- {aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/setup.cfg +0 -0
|
@@ -57,8 +57,13 @@ from .invoker import (
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
from .monitoring import process_run, RunReporter, ProcessRunPublisher
|
|
61
|
+
|
|
60
62
|
__all__ = [
|
|
61
63
|
'API',
|
|
64
|
+
'process_run',
|
|
65
|
+
'RunReporter',
|
|
66
|
+
'ProcessRunPublisher',
|
|
62
67
|
'SQSConsumer',
|
|
63
68
|
'MongoManager',
|
|
64
69
|
'DatabaseProxy',
|
|
@@ -203,10 +203,14 @@ class API(ABC):
|
|
|
203
203
|
|
|
204
204
|
@property
|
|
205
205
|
def service_code(self) -> str:
|
|
206
|
-
"""Names this service inside a permission, as in `core:GET /users`.
|
|
207
|
-
import os
|
|
206
|
+
"""Names this service inside a permission, as in `core:GET /users`.
|
|
208
207
|
|
|
209
|
-
|
|
208
|
+
Resolved by the same function the catalog uses, so the name a permission
|
|
209
|
+
is stored under and the name it is demanded by cannot drift.
|
|
210
|
+
"""
|
|
211
|
+
from ..permissions import service_code
|
|
212
|
+
|
|
213
|
+
return service_code()
|
|
210
214
|
|
|
211
215
|
@property
|
|
212
216
|
def auth_data(self) -> Optional[Dict[str, Any]]:
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Reporting that a process started and finished, in one line.
|
|
2
|
+
|
|
3
|
+
Two hand-written publishes would work, and the second one would get forgotten on
|
|
4
|
+
the path that matters: the failure. So the boundary is a context manager — enter
|
|
5
|
+
and it says it started, leave and it says how it went, exception included.
|
|
6
|
+
|
|
7
|
+
Nothing here can raise into the caller. Monitoring must never be able to break
|
|
8
|
+
the thing it monitors: a topic that does not exist, credentials that are wrong,
|
|
9
|
+
SNS being slow are all reasons to lose a record, never reasons to fail a run that
|
|
10
|
+
otherwise worked.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import uuid
|
|
16
|
+
from contextlib import asynccontextmanager
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
from ..sns.publisher import SNSPublisher
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
LAUNCHED = "launched"
|
|
25
|
+
RUNNING = "running"
|
|
26
|
+
SUCCESS = "success"
|
|
27
|
+
FAILED = "failed"
|
|
28
|
+
|
|
29
|
+
TOPIC_ENV = "PROCESS_RUNS_TOPIC_ARN"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ProcessRunPublisher(SNSPublisher):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RunReporter:
|
|
37
|
+
"""Lo que el proceso usa adentro del bloque para contar cómo le fue."""
|
|
38
|
+
|
|
39
|
+
__slots__ = ("run_id", "process_code", "counters", "scope", "_error")
|
|
40
|
+
|
|
41
|
+
def __init__(self, run_id: str, process_code: str):
|
|
42
|
+
self.run_id = run_id
|
|
43
|
+
self.process_code = process_code
|
|
44
|
+
self.counters: Dict[str, Any] = {}
|
|
45
|
+
self.scope: Optional[str] = None
|
|
46
|
+
self._error: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
def count(self, name: str, amount: int = 1) -> None:
|
|
49
|
+
"""Suma a un contador. Lo que se mida con esto se define en el proceso."""
|
|
50
|
+
self.counters[name] = self.counters.get(name, 0) + amount
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _publish(payload: Dict[str, Any]) -> None:
|
|
54
|
+
"""Manda el evento, y si no se puede lo deja en el log y sigue.
|
|
55
|
+
|
|
56
|
+
Se espera a que salga en lugar de dispararlo y seguir: una lambda se congela
|
|
57
|
+
apenas devuelve, y una tarea suelta que todavía no corrió se pierde entera.
|
|
58
|
+
Son milisegundos contra perder el registro.
|
|
59
|
+
"""
|
|
60
|
+
topic = os.getenv(TOPIC_ENV, "")
|
|
61
|
+
if not topic:
|
|
62
|
+
logger.warning(
|
|
63
|
+
"%s is not set, so run %s of %s was not reported",
|
|
64
|
+
TOPIC_ENV, payload.get("run_id"), payload.get("process_code"),
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
await ProcessRunPublisher(topic).publish({"content": _clean(payload)})
|
|
70
|
+
except Exception as error:
|
|
71
|
+
# A propósito no se vuelve a lanzar: perder el registro de una ejecución
|
|
72
|
+
# es malo, tumbar la ejecución por no poder registrarla es peor.
|
|
73
|
+
logger.error("Could not report run %s: %s", payload.get("run_id"), error)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _clean(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
77
|
+
return {key: value for key, value in payload.items() if value is not None}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _now() -> str:
|
|
81
|
+
return datetime.now(timezone.utc).isoformat()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@asynccontextmanager
|
|
85
|
+
async def process_run(
|
|
86
|
+
process_code: str,
|
|
87
|
+
*,
|
|
88
|
+
run_id: Optional[str] = None,
|
|
89
|
+
scope: Optional[str] = None,
|
|
90
|
+
resource_arn: Optional[str] = None,
|
|
91
|
+
request_id: Optional[str] = None,
|
|
92
|
+
):
|
|
93
|
+
"""Reporta el principio y el final de una ejecución.
|
|
94
|
+
|
|
95
|
+
Usage:
|
|
96
|
+
async with process_run("business-region-sync-ct") as run:
|
|
97
|
+
run.count("regions_received")
|
|
98
|
+
|
|
99
|
+
El proceso tiene que estar registrado en core; si no lo está, el evento se
|
|
100
|
+
descarta allá y queda en su log. Eso es a propósito: lo que nadie declaró no
|
|
101
|
+
se está vigilando, y registrarlo solo llenaría la pantalla de ejecuciones que
|
|
102
|
+
nadie puede configurar.
|
|
103
|
+
"""
|
|
104
|
+
reporter = RunReporter(run_id or str(uuid.uuid4()), process_code)
|
|
105
|
+
started_at = _now()
|
|
106
|
+
|
|
107
|
+
await _publish({
|
|
108
|
+
"run_id": reporter.run_id,
|
|
109
|
+
"process_code": process_code,
|
|
110
|
+
"status": LAUNCHED,
|
|
111
|
+
"started_at": started_at,
|
|
112
|
+
"scope": scope,
|
|
113
|
+
# Lo único que AWS deja en el ambiente. El ARN completo y el id de la
|
|
114
|
+
# invocación viven en el `context` del handler, que acá no se tiene: quien
|
|
115
|
+
# lo tenga los pasa por `resource_arn` y `request_id`.
|
|
116
|
+
"resource_arn": resource_arn or os.getenv("AWS_LAMBDA_FUNCTION_NAME") or None,
|
|
117
|
+
"request_id": request_id,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
yield reporter
|
|
122
|
+
except Exception as error:
|
|
123
|
+
await _publish({
|
|
124
|
+
"run_id": reporter.run_id,
|
|
125
|
+
"process_code": process_code,
|
|
126
|
+
"status": FAILED,
|
|
127
|
+
"started_at": started_at,
|
|
128
|
+
"finished_at": _now(),
|
|
129
|
+
"error": f"{type(error).__name__}: {error}"[:2000],
|
|
130
|
+
"counters": reporter.counters,
|
|
131
|
+
"scope": reporter.scope or scope,
|
|
132
|
+
"request_id": request_id,
|
|
133
|
+
})
|
|
134
|
+
# La excepción sigue su camino: el monitoreo anota lo que pasó, no lo
|
|
135
|
+
# decide. Tragarla acá convertiría una falla en un éxito silencioso.
|
|
136
|
+
raise
|
|
137
|
+
else:
|
|
138
|
+
await _publish({
|
|
139
|
+
"run_id": reporter.run_id,
|
|
140
|
+
"process_code": process_code,
|
|
141
|
+
"status": SUCCESS,
|
|
142
|
+
"started_at": started_at,
|
|
143
|
+
"finished_at": _now(),
|
|
144
|
+
"counters": reporter.counters,
|
|
145
|
+
"scope": reporter.scope or scope,
|
|
146
|
+
"request_id": request_id,
|
|
147
|
+
})
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/permissions/__init__.py
RENAMED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from .catalog import ALL, allows, discover, required_for, service_wildcard
|
|
1
|
+
from .catalog import ALL, allows, discover, required_for, service_code, service_wildcard
|
|
2
2
|
|
|
3
3
|
__all__ = [
|
|
4
4
|
"ALL",
|
|
5
5
|
"allows",
|
|
6
6
|
"discover",
|
|
7
7
|
"required_for",
|
|
8
|
+
"service_code",
|
|
8
9
|
"service_wildcard",
|
|
9
10
|
]
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/permissions/catalog.py
RENAMED
|
@@ -35,6 +35,19 @@ FILE_METHODS = {
|
|
|
35
35
|
ALL = "*"
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def service_code() -> str:
|
|
39
|
+
"""The name this service takes inside a permission code, as in `core:GET /users`.
|
|
40
|
+
|
|
41
|
+
One function on purpose. Publishing the catalog and checking a permission are
|
|
42
|
+
two different places, and if each resolves the name its own way one stores
|
|
43
|
+
`core:GET /users` while the other demands `constitution-core:GET /users`.
|
|
44
|
+
Nothing reports that: every role with explicit permissions just gets 403 on
|
|
45
|
+
everything, and a role holding `*` keeps working, so it looks like a problem
|
|
46
|
+
with the role.
|
|
47
|
+
"""
|
|
48
|
+
return os.getenv("SERVICE_CODE") or os.getenv("NAMESPACE") or "service"
|
|
49
|
+
|
|
50
|
+
|
|
38
51
|
def discover(api_root: str, service: str) -> List[Dict[str, str]]:
|
|
39
52
|
"""Every endpoint this service answers, as permission records.
|
|
40
53
|
|
|
@@ -39,6 +39,8 @@ aws_python_helper/model_index_sync/__init__.py
|
|
|
39
39
|
aws_python_helper/model_index_sync/base.py
|
|
40
40
|
aws_python_helper/model_query/__init__.py
|
|
41
41
|
aws_python_helper/model_query/base.py
|
|
42
|
+
aws_python_helper/monitoring/__init__.py
|
|
43
|
+
aws_python_helper/monitoring/run.py
|
|
42
44
|
aws_python_helper/permissions/__init__.py
|
|
43
45
|
aws_python_helper/permissions/catalog.py
|
|
44
46
|
aws_python_helper/repository/__init__.py
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/auth_middleware.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/api/auth_validators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/context/state_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/database_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/database/mongo_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/invoker/lambda_invoker.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/__init__.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/base.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/fetcher.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/lambda_standalone/handler.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_index_sync/__init__.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_index_sync/base.py
RENAMED
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/model_query/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper/repository/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{aws_python_helper-1.7.0 → aws_python_helper-1.8.0}/aws_python_helper.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|