grpc4bmi.bmi_client_apptainer module

class grpc4bmi.bmi_client_apptainer.BmiClientApptainer(image: str, work_dir: str, input_dirs: Iterable[str] = (), delay=0, timeout=None, capture_logs=True)[source]

Bases: BmiClient

BMI GRPC client for model running inside a Apptainer container.

On instantization launches an Apptainer container. The Apptainer container image is expected to run a BMI GRPC server as its default command. The client picks a random port and expects the container to run the BMI GRPC server on that port. The port is passed to the container using the BMI_PORT environment variable.

Parameters:
  • image

    Apptainer image.

    For Docker Hub image use docker://* or convert it to a Apptainer image file.

    To convert Docker image ewatercycle/walrus-grpc4bmi with v0.2.0 tag to ./ewatercycle-walrus-grpc4bmi_v0.2.0.sif Apptainer image file use:

    apptainer pull ewatercycle-walrus-grpc4bmi_v0.2.0.sif \
      docker://ewatercycle/walrus-grpc4bmi:v0.2.0
    

  • input_dirs (Iterable[str]) –

    Directories for input files of model.

    All of directories will be mounted read-only inside Apptainer container on same path as outside container.

  • work_dir (str) –

    Working directory for model.

    Directory is mounted inside container and changed into.

    To create a random work directory you could use

    from tempfile import TemporaryDirectory
    from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
    
    work_dir = TemporaryDirectory()
    
    image = 'ewatercycle-walrus-grpc4bmi_v0.2.0.sif'
    client =  BmiClientApptainer(image, work_dir.name)
    
    # Write config to work_dir and interact with client
    
    # After checking output in work_dir, clean up
    work_dir.cleanup()
    

  • delay (int) –

    Seconds to wait for Apptainer container to startup, before connecting to it

    Increase when container takes a long time to startup.

  • timeout (int) –

    Seconds to wait for gRPC client to connect to server.

    By default will try forever to connect to gRPC server inside container. Set to low number to escape endless wait.

  • capture_logs (bool) –

    Whether to capture stdout and stderr of container .

    If false then redirects output to null device never to be seen again. If true then redirects output to temporary file which can be read with BmiClientApptainer.logs(). The temporary file gets removed when this object is deleted.

Example 1: Config file already inside image

MARRMoT has an example config file inside its Docker image.

from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
client = BmiClientApptainer(image='docker://ewatercycle/marrmot-grpc4bmi:latest',
                            work_dir='/opt/MARRMoT/BMI/Config')
client.initialize('/opt/MARRMoT/BMI/Config/BMI_testcase_m01_BuffaloRiver_TN_USA.mat')
client.update_until(client.get_end_time())
del client

Example 2: Config file in input directory

The config file and all other files the model needs are in a directory (/tmp/input). Use /tmp/work to capture any output files like logs generated by model.

from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
# Generate config file called 'config.mat' in `/tmp/input` directory
client = BmiClientApptainer(image='docker://ewatercycle/marrmot-grpc4bmi:latest',
                            input_dirs=['/tmp/input'],
                            work_dir='/tmp/work')
client.initialize('/tmp/input/config.mat')
client.update_until(client.get_end_time())
del client

Example 3: Read only input directory with config file in work directory

The forcing data is in a shared read-only location like /shared/forcings/walrus. In the config file (/tmp/work/walrus.yml) point to a forcing data file (/shared/forcings/walrus/PEQ_Hupsel.dat).

from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
client = BmiClientApptainer(image='ewatercycle-walrus-grpc4bmi_v0.2.0.sif',
                            input_dirs=['/shared/forcings/walrus'],
                            work_dir='/tmp/work')
client.initialize('walrus.yml')
client.update_until(client.get_end_time())
del client

Example 4: Model writes in sub directory of input directory

Some models, for example wflow, write output in a sub-directory of the input directory. If the input directory is set with the input_dirs argument then the model will be unable to write its output as input directories are mounted read-only. That will most likely cause the model to die. A workaround is to use the work_dir argument with input directory as value instead. This will make the whole input directory writable so the model can do its thing.

When input directory is on a shared disk where you do not have write permission then the input dir should be copied to a work directory (/scratch/wflow) so model can write.

from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
client = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
                            work_dir='/scratch/wflow')
client.initialize('wflow_sbm.ini')
client.update_until(client.get_end_time())
del client

Example 5: Inputs are in multiple directories

A model has its forcings (/shared/forcings/muese), parameters (/shared/model/wflow/staticmaps) and config file (/tmp/work/wflow_sbm.ini) in different locations. The config file should be set to point to the forcing and parameters files.

from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
client = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
                            input_dirs=['/shared/forcings/muese',
                                        '/shared/model/wflow/staticmaps'],
                            work_dir='/tmp/work')
client.initialize('wflow_sbm.ini')
client.update_until(client.get_end_time())
del client

Example 6: Run model twice with their own work directory

While running 2 or models at the same time you do not want the any config or output to be mixed.

from grpc4bmi.bmi_client_apptainer import BmiClientApptainer
client_muese = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
                                  work_dir='/scratch/wflow-meuse')
client_muese.initialize('wflow_sbm.meuse.ini')
client_rhine = BmiClientApptainer(image='docker://ewatercycle/wflow-grpc4bmi:latest',
                                  work_dir='/scratch/wflow-rhine')
client_rhine.initialize('wflow_sbm.rhine.ini')
...
# Run models and set/get values
...
del client_muese
del client_rhine
get_value_ref(var_name)[source]
logs() str[source]

Returns complete combined stdout and stderr written by the Apptainer container.

When object was created with log_enable=False argument then always returns empty string.

grpc4bmi.bmi_client_apptainer.check_apptainer_version()[source]
grpc4bmi.bmi_client_apptainer.check_apptainer_version_string(version_output: str) bool[source]