Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path to pdl file directory>:/scenarios provider-simenv --pdl /scenarios/<pdl filename>
```

You can also configure a PostgreSQL interface for data storage (see next chapter) with

```bash
docker run --rm -v <path to pdl file directory>:/scenarios provider-simenv --pdl /scenarios/<pdl filename> --postgres-url <PostgreSQL URL string>
```

---

## PostgreSQL Setup (Optional)

PostgreSQL enables live data access during the simulation — required for future palaestrAI
Expand Down Expand Up @@ -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 <path to pdl file>
```
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 |
Expand Down
23 changes: 23 additions & 0 deletions compose-up.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -lt 1 ]]; then
echo "Usage: $0 <path-to-pdl-file> [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 "$@"
37 changes: 37 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
6 changes: 0 additions & 6 deletions src/provider-simenv/main.py

This file was deleted.

16 changes: 11 additions & 5 deletions src/provider_simenv/db_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
print(cfg) # safe repr, password masked
"""

import os
from dataclasses import dataclass, field

@dataclass
Expand All @@ -23,18 +24,23 @@ class PostgresDBConfig:
- palaestrAI: host=<server>, port=<port>, user=<user>, password=<pw>
"""

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:
"""
Return a SQLAlchemy-compativbe connection URL.

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}"
Expand Down
14 changes: 14 additions & 0 deletions src/provider_simenv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down