compose-runner 0.6.5__tar.gz → 0.7.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.
- {compose_runner-0.6.5 → compose_runner-0.7.0}/PKG-INFO +2 -2
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/_version.py +2 -2
- compose_runner-0.7.0/compose_runner/aws_lambda/cost_check_handler.py +71 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/infra/cdk/stacks/compose_runner_stack.py +49 -1
- {compose_runner-0.6.5 → compose_runner-0.7.0}/pyproject.toml +1 -1
- {compose_runner-0.6.5 → compose_runner-0.7.0}/.gitignore +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/Dockerfile +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/LICENSE +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/README.md +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/aws_lambda/.dockerignore +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/aws_lambda/Dockerfile +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/__init__.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/aws_lambda/__init__.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/aws_lambda/common.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/aws_lambda/log_poll_handler.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/aws_lambda/results_handler.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/aws_lambda/run_handler.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/aws_lambda/status_handler.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/cli.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/ecs_task.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/run.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/sentry.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_lambda_handlers/test_select_task_size_uses_large_for_montecarlo.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_lambda_handlers/test_select_task_size_uses_standard_for_fdr.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_run/test_download_bundle.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_run/test_run_database_workflow.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_run/test_run_group_comparison_workflow.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_run/test_run_string_group_comparison_workflow.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/cassettes/test_run/test_run_workflow.yaml +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/conftest.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/test_cli.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/test_ecs_task.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/test_lambda_handlers.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/compose_runner/tests/test_run.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/infra/cdk/app.py +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/infra/cdk/cdk.json +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/infra/cdk/requirements.txt +0 -0
- {compose_runner-0.6.5 → compose_runner-0.7.0}/infra/cdk/stacks/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compose-runner
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A package for running neurosynth-compose analyses
|
|
5
5
|
Project-URL: Repository, https://github.com/neurostuff/compose-runner
|
|
6
6
|
Author-email: James Kent <jamesdkent21@gmail.com>
|
|
@@ -10,7 +10,7 @@ Keywords: meta-analysis,neuroimaging,neurosynth,neurosynth-compose
|
|
|
10
10
|
Classifier: License :: OSI Approved :: BSD License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Dist: click
|
|
13
|
-
Requires-Dist: nimare
|
|
13
|
+
Requires-Dist: nimare>=0.7.0
|
|
14
14
|
Requires-Dist: numpy
|
|
15
15
|
Requires-Dist: sentry-sdk
|
|
16
16
|
Provides-Extra: aws
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.7.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 7, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
import boto3
|
|
10
|
+
from botocore.exceptions import BotoCoreError, ClientError
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
logger.setLevel(logging.INFO)
|
|
14
|
+
|
|
15
|
+
_CE_CLIENT = boto3.client("ce", region_name=os.environ.get("AWS_REGION", "us-east-1"))
|
|
16
|
+
|
|
17
|
+
COST_LIMIT_ENV = "COST_LIMIT_USD"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _month_range(today: _dt.date) -> Dict[str, str]:
|
|
21
|
+
start = today.replace(day=1)
|
|
22
|
+
# Cost Explorer end date is exclusive; add a day to include today.
|
|
23
|
+
end = today + _dt.timedelta(days=1)
|
|
24
|
+
return {"Start": start.isoformat(), "End": end.isoformat()}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _current_month_cost() -> Dict[str, Any]:
|
|
28
|
+
period = _month_range(_dt.date.today())
|
|
29
|
+
response = _CE_CLIENT.get_cost_and_usage(
|
|
30
|
+
TimePeriod=period,
|
|
31
|
+
Granularity="MONTHLY",
|
|
32
|
+
Metrics=["UnblendedCost"],
|
|
33
|
+
)
|
|
34
|
+
results = response.get("ResultsByTime", [])
|
|
35
|
+
total = results[0]["Total"]["UnblendedCost"] if results else {"Amount": "0", "Unit": "USD"}
|
|
36
|
+
amount = float(Decimal(total.get("Amount", "0")))
|
|
37
|
+
currency = total.get("Unit", "USD")
|
|
38
|
+
return {"amount": amount, "currency": currency, "time_period": period}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
42
|
+
limit_raw = os.environ.get(COST_LIMIT_ENV)
|
|
43
|
+
if not limit_raw:
|
|
44
|
+
raise RuntimeError(f"{COST_LIMIT_ENV} environment variable must be set.")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
limit = float(limit_raw)
|
|
48
|
+
except ValueError as exc: # noqa: PERF203
|
|
49
|
+
raise RuntimeError(f"Invalid {COST_LIMIT_ENV}: {limit_raw}") from exc
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
cost = _current_month_cost()
|
|
53
|
+
except (ClientError, BotoCoreError) as exc:
|
|
54
|
+
logger.error("Failed to query Cost Explorer: %s", exc)
|
|
55
|
+
return {
|
|
56
|
+
"status": "ERROR",
|
|
57
|
+
"allowed": False,
|
|
58
|
+
"error": "cost_explorer_unavailable",
|
|
59
|
+
"limit": limit,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
amount = cost["amount"]
|
|
63
|
+
allowed = amount < limit
|
|
64
|
+
return {
|
|
65
|
+
"status": "OK",
|
|
66
|
+
"allowed": allowed,
|
|
67
|
+
"current_spend": amount,
|
|
68
|
+
"limit": limit,
|
|
69
|
+
"currency": cost.get("currency", "USD"),
|
|
70
|
+
"time_period": cost.get("time_period"),
|
|
71
|
+
}
|
|
@@ -36,6 +36,7 @@ class ComposeRunnerStack(Stack):
|
|
|
36
36
|
poll_memory_size = int(self.node.try_get_context("pollMemorySize") or 512)
|
|
37
37
|
poll_timeout_seconds = int(self.node.try_get_context("pollTimeoutSeconds") or 30)
|
|
38
38
|
poll_lookback_ms = int(self.node.try_get_context("pollLookbackMs") or 3600000)
|
|
39
|
+
monthly_spend_limit_usd = float(self.node.try_get_context("monthlySpendLimit") or 100)
|
|
39
40
|
|
|
40
41
|
task_cpu = int(self.node.try_get_context("taskCpu") or 4096)
|
|
41
42
|
task_memory_mib = int(self.node.try_get_context("taskMemoryMiB") or 30720)
|
|
@@ -243,6 +244,31 @@ class ComposeRunnerStack(Stack):
|
|
|
243
244
|
max_attempts=2,
|
|
244
245
|
)
|
|
245
246
|
|
|
247
|
+
cost_check_code = lambda_.DockerImageCode.from_image_asset(
|
|
248
|
+
str(project_root),
|
|
249
|
+
file="aws_lambda/Dockerfile",
|
|
250
|
+
cmd=["compose_runner.aws_lambda.cost_check_handler.handler"],
|
|
251
|
+
build_args=build_args,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
cost_check_function = lambda_.DockerImageFunction(
|
|
255
|
+
self,
|
|
256
|
+
"ComposeRunnerCostCheck",
|
|
257
|
+
code=cost_check_code,
|
|
258
|
+
memory_size=256,
|
|
259
|
+
timeout=Duration.seconds(15),
|
|
260
|
+
environment={
|
|
261
|
+
"COST_LIMIT_USD": str(monthly_spend_limit_usd),
|
|
262
|
+
},
|
|
263
|
+
description="Blocks executions when monthly spend exceeds the configured limit.",
|
|
264
|
+
)
|
|
265
|
+
cost_check_function.add_to_role_policy(
|
|
266
|
+
iam.PolicyStatement(
|
|
267
|
+
actions=["ce:GetCostAndUsage"],
|
|
268
|
+
resources=["*"],
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
|
|
246
272
|
run_output = sfn.Pass(
|
|
247
273
|
self,
|
|
248
274
|
"ComposeRunnerOutput",
|
|
@@ -256,7 +282,7 @@ class ComposeRunnerStack(Stack):
|
|
|
256
282
|
},
|
|
257
283
|
)
|
|
258
284
|
|
|
259
|
-
|
|
285
|
+
task_selection = sfn.Choice(
|
|
260
286
|
self,
|
|
261
287
|
"SelectFargateTask",
|
|
262
288
|
).when(
|
|
@@ -266,6 +292,28 @@ class ComposeRunnerStack(Stack):
|
|
|
266
292
|
run_task_standard.next(run_output)
|
|
267
293
|
)
|
|
268
294
|
|
|
295
|
+
cost_limit_exceeded = sfn.Fail(
|
|
296
|
+
self,
|
|
297
|
+
"CostLimitExceeded",
|
|
298
|
+
cause="Monthly spend limit exceeded.",
|
|
299
|
+
error="CostLimitExceeded",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
enforce_cost_limit = sfn.Choice(self, "EnforceMonthlyCostLimit").when(
|
|
303
|
+
sfn.Condition.boolean_equals("$.cost_check.Payload.allowed", False),
|
|
304
|
+
cost_limit_exceeded,
|
|
305
|
+
).otherwise(task_selection)
|
|
306
|
+
|
|
307
|
+
cost_check_step = tasks.LambdaInvoke(
|
|
308
|
+
self,
|
|
309
|
+
"CheckMonthlyCost",
|
|
310
|
+
lambda_function=cost_check_function,
|
|
311
|
+
payload=sfn.TaskInput.from_object({"stateInput.$": "$"}),
|
|
312
|
+
result_path="$.cost_check",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
definition_chain = cost_check_step.next(enforce_cost_limit)
|
|
316
|
+
|
|
269
317
|
state_machine = sfn.StateMachine(
|
|
270
318
|
self,
|
|
271
319
|
"ComposeRunnerStateMachine",
|
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
15
15
|
]
|
|
16
16
|
dynamic = ["version"]
|
|
17
|
-
dependencies = ["nimare", "click", "sentry-sdk", "numpy"]
|
|
17
|
+
dependencies = ["nimare>=0.7.0", "click", "sentry-sdk", "numpy"]
|
|
18
18
|
|
|
19
19
|
[project.urls]
|
|
20
20
|
Repository = "https://github.com/neurostuff/compose-runner"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|