From cd26c31ac71bd5f6efecceeaff999f53ff6f63e4 Mon Sep 17 00:00:00 2001 From: Lasse Hammer Date: Wed, 27 May 2026 14:21:23 +0200 Subject: [PATCH 1/2] Docker and docker-compose setup with updated readme --- Dockerfile | 21 ++++++++++++++++++ README.md | 36 +++++++++++++++++++++++++++++++ compose-up.sh | 23 ++++++++++++++++++++ docker-compose.yml | 37 ++++++++++++++++++++++++++++++++ src/provider_simenv/db_config.py | 16 +++++++++----- src/provider_simenv/main.py | 14 ++++++++++++ 6 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 Dockerfile create mode 100755 compose-up.sh create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6e7914a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.12-slim + +# Keep Python output unbuffered so logs stream immediately. +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Copy and install the package defined in pyproject.toml. +COPY . /app +RUN pip install --no-cache-dir '.[db]' + +# Run from the package source directory so bundled data/input and data/output paths resolve. +WORKDIR /app/src/provider_simenv + +# Pass simulation arguments directly to main.py. +# Example: +# docker run --rm simenv --pdl scenarios/s1-soja.pdl.yaml \ +# --postgres-url postgresql+psycopg2://postgres:postgres@host:5432/provider_simenv +ENTRYPOINT ["python", "main.py"] +CMD ["--pdl", "scenarios/s1-soja.pdl.yaml"] diff --git a/README.md b/README.md index 15aa5eb..0c76f69 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,29 @@ Scenarios are defined in `data/input/SimulatorScenarios.csv`. Each row is one sc --- +## Run in docker container + +The simulation can also be run in a docker container. +First, build the container. In the repo root run + +```bash +docker build -t provider-simenv . +``` + +Then you can run the simulation in the container with + +```bash +docker run --rm -v :/scenarios provider-simenv --pdl /scenarios/ +``` + +You can also configure a PostgreSQL interface for data storage (see next chapter) with + +```bash +docker run --rm -v :/scenarios provider-simenv --pdl /scenarios/ --postgres-url +``` + +--- + ## PostgreSQL Setup (Optional) PostgreSQL enables live data access during the simulation — required for future palaestrAI @@ -209,6 +232,19 @@ Subsequent scenarios within the same run append to the existing tables. --- +## docker-compose + +You can also run a docker compose that sets up a PostgreSQL database and links it to the simulation container. +Using our helper script, everything is configured automatically. +Just run + +```bash +./compose-up.sh +``` +from the repository root. This builds the database container, if it's not already up and runs a simulation of the specified pdl file. You can run different configurations by simply repeating this call with the respective paths. + +--- + ## Output Files | File | Description | diff --git a/compose-up.sh b/compose-up.sh new file mode 100755 index 0000000..9936571 --- /dev/null +++ b/compose-up.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 [docker compose args...]" >&2 + echo "Example: $0 src/provider_simenv/scenarios/s1-soja.pdl.yaml" >&2 + exit 1 +fi + +pdl_input="$1" +shift || true + +if [[ ! -f "$pdl_input" ]]; then + echo "Error: PDL file not found: $pdl_input" >&2 + exit 1 +fi + +pdl_abs="$(cd "$(dirname "$pdl_input")" && pwd)/$(basename "$pdl_input")" +export PDL_DIR="$(dirname "$pdl_abs")" +export PDL_FILE="$(basename "$pdl_abs")" + +# Start postgres and simulation. The postgres named volume keeps DB data across runs. +docker compose up simenv "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..19b6827 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +services: + postgres: + image: postgres:16 + container_name: provider-simenv-postgres + environment: + POSTGRES_DB: provider_simenv + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + volumes: + - simenv_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d provider_simenv"] + interval: 5s + timeout: 5s + retries: 12 + + simenv: + build: + context: . + dockerfile: Dockerfile + image: provider-simenv:latest + depends_on: + postgres: + condition: service_healthy + volumes: + - ${PDL_DIR:?Set PDL_DIR to the directory that contains the PDL file}:/pdl:ro + command: + - --pdl + - /pdl/${PDL_FILE:?Set PDL_FILE to the PDL filename} + - --postgres-url + - postgresql+psycopg2://postgres:postgres@postgres:5432/provider_simenv + restart: "no" + +volumes: + simenv_postgres_data: diff --git a/src/provider_simenv/db_config.py b/src/provider_simenv/db_config.py index 67e7313..5980d8b 100644 --- a/src/provider_simenv/db_config.py +++ b/src/provider_simenv/db_config.py @@ -11,6 +11,7 @@ print(cfg) # safe repr, password masked """ +import os from dataclasses import dataclass, field @dataclass @@ -23,11 +24,12 @@ class PostgresDBConfig: - palaestrAI: host=, port=, user=, password= """ - host: str = "localhost" - port: int = 5432 - dbname: str = "provider_simenv" - user: str = "postgres" - password: str = "postgres" + host: str = field(default_factory=lambda: os.getenv("PROVIDER_SIMENV_PG_HOST", "localhost")) + port: int = field(default_factory=lambda: int(os.getenv("PROVIDER_SIMENV_PG_PORT", "5432"))) + dbname: str = field(default_factory=lambda: os.getenv("PROVIDER_SIMENV_PG_DB", "provider_simenv")) + user: str = field(default_factory=lambda: os.getenv("PROVIDER_SIMENV_PG_USER", "postgres")) + password: str = field(default_factory=lambda: os.getenv("PROVIDER_SIMENV_PG_PASSWORD", "postgres")) + postgres_url: str | None = field(default_factory=lambda: os.getenv("PROVIDER_SIMENV_POSTGRES_URL")) def sqlalchemy_url(self) -> str: """ @@ -35,6 +37,10 @@ def sqlalchemy_url(self) -> str: Example: postgresql+psycopg2://postgres:postgres@localhost:5432/provider_simenv """ + if self.postgres_url: + # Accept either postgresql://... or postgresql+psycopg2://... + return self.postgres_url + return ( f"postgresql+psycopg2://{self.user}:{self.password}" f"@{self.host}:{self.port}/{self.dbname}" diff --git a/src/provider_simenv/main.py b/src/provider_simenv/main.py index e0d3890..07ec351 100644 --- a/src/provider_simenv/main.py +++ b/src/provider_simenv/main.py @@ -73,8 +73,22 @@ def csv_to_sqlite(output_dir: str, db_name: str = "provider-simenv.sqlite") -> N "with values derived from the PDL before the simulation runs." ), ) + parser.add_argument( + "--postgres-url", + type=str, + default=None, + metavar="URL", + help=( + "Optional PostgreSQL SQLAlchemy connection string for tick writes, " + "e.g. postgresql+psycopg2://user:pass@host:5432/dbname" + ), + ) args = parser.parse_args() + if args.postgres_url: + os.environ["PROVIDER_SIMENV_POSTGRES_URL"] = args.postgres_url + print("[main] Using PostgreSQL connection string from --postgres-url") + # Folder paths (both needed for PDL injection and Config) here = os.path.dirname(os.path.abspath(__file__)) input_folder = os.path.join(here, "data", "input") From 6b7d7e746c958aafdee57819398d16a64844b136 Mon Sep 17 00:00:00 2001 From: Lasse Hammer Date: Wed, 27 May 2026 14:21:54 +0200 Subject: [PATCH 2/2] Removed old main file --- src/provider-simenv/main.py | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/provider-simenv/main.py diff --git a/src/provider-simenv/main.py b/src/provider-simenv/main.py deleted file mode 100644 index c6d498a..0000000 --- a/src/provider-simenv/main.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(): - print("Hello from provider-simenv!") - - -if __name__ == "__main__": - main()