Commit c52ffac8 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'experimental/docker'

parents ec43e319 10f2a195
.venv
.git
.pytest_cache
.mypy_cache
*.py[oc]
dist/
docs/
*.egg-info
# Repo cache, e.g. during SimStadt tests
**/.cache/
FROM python:3.11-slim AS base
ARG DEBIAN_FRONTEND=noninteractive
ARG JRE_VERSION=17.0.18+10
ARG JRE_DEB="bellsoft-jre${JRE_VERSION}-linux-amd64-full.deb"
ARG JRE_URL=https://download.bell-sw.com/java/${JRE_VERSION}/${JRE_DEB}
ARG INSEL_VERSION=8.3.4.0b
ARG INSEL_DEB="insel_${INSEL_VERSION}_x64_mini.deb"
ARG INSEL_URL=https://insel.eu/download/${INSEL_DEB}
# Java 17 + INSEL
RUN apt-get update && \
apt-get install curl --no-install-recommends -y && \
curl ${JRE_URL} -o /tmp/${JRE_DEB} -k && \
curl ${INSEL_URL} -o /tmp/${INSEL_DEB} -k && \
apt-get install /tmp/${JRE_DEB} /tmp/${INSEL_DEB} --no-install-recommends -y && \
apt-get remove curl -y && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean && \
rm /tmp/${JRE_DEB} /tmp/${INSEL_DEB}
# SRA
RUN apt-get update && \
apt-get install libglu1-mesa --no-install-recommends -y && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean
ADD https://simstadt.hft-stuttgart.de/download/InstallFiles/citysim_sra /usr/local/bin/
RUN chmod a+rx /usr/local/bin/citysim_sra
ADD https://simstadt.hft-stuttgart.de/download/InstallFiles/libshortwave.so /usr/lib/
RUN chmod a+r /usr/lib/libshortwave.so
RUN groupadd -g 1000 simstadt \
&& useradd simstadt --create-home --shell /bin/bash -u 1000 -g 1000
COPY --from=ghcr.io/astral-sh/uv:0.12.17 /uv /uvx /usr/local/bin/
RUN mkdir -p /opt/venv && chown simstadt:simstadt /opt/venv
ENV VIRTUAL_ENV=/opt/venv
ENV UV_PROJECT_ENVIRONMENT=/opt/venv
ENV PATH="/opt/venv/bin:${PATH}"
USER simstadt
RUN mkdir -p /home/simstadt/app /home/simstadt/Desktop
WORKDIR /home/simstadt/app
FROM base AS cli
COPY --chown=simstadt:simstadt pyproject.toml uv.lock README.md ./
RUN uv sync --frozen --no-install-project
RUN uv pip install simstadt
# Lets `simstadt <template-name> <citygml>` work out of the box using the templates
# bundled with the PyPI package, without requiring users to mount/supply their own.
ENV SIMSTADT_TEMPLATE_PATH=/opt/venv/lib/python3.11/site-packages/simstadt/templates
RUN simstadt --install
COPY --chown=simstadt:simstadt tests ./tests
CMD ["simstadt"]
...@@ -18,6 +18,22 @@ simple_tests: ## Run simple tests, without running SimStadt ...@@ -18,6 +18,22 @@ simple_tests: ## Run simple tests, without running SimStadt
simstadt: ## Run simstadt script, and display info about SimStadt simstadt: ## Run simstadt script, and display info about SimStadt
uv run simstadt uv run simstadt
shell: ## Start bash
@echo "${green}Start shell${no_color}"
docker run --rm -it simstadt/simstadt:cli bash
docker-build: ## Build the cli Docker image
docker build --target cli -t simstadt/simstadt:cli .
docker-test: docker-build ## Run the full test suite (incl. integration tests) against the published PyPI package inside Docker
docker run --rm simstadt/simstadt:cli uv run --no-sync pytest -v
_check_docker_push:
@echo -n "Are you sure you want to push the Docker images to Docker Hub? [y/N] " && read ans && [ $${ans:-N} = y ]
docker-push: _check_docker_push docker-build ## Push the cli image to Docker Hub (requires prior `docker login`)
docker push simstadt/simstadt:cli
tests_installed_package: ## Tests the installed simstadt package tests_installed_package: ## Tests the installed simstadt package
pytest --pyargs simstadt -v pytest --pyargs simstadt -v
...@@ -47,4 +63,4 @@ _upload: ...@@ -47,4 +63,4 @@ _upload:
install_editable: ## Install package in dev mode install_editable: ## Install package in dev mode
pip install -e . pip install -e .
.PHONY: help tests simstadt build coverage .PHONY: help tests simstadt build coverage docker-build docker-test docker-push
...@@ -41,9 +41,9 @@ For more control, use `run_workflow_with_citygml` directly: ...@@ -41,9 +41,9 @@ For more control, use `run_workflow_with_citygml` directly:
from simstadt import run_workflow_with_citygml from simstadt import run_workflow_with_citygml
results = run_workflow_with_citygml( results = run_workflow_with_citygml(
template="104_HeatDemandWithShadow", # name in templates/ or full Path template="HeatDemandWithShadow", # name in templates/ or full Path
citygml_path="path/to/city.gml", citygml_path="path/to/city.gml",
replaces={"<string>METEONORM_FILE</string>": "<string>Wuerzburg-hour.csv</string>"}, replaces={"<string>METEONORM_FILE</string>": "<string>Wuerzburg-hour.csv</string>"}, # optional
project_path=Path("path/to/project.proj"), # optional project_path=Path("path/to/project.proj"), # optional
) )
print(results.kpis) print(results.kpis)
...@@ -63,12 +63,12 @@ simstadt ...@@ -63,12 +63,12 @@ simstadt
# See help # See help
simstadt --help simstadt --help
usage: simstadt [-h] [--gui] [--csv-export] [--install] [-d DESCRIPTION] [--destination DESTINATION] [-p PROJECT_PATH] [-f] [-s PATH] [-v] [template] [citygml] usage: simstadt [-h] [--gui] [--csv-export] [--install] [-d DESCRIPTION] [--destination DESTINATION] [-p PROJECT_PATH] [-f] [-s PATH] [-v] [-V] [template] [citygml]
simstadt - Python library for SimStadt workflows. simstadt - Python library for SimStadt workflows.
positional arguments: positional arguments:
template Template name (from SIMSTADT_TEMPLATE_PATH) or path to a .flow directory. template Template name (from SIMSTADT_TEMPLATE_PATH), path to a .flow directory, or a bundled template name.
citygml Path to the CityGML input file. citygml Path to the CityGML input file.
options: options:
...@@ -85,6 +85,10 @@ simstadt --help ...@@ -85,6 +85,10 @@ simstadt --help
-f, --files Show output files after the run. -f, --files Show output files after the run.
-s PATH, --save PATH Save result DataFrame to a file (.csv or .json). -s PATH, --save PATH Save result DataFrame to a file (.csv or .json).
-v, --verbose Enable debug logging. -v, --verbose Enable debug logging.
-V, --version Print the simstadtpy and SimStadt versions, then exit.
# Print the simstadtpy and SimStadt versions
simstadt --version
# Download and install the latest SimStadt release to ~/Desktop # Download and install the latest SimStadt release to ~/Desktop
simstadt --install simstadt --install
...@@ -93,10 +97,19 @@ simstadt --install ...@@ -93,10 +97,19 @@ simstadt --install
simstadt --gui simstadt --gui
# Run a workflow from the command line # Run a workflow from the command line
simstadt 104_HeatDemandWithShadow path/to/city.gml -v simstadt HeatDemandWithShadow.flow path/to/city.gml -v
# Save results to CSV or JSON
simstadt PVWithShadow path/to/city.gml --save results.csv
# Save results to CSV or JSON # Save results to CSV or JSON
simstadt 104_HeatDemandWithShadow path/to/city.gml --save results.csv simstadt HeatDemand path/to/city.gml --save results.csv
# List output files
simstadt HeatDemand.flow path/to/city.gml --files
# Debug information
simstadt PV path/to/city.gml --verbose
# Run RegionChooser (separate entry point) # Run RegionChooser (separate entry point)
regionchooser regionchooser
......
...@@ -17,5 +17,17 @@ ...@@ -17,5 +17,17 @@
* [x] Once this library is written, it should be used in LCCALC & SektorSim * [x] Once this library is written, it should be used in LCCALC & SektorSim
* [ ] Write documentation * [ ] Write documentation
* [X] Publish to pipy * [x] Publish to pipy
* [x] Write a basic makefile with doc / publish / tests * [x] Write a basic makefile with doc / publish / tests
* [ ] Display clear location of workflow
### Docker
* [ ] Readme and examples at https://hub.docker.com/repository/docker/simstadt/simstadt
* [ ] Fix simstadt CLI, with easier template names
* [ ] Docker compose
* [ ] Example with dataframe & KPIs
* [ ] Doc to https://simstadt.hft-stuttgart.de/
* [ ] latest tag as cli?
[project] [project]
name = "simstadt" name = "simstadt"
version = "0.2.11" version = "0.3.0"
description = "Python library for using and testing SimStadt workflows." description = "Python library for using and testing SimStadt workflows."
readme = "README.md" readme = "README.md"
authors = [ authors = [
......
...@@ -50,8 +50,26 @@ def cmd_info(args: argparse.Namespace) -> None: ...@@ -50,8 +50,26 @@ def cmd_info(args: argparse.Namespace) -> None:
print("SimStadt version: unknown (could not parse version string)") print("SimStadt version: unknown (could not parse version string)")
def cmd_version() -> None:
from importlib.metadata import version as pkg_version
from .runner import get_version_and_date
print(f"simstadtpy: {pkg_version('simstadt')}")
try:
version, date = get_version_and_date()
yyyy, mm, dd = date[:4], date[4:6], date[6:8]
print(f"SimStadt: {version} ({yyyy}-{mm}-{dd})")
except ValueError as e:
print(f"SimStadt: not found\n{e}", file=sys.stderr)
def cmd_run(args: argparse.Namespace) -> None: def cmd_run(args: argparse.Namespace) -> None:
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARNING) logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.WARNING,
format="%(levelname)s: %(message)s",
datefmt="[%X]",
)
from .runner import run_workflow_with_citygml from .runner import run_workflow_with_citygml
...@@ -71,7 +89,7 @@ def cmd_run(args: argparse.Namespace) -> None: ...@@ -71,7 +89,7 @@ def cmd_run(args: argparse.Namespace) -> None:
print(f"Error: {e}", file=sys.stderr) print(f"Error: {e}", file=sys.stderr)
raise SystemExit(1) raise SystemExit(1)
print(f"\n{results.description}\n") print(f"{results.description}\n")
for kpi in results.kpis: for kpi in results.kpis:
print(f" {kpi}") print(f" {kpi}")
...@@ -94,7 +112,11 @@ def build_parser() -> argparse.ArgumentParser: ...@@ -94,7 +112,11 @@ def build_parser() -> argparse.ArgumentParser:
description="simstadt - Python library for SimStadt workflows.", description="simstadt - Python library for SimStadt workflows.",
) )
parser.add_argument("--gui", action="store_true", help="Launch the SimStadt GUI.") parser.add_argument("--gui", action="store_true", help="Launch the SimStadt GUI.")
parser.add_argument("--csv-export", action="store_true", help="Export CSV from workflowsteps, when available.") parser.add_argument(
"--csv-export",
action="store_true",
help="Export CSV from workflowsteps, when available.",
)
parser.add_argument( parser.add_argument(
"--install", "--install",
action="store_true", action="store_true",
...@@ -103,7 +125,7 @@ def build_parser() -> argparse.ArgumentParser: ...@@ -103,7 +125,7 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument( parser.add_argument(
"template", "template",
nargs="?", nargs="?",
help="Template name (from SIMSTADT_TEMPLATE_PATH) or path to a .flow directory.", help="Template name (from SIMSTADT_TEMPLATE_PATH), path to a .flow directory, or a bundled template name.",
) )
parser.add_argument( parser.add_argument(
"citygml", nargs="?", type=Path, help="Path to the CityGML input file." "citygml", nargs="?", type=Path, help="Path to the CityGML input file."
...@@ -134,6 +156,12 @@ def build_parser() -> argparse.ArgumentParser: ...@@ -134,6 +156,12 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument( parser.add_argument(
"-v", "--verbose", action="store_true", help="Enable debug logging." "-v", "--verbose", action="store_true", help="Enable debug logging."
) )
parser.add_argument(
"-V",
"--version",
action="store_true",
help="Print the simstadtpy and SimStadt versions, then exit.",
)
return parser return parser
...@@ -141,6 +169,10 @@ def simstadt_app() -> None: ...@@ -141,6 +169,10 @@ def simstadt_app() -> None:
parser = build_parser() parser = build_parser()
args = parser.parse_args() args = parser.parse_args()
if args.version:
cmd_version()
return
if args.install: if args.install:
cmd_install() cmd_install()
......
...@@ -56,16 +56,25 @@ def find_simstadt(parent_folder: Path, simstadt_glob: str = SIMSTADT2_GLOB) -> P ...@@ -56,16 +56,25 @@ def find_simstadt(parent_folder: Path, simstadt_glob: str = SIMSTADT2_GLOB) -> P
def get_simstadt_folder() -> Path: def get_simstadt_folder() -> Path:
value = os.environ.get("SIMSTADT_FOLDER", "").strip() value = os.environ.get("SIMSTADT_FOLDER", "").strip()
if value: if value:
return Path(value) path = Path(value)
if not path.exists():
raise ValueError(f"SIMSTADT_FOLDER={value!r} does not exist.")
return path
return find_simstadt(Path.home() / "Desktop") return find_simstadt(Path.home() / "Desktop")
def get_template_path() -> Path: def get_template_path() -> Path:
if "SIMSTADT_TEMPLATE_PATH" in os.environ: if "SIMSTADT_TEMPLATE_PATH" in os.environ:
return Path(os.environ["SIMSTADT_TEMPLATE_PATH"]) return Path(os.environ["SIMSTADT_TEMPLATE_PATH"])
fallback = Path.cwd() / "templates" local_templates = Path.cwd() / "templates"
if fallback.exists(): if local_templates.exists():
return fallback return local_templates
bundled_templates = Path(__file__).parent / "templates"
if bundled_templates.exists():
log.info("SIMSTADT_TEMPLATE_PATH undefined, and templates/ not present. Trying bundled templates.")
for template in bundled_templates.glob("*.flow"):
log.debug("Available template : %s", template.name)
return bundled_templates
raise ValueError( raise ValueError(
"No template path found. Set SIMSTADT_TEMPLATE_PATH or create a templates/ directory." "No template path found. Set SIMSTADT_TEMPLATE_PATH or create a templates/ directory."
) )
...@@ -170,6 +179,7 @@ def copy_workflow_from_template( ...@@ -170,6 +179,7 @@ def copy_workflow_from_template(
replaces: dict | None = None, replaces: dict | None = None,
) -> Path: ) -> Path:
template_path = template_path.with_suffix(".flow") template_path = template_path.with_suffix(".flow")
log.info("Using template from %s", template_path)
workflow_path = (project_path / destination).with_suffix(".flow") workflow_path = (project_path / destination).with_suffix(".flow")
shutil.copytree(template_path, workflow_path, dirs_exist_ok=True) shutil.copytree(template_path, workflow_path, dirs_exist_ok=True)
if replaces: if replaces:
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<void property="weatherDataSource"> <void property="weatherDataSource">
<object class="java.lang.Enum" method="valueOf"> <object class="java.lang.Enum" method="valueOf">
<class>de.hft.stuttgart.simstadt2.weather.WeatherDataSourceType</class> <class>de.hft.stuttgart.simstadt2.weather.WeatherDataSourceType</class>
<string>TMY3_HOURLY_FILE</string> <string>PVGIS_DATABASE</string>
</object> </object>
</void> </void>
</object> </object>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment