processforge 0.3.8__tar.gz → 0.3.10__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.
- {processforge-0.3.8/src/processforge.egg-info → processforge-0.3.10}/PKG-INFO +5 -1
- {processforge-0.3.8 → processforge-0.3.10}/pyproject.toml +6 -1
- processforge-0.3.10/src/processforge/api/serve.py +116 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/init.py +11 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/run.py +4 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/container_client.py +2 -2
- processforge-0.3.10/src/processforge/utils/__init__.py +0 -0
- processforge-0.3.10/src/processforge/utils/s3_upload.py +69 -0
- {processforge-0.3.8 → processforge-0.3.10/src/processforge.egg-info}/PKG-INFO +5 -1
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge.egg-info/SOURCES.txt +4 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge.egg-info/requires.txt +5 -0
- processforge-0.3.10/tests/test_s3_upload.py +81 -0
- {processforge-0.3.8 → processforge-0.3.10}/LICENSE +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/MANIFEST.in +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/README.md +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/flowsheets/closed-loop-chain.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/flowsheets/custom/crimson/MUWS_HB.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/flowsheets/custom/simple_hyrdaulic_scheme.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/flowsheets/festim/hydrogen_transport_1d.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/flowsheets/hydraulic-chain.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/flowsheets/openmc/msre_eigenvalue.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/setup.cfg +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/__main__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/_schema.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/analysis/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/analysis/dof.py +0 -0
- {processforge-0.3.8/src/processforge/utils → processforge-0.3.10/src/processforge/api}/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/apply.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/common.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/diagram.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/display.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/export_fmu.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/export_modelica.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/plan.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/cli/validate.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/compose.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/backends/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/backends/base.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/backends/casadi_backend.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/backends/pyomo_backend.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/backends/scipy_backend.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/flowsheet.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/jacobian.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/mixin.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/solver.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/stream_var.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/flash_eo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/heater_eo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/pipes_eo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/pump_eo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/strainer_eo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/eo/units/valve_eo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/flowsheet.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/fmu/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/fmu/_fmi_vars.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/fmu/builder.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/fmu/slave_template.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/lock.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/modelica/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/modelica/mo_writer.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/modelica/omc_runner.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/modelica/transpiler.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/modelica/unit_equations.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/pcl/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/pcl/compiler.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/pcl/namespace.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/provenance.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/base.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/base_jacobian_mixin.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/cantera_jacobian.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/cantera_provider.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/coolprop_provider.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/festim_provider.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/jacobian_contributor.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/manager.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/modelica_jacobian.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/modelica_provider.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/openmc_provider.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/reference_state_registry.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/registry.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/result.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/result_schema.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/schemas/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/schemas/flowsheet_schema.json +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/schemas/openmc/openmc_model.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/simulate.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/solver.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/state.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/thermo.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/types.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/__init__.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/base.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/cstr.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/flash.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/heater.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/pfr.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/pipes.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/pump.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/registry.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/solver_unit.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/strainer.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/tank.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/units/valve.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/utils/flowsheet_diagram.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/utils/mermaid_diagram.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/utils/topology.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/utils/unit_consistency.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/utils/validate_flowsheet.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge/utils/validation.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge.egg-info/dependency_links.txt +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge.egg-info/entry_points.txt +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/src/processforge.egg-info/top_level.txt +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/tests/test_cli.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/tests/test_festim_provider.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/tests/test_init.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/tests/test_providers.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/tests/test_state.py +0 -0
- {processforge-0.3.8 → processforge-0.3.10}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: processforge
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: A Python-based process simulation framework for chemical engineering applications.
|
|
5
5
|
Author-email: Processforge Team <team@processforge.dev>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -30,6 +30,10 @@ Requires-Dist: pandas>=2.2
|
|
|
30
30
|
Requires-Dist: zarr>=3.0
|
|
31
31
|
Requires-Dist: coolprop>=7.2.0
|
|
32
32
|
Requires-Dist: typer>=0.12
|
|
33
|
+
Provides-Extra: api
|
|
34
|
+
Requires-Dist: fastapi>=0.110; extra == "api"
|
|
35
|
+
Requires-Dist: uvicorn>=0.29; extra == "api"
|
|
36
|
+
Requires-Dist: s3fs>=2024.0; extra == "api"
|
|
33
37
|
Provides-Extra: eo
|
|
34
38
|
Requires-Dist: pyomo>=6.7; extra == "eo"
|
|
35
39
|
Provides-Extra: eo-casadi
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "processforge"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.10"
|
|
8
8
|
description = "A Python-based process simulation framework for chemical engineering applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -39,6 +39,11 @@ dependencies = [
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
[project.optional-dependencies]
|
|
42
|
+
api = [
|
|
43
|
+
"fastapi>=0.110",
|
|
44
|
+
"uvicorn>=0.29",
|
|
45
|
+
"s3fs>=2024.0",
|
|
46
|
+
]
|
|
42
47
|
eo = [
|
|
43
48
|
"pyomo>=6.7",
|
|
44
49
|
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Processforge provider HTTP API.
|
|
2
|
+
|
|
3
|
+
Run as a module so deployment platforms that execute
|
|
4
|
+
``python -m processforge.api.serve`` can boot the server directly::
|
|
5
|
+
|
|
6
|
+
python -m processforge.api.serve
|
|
7
|
+
|
|
8
|
+
The listen port is taken from the ``PORT`` environment variable
|
|
9
|
+
(default ``9000``), and the provider type from ``PROVIDER_TYPE``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import traceback
|
|
16
|
+
|
|
17
|
+
from fastapi import FastAPI, HTTPException
|
|
18
|
+
from loguru import logger
|
|
19
|
+
|
|
20
|
+
app = FastAPI(title="Processforge Provider")
|
|
21
|
+
|
|
22
|
+
PROVIDER_TYPE = os.environ.get("PROVIDER_TYPE", "")
|
|
23
|
+
DEFAULT_PORT = int(os.environ.get("PORT", "9000"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.get("/health")
|
|
27
|
+
def health():
|
|
28
|
+
return {
|
|
29
|
+
"status": "ready",
|
|
30
|
+
"provider_type": PROVIDER_TYPE,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.post("/run")
|
|
35
|
+
def run(body: dict):
|
|
36
|
+
from processforge.types import UnitConfig, MaterialDef
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
unit_cfg = UnitConfig.from_dict(body["unit_config"])
|
|
40
|
+
except Exception as exc:
|
|
41
|
+
raise HTTPException(status_code=400, detail=f"Invalid unit_config: {exc}") from exc
|
|
42
|
+
|
|
43
|
+
materials_raw = body.get("materials", {})
|
|
44
|
+
materials = {
|
|
45
|
+
name: MaterialDef.from_dict(mat)
|
|
46
|
+
for name, mat in materials_raw.items()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
from processforge.providers.registry import get_provider_class
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
provider_cls = get_provider_class(PROVIDER_TYPE)
|
|
53
|
+
except ValueError as exc:
|
|
54
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
55
|
+
|
|
56
|
+
provider = provider_cls()
|
|
57
|
+
|
|
58
|
+
# Build the provider config for initialize(). Prefer the real config the
|
|
59
|
+
# CLI serialised into the request body (carries cross_sections, output_dir,
|
|
60
|
+
# url, …); fall back to a minimal container-default config otherwise.
|
|
61
|
+
provider_config_raw = body.get("provider_config")
|
|
62
|
+
if provider_config_raw:
|
|
63
|
+
from processforge.types import provider_config_from_dict
|
|
64
|
+
|
|
65
|
+
provider_config = provider_config_from_dict(provider_config_raw)
|
|
66
|
+
else:
|
|
67
|
+
provider_config = type("Cfg", (), {
|
|
68
|
+
"url": None,
|
|
69
|
+
"output_dir": os.environ.get("PROCESSFORGE_OUTPUT_DIR", "/data"),
|
|
70
|
+
"cross_sections": None,
|
|
71
|
+
"type": PROVIDER_TYPE,
|
|
72
|
+
})()
|
|
73
|
+
|
|
74
|
+
# Build a minimal flowsheet config holding materials.
|
|
75
|
+
flowsheet_config = type("FS", (), {"materials": materials})()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
provider.initialize(provider_config, flowsheet_config)
|
|
79
|
+
result = provider.run_simulation(unit_cfg, body.get("inlet", {}))
|
|
80
|
+
_maybe_upload_run_outputs(result)
|
|
81
|
+
return result.as_dict() | {"metadata": result.metadata}
|
|
82
|
+
except HTTPException:
|
|
83
|
+
raise
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
raise HTTPException(
|
|
86
|
+
status_code=500,
|
|
87
|
+
detail=f"Simulation failed: {exc}\n{traceback.format_exc()}",
|
|
88
|
+
) from exc
|
|
89
|
+
finally:
|
|
90
|
+
provider.teardown()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _maybe_upload_run_outputs(result) -> None:
|
|
94
|
+
"""Upload the provider's run_dir to S3 when configured.
|
|
95
|
+
|
|
96
|
+
No-op unless ``S3_BUCKET`` is set in the container environment and the
|
|
97
|
+
provider recorded a ``run_dir`` in its result metadata. Upload failures are
|
|
98
|
+
logged and never fail the run.
|
|
99
|
+
"""
|
|
100
|
+
run_dir = result.metadata.get("run_dir")
|
|
101
|
+
if not run_dir:
|
|
102
|
+
return
|
|
103
|
+
try:
|
|
104
|
+
from processforge.utils.s3_upload import upload_directory_to_s3
|
|
105
|
+
|
|
106
|
+
s3_uri = upload_directory_to_s3(run_dir)
|
|
107
|
+
if s3_uri:
|
|
108
|
+
result.metadata["s3_uri"] = s3_uri
|
|
109
|
+
except Exception as exc: # noqa: BLE001
|
|
110
|
+
logger.warning(f"S3 upload of {run_dir} failed: {exc}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
import uvicorn
|
|
115
|
+
|
|
116
|
+
uvicorn.run(app, host="0.0.0.0", port=DEFAULT_PORT)
|
|
@@ -224,6 +224,17 @@ def init(
|
|
|
224
224
|
)
|
|
225
225
|
except subprocess.TimeoutExpired:
|
|
226
226
|
logger.warning("docker compose pull timed out after 600s.")
|
|
227
|
+
|
|
228
|
+
logger.info(
|
|
229
|
+
"To start the containerized provider(s), run:\n"
|
|
230
|
+
f" docker compose -f {compose_path} up -d"
|
|
231
|
+
)
|
|
232
|
+
if any(info["type"] == "openmc" for info in local_docker_providers.values()):
|
|
233
|
+
logger.info(
|
|
234
|
+
"Running OpenMC — set OPENMC_DATA_ROOT to mount your OpenMC "
|
|
235
|
+
"data/workspace into the container (defaults to 'outputs'):\n"
|
|
236
|
+
f" OPENMC_DATA_ROOT=/path/to/data docker compose -f {compose_path} up -d"
|
|
237
|
+
)
|
|
227
238
|
elif remote_docker_providers:
|
|
228
239
|
logger.info(
|
|
229
240
|
"All containerized providers use remote URLs — skipping Docker setup."
|
|
@@ -100,6 +100,10 @@ def run(
|
|
|
100
100
|
logger.info(f" Backend : {display_backend(config, getattr(fs, 'backend', 'dynamic'))}")
|
|
101
101
|
logger.info(f"Results saved to {zarr_path}")
|
|
102
102
|
|
|
103
|
+
for name, res in results.items():
|
|
104
|
+
if isinstance(res, dict) and (s3_uri := res.get("metadata", {}).get("s3_uri")):
|
|
105
|
+
logger.info(f" Run outputs (S3): {s3_uri}")
|
|
106
|
+
|
|
103
107
|
if export_images:
|
|
104
108
|
try:
|
|
105
109
|
plot_results(results, fname=f"{base_name}_results.png")
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""CLI-side HTTP client for containerized providers (OpenMC, FESTIM, …).
|
|
2
2
|
|
|
3
3
|
A provider that runs in a Docker container does its real work inside the
|
|
4
|
-
container (in ``
|
|
4
|
+
container (in ``processforge.api.serve``). The CLI side must NOT import the
|
|
5
5
|
backend library (OpenMC, FESTIM, …) locally — instead it speaks HTTP to the
|
|
6
6
|
container's ``provider_server.py`` instance. :class:`ContainerProviderClient`
|
|
7
7
|
implements the :class:`~processforge.providers.base.AbstractProvider` contract
|
|
8
8
|
as a thin HTTP wrapper around that server.
|
|
9
9
|
|
|
10
|
-
Request/response contract (matches ``
|
|
10
|
+
Request/response contract (matches ``processforge.api.serve``)
|
|
11
11
|
----------------------------------------------------------------
|
|
12
12
|
* ``POST /run`` with a JSON body
|
|
13
13
|
``{"unit_config", "materials", "inlet", "output_dir", "provider_config"}``.
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""S3 upload helper for provider containers.
|
|
2
|
+
|
|
3
|
+
Uploads a provider's run-output directory (``run_dir``) to S3 so ephemeral
|
|
4
|
+
container-local artifacts (``statepoint.h5``, mesh tallies, ``*.h5m``) survive
|
|
5
|
+
after the container stops. The container opts in by setting ``S3_BUCKET`` in its
|
|
6
|
+
environment; credential env vars reuse the convention from
|
|
7
|
+
:func:`processforge.result.save_results_zarr_s3`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def s3_storage_options() -> dict:
|
|
17
|
+
"""Build s3fs storage options from environment variables.
|
|
18
|
+
|
|
19
|
+
Returns an empty dict when no credentials are configured so s3fs falls
|
|
20
|
+
back to its default credential chain (e.g. instance/role-based auth).
|
|
21
|
+
"""
|
|
22
|
+
opts: dict = {}
|
|
23
|
+
key = os.environ.get("S3_ACCESS_KEY")
|
|
24
|
+
secret = os.environ.get("S3_SECRET_KEY")
|
|
25
|
+
if key:
|
|
26
|
+
opts["key"] = key
|
|
27
|
+
if secret:
|
|
28
|
+
opts["secret"] = secret
|
|
29
|
+
client_kwargs = {
|
|
30
|
+
k: v
|
|
31
|
+
for k, v in {
|
|
32
|
+
"endpoint_url": os.environ.get("S3_ENDPOINT_URL"),
|
|
33
|
+
"region_name": os.environ.get("S3_REGION_NAME", "ams3"),
|
|
34
|
+
}.items()
|
|
35
|
+
if v
|
|
36
|
+
}
|
|
37
|
+
if client_kwargs:
|
|
38
|
+
opts["client_kwargs"] = client_kwargs
|
|
39
|
+
return opts
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def upload_directory_to_s3(run_dir: str) -> str | None:
|
|
43
|
+
"""Upload *run_dir* recursively to S3.
|
|
44
|
+
|
|
45
|
+
Reads the destination from the container environment:
|
|
46
|
+
|
|
47
|
+
* ``S3_BUCKET`` — required; enables the upload. When unset, returns ``None``
|
|
48
|
+
and the caller keeps its local artifacts.
|
|
49
|
+
* ``S3_PREFIX`` — optional key prefix (e.g. ``openmc-runs``).
|
|
50
|
+
|
|
51
|
+
The object key is ``<S3_PREFIX>/<run_dir_basename>-<utc_stamp>/`` so
|
|
52
|
+
repeated runs do not clobber each other.
|
|
53
|
+
|
|
54
|
+
Returns the uploaded ``s3://`` URI, or ``None`` when disabled.
|
|
55
|
+
"""
|
|
56
|
+
bucket = os.environ.get("S3_BUCKET")
|
|
57
|
+
if not bucket:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
prefix = os.environ.get("S3_PREFIX", "").strip("/")
|
|
61
|
+
tag = f"{os.path.basename(run_dir.rstrip('/'))}-{datetime.now(timezone.utc):%Y%m%dT%H%M%SZ}"
|
|
62
|
+
s3_dir = f"{prefix + '/' if prefix else ''}{tag}"
|
|
63
|
+
|
|
64
|
+
import s3fs
|
|
65
|
+
|
|
66
|
+
s3fs.S3FileSystem(**s3_storage_options()).put(
|
|
67
|
+
run_dir, f"s3://{bucket}/{s3_dir}", recursive=True
|
|
68
|
+
)
|
|
69
|
+
return f"s3://{bucket}/{s3_dir}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: processforge
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: A Python-based process simulation framework for chemical engineering applications.
|
|
5
5
|
Author-email: Processforge Team <team@processforge.dev>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -30,6 +30,10 @@ Requires-Dist: pandas>=2.2
|
|
|
30
30
|
Requires-Dist: zarr>=3.0
|
|
31
31
|
Requires-Dist: coolprop>=7.2.0
|
|
32
32
|
Requires-Dist: typer>=0.12
|
|
33
|
+
Provides-Extra: api
|
|
34
|
+
Requires-Dist: fastapi>=0.110; extra == "api"
|
|
35
|
+
Requires-Dist: uvicorn>=0.29; extra == "api"
|
|
36
|
+
Requires-Dist: s3fs>=2024.0; extra == "api"
|
|
33
37
|
Provides-Extra: eo
|
|
34
38
|
Requires-Dist: pyomo>=6.7; extra == "eo"
|
|
35
39
|
Provides-Extra: eo-casadi
|
|
@@ -30,6 +30,8 @@ src/processforge.egg-info/requires.txt
|
|
|
30
30
|
src/processforge.egg-info/top_level.txt
|
|
31
31
|
src/processforge/analysis/__init__.py
|
|
32
32
|
src/processforge/analysis/dof.py
|
|
33
|
+
src/processforge/api/__init__.py
|
|
34
|
+
src/processforge/api/serve.py
|
|
33
35
|
src/processforge/cli/__init__.py
|
|
34
36
|
src/processforge/cli/apply.py
|
|
35
37
|
src/processforge/cli/common.py
|
|
@@ -105,6 +107,7 @@ src/processforge/units/valve.py
|
|
|
105
107
|
src/processforge/utils/__init__.py
|
|
106
108
|
src/processforge/utils/flowsheet_diagram.py
|
|
107
109
|
src/processforge/utils/mermaid_diagram.py
|
|
110
|
+
src/processforge/utils/s3_upload.py
|
|
108
111
|
src/processforge/utils/topology.py
|
|
109
112
|
src/processforge/utils/unit_consistency.py
|
|
110
113
|
src/processforge/utils/validate_flowsheet.py
|
|
@@ -113,5 +116,6 @@ tests/test_cli.py
|
|
|
113
116
|
tests/test_festim_provider.py
|
|
114
117
|
tests/test_init.py
|
|
115
118
|
tests/test_providers.py
|
|
119
|
+
tests/test_s3_upload.py
|
|
116
120
|
tests/test_state.py
|
|
117
121
|
tests/test_types.py
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Tests for the S3 run-output upload helper."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from processforge.utils import s3_upload
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def clean_env(monkeypatch):
|
|
16
|
+
for var in (
|
|
17
|
+
"S3_BUCKET",
|
|
18
|
+
"S3_PREFIX",
|
|
19
|
+
"S3_ACCESS_KEY",
|
|
20
|
+
"S3_SECRET_KEY",
|
|
21
|
+
"S3_ENDPOINT_URL",
|
|
22
|
+
"S3_REGION_NAME",
|
|
23
|
+
):
|
|
24
|
+
monkeypatch.delenv(var, raising=False)
|
|
25
|
+
return monkeypatch
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_s3_storage_options_defaults_to_ams3(clean_env):
|
|
29
|
+
opts = s3_upload.s3_storage_options()
|
|
30
|
+
assert opts["client_kwargs"]["region_name"] == "ams3"
|
|
31
|
+
# No creds configured -> not present, s3fs uses default chain.
|
|
32
|
+
assert "key" not in opts
|
|
33
|
+
assert "secret" not in opts
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_s3_storage_options_includes_credentials(clean_env):
|
|
37
|
+
clean_env.setenv("S3_ACCESS_KEY", "ak")
|
|
38
|
+
clean_env.setenv("S3_SECRET_KEY", "sk")
|
|
39
|
+
clean_env.setenv("S3_ENDPOINT_URL", "https://s3.example.com")
|
|
40
|
+
opts = s3_upload.s3_storage_options()
|
|
41
|
+
assert opts["key"] == "ak"
|
|
42
|
+
assert opts["secret"] == "sk"
|
|
43
|
+
assert opts["client_kwargs"]["endpoint_url"] == "https://s3.example.com"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_upload_directory_to_s3_disabled_without_bucket(clean_env):
|
|
47
|
+
assert s3_upload.upload_directory_to_s3("/tmp/run") is None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_upload_directory_to_s3_uploads_recursively(clean_env):
|
|
51
|
+
clean_env.setenv("S3_BUCKET", "my-bucket")
|
|
52
|
+
clean_env.setenv("S3_PREFIX", "openmc-runs")
|
|
53
|
+
|
|
54
|
+
mock_fs = MagicMock()
|
|
55
|
+
mock_s3fs = MagicMock()
|
|
56
|
+
mock_s3fs.S3FileSystem.return_value = mock_fs
|
|
57
|
+
|
|
58
|
+
with patch.dict(sys.modules, {"s3fs": mock_s3fs}):
|
|
59
|
+
uri = s3_upload.upload_directory_to_s3("/tmp/processforge/openmc/msre_run")
|
|
60
|
+
|
|
61
|
+
assert uri.startswith("s3://my-bucket/openmc-runs/msre_run-")
|
|
62
|
+
assert uri.endswith(datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")[:8]) or "-" in uri
|
|
63
|
+
mock_fs.put.assert_called_once()
|
|
64
|
+
args, kwargs = mock_fs.put.call_args
|
|
65
|
+
assert args[0] == "/tmp/processforge/openmc/msre_run"
|
|
66
|
+
assert args[1].startswith("s3://my-bucket/openmc-runs/msre_run-")
|
|
67
|
+
assert kwargs.get("recursive") is True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_upload_directory_to_s3_no_prefix(clean_env):
|
|
71
|
+
clean_env.setenv("S3_BUCKET", "b")
|
|
72
|
+
|
|
73
|
+
mock_fs = MagicMock()
|
|
74
|
+
mock_s3fs = MagicMock()
|
|
75
|
+
mock_s3fs.S3FileSystem.return_value = mock_fs
|
|
76
|
+
|
|
77
|
+
with patch.dict(sys.modules, {"s3fs": mock_s3fs}):
|
|
78
|
+
uri = s3_upload.upload_directory_to_s3("/data/festim")
|
|
79
|
+
|
|
80
|
+
assert uri == f"s3://b/festim-{datetime.now(timezone.utc):%Y%m%dT%H%M%SZ}"
|
|
81
|
+
assert mock_fs.put.call_args[1].get("recursive") is True
|
|
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
|
{processforge-0.3.8/src/processforge/utils → processforge-0.3.10/src/processforge/api}/__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
|
|
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
|
{processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/base_jacobian_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/jacobian_contributor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.8 → processforge-0.3.10}/src/processforge/providers/reference_state_registry.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
|
|
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
|