Skip to content

Hello World Application

Evan Carlin edited this page Nov 5, 2021 · 21 revisions

Creating a new sirepo application

Let's add a new sirepo app, named "myapp". Full source for the application can be found in the master sirepo branch.

First, create the schema for the application. The myapp schema contains a model named "dog" with a few basic attributes.

sirepo/package_data/static/json/myapp-schema.json

{
    "dynamicFiles": {
        "sirepoLibs": {
            "js": ["myapp.js"]
        }
    },
    "localRoutes": {
        "source": {
            "config": {
                "controller": "MyAppSourceController as source",
                "templateUrl": "/static/html/myapp-source.html"
            }
        }
    },
    "enum": {
        "Gender": [
            ["male", "Male"],
            ["female", "Female"]
        ],
        "DogDisposition": [
            ["aggressive", "Aggressive"],
            ["friendly", "Friendly"],
            ["submissive", "Submissive"]
        ]
    },
    "model": {
        "dog": {
            "breed": ["Breed", "String"],
            "gender": ["Gender", "Gender", "male"],
            "height": ["Height [cm]", "Float", 50.0, "Distance from front paws to withers"],
            "weight": ["Weight [lbs]", "Float", 60.5],
            "disposition": ["Disposition", "DogDisposition", "friendly"],
            "favoriteTreat": ["Favorite Treat", "OptionalString", ""]
        },
        "heightWeightReport": {}
    },
    "view": {
        "dog": {
            "title": "Dog",
            "basic": [
                "breed",
                "weight",
                "height",
                "disposition",
                "favoriteTreat"
            ],
            "advanced": [
                "breed",
                "gender",
                "weight",
                "height"
            ]
        },
        "heightWeightReport": {
            "title": "Physical Characteristics",
            "advanced": []
        }
    }
}

When a new simulation is created, the default-data.json file contains the initial values.

sirepo/package_data/template/myapp/default-data.json

{
    "models": {
        "dog": {
            "breed": "Labrador Retriever",
            "gender": "female"
        },
        "simulation": {}
    },
    "simulationType": "myapp"
}

Next we'll need the javascript application which contains an angularjs controller for the source page and the application header.

sirepo/package_data/static/js/myapp.js

'use strict';

var srlog = SIREPO.srlog;
var srdbg = SIREPO.srdbg;

SIREPO.app.controller('MyAppSourceController', function (appState, panelState, $scope) {
    var self = this;

    function handleDogDisposition() {
        panelState.showField('dog', 'favoriteTreat', appState.models.dog.disposition == 'friendly');
    }

    appState.whenModelsLoaded($scope, function() {
        // after the model data is available, hide/show the
        // favoriteTreat field depending on the disposition
	handleDogDisposition();
	appState.watchModelFields($scope, ['dog.disposition'], function() {
            // respond to changes in the disposition field value
            handleDogDisposition();
	});
    });
});

SIREPO.app.directive('appFooter', function() {
    return {
	restrict: 'A',
	scope: {
            nav: '=appFooter',
	},
        template: [
            '<div data-common-footer="nav"></div>',
	].join(''),
    };
});

SIREPO.app.directive('appHeader', function(appState, panelState) {
    return {
	restrict: 'A',
	scope: {
            nav: '=appHeader',
	},
        template: [
            '<div data-app-header-brand="nav"></div>',
            '<div data-app-header-left="nav"></div>',
            '<div data-app-header-right="nav">',
              '<app-header-right-sim-loaded>',
		'<div data-sim-sections="">',
                  '<li class="sim-section" data-ng-class="{active: nav.isActive(\'source\')}"><a href data-ng-click="nav.openSec\
tion(\'source\')"><span class="glyphicon glyphicon-flash"></span> Source</a></li>',
		'</div>',
              '</app-header-right-sim-loaded>',
              '<app-settings>',
		//  '<div>App-specific setting item</div>',
              '</app-settings>',
              '<app-header-right-sim-list>',
              '</app-header-right-sim-list>',
            '</div>',
	].join(''),
    };
});

The myapp.js refers to the myapp-source.html file for the definition of the "Source" page.

sirepo/package_data/static/html/myapp-source.html

<div class="container-fluid">
  <div class="row">
    <div class="col-md-6">
        <div data-basic-editor-panel="" data-view-name="dog"></div>
    </div>
    <div class="col-md-6">
      <div data-report-panel="parameter" data-model-name="heightWeightReport"></div>
    </div>
  </div>
</div>

The myapp-source.html will arrange the dog editor and report.



The server components are comprised of the application code generator (template/myapp.py) and the executor (pkcli/myapp.py).

sirepo/template/myapp.py

# -*- coding: utf-8 -*-
u"""Myapp execution template.

:copyright: Copyright (c) 2017-2018 RadiaSoft LLC.  All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""
from __future__ import absolute_import, division, print_function
from pykern import pkcollections
from pykern import pkio
from pykern import pkjinja
from pykern.pkdebug import pkdc, pkdp
from sirepo import simulation_db
from sirepo.template import template_common
import copy


SIM_TYPE = 'myapp'

INPUT_NAME = 'hundli.yml'

OUTPUT_NAME = 'hundli.csv'

_SCHEMA = simulation_db.get_schema(SIM_TYPE)


def fixup_old_data(data):
    for m in _SCHEMA.model:
	if m not in data.models:
	    data.models[m] = pkcollections.Dict({})
	template_common.update_model_defaults(data.models[m], m, _SCHEMA)


def get_data_file(run_dir, model, frame, options=None):
    f = run_dir.join(OUTPUT_NAME)
    return f.basename, f.read(), 'text/csv'


def lib_files(data, source_lib):
    return []


def models_related_to_report(data):
    r = data['report']
    return [
	r,
        'dog',
    ]


def python_source_for_model(data, model):
    return _generate_parameters_file(data)


def write_parameters(data, run_dir, is_parallel):
    pkio.write_text(
	run_dir.join(template_common.PARAMETERS_PYTHON_FILE),
	_generate_parameters_file(data),
    )


def _generate_parameters_file(data):
    if 'report' in data:
	assert data['report'] == 'heightWeightReport', \
            'unknown report: {}'.format(data['report'])
    v = copy.deepcopy(data['models'], pkcollections.Dict())
    v.input_name = INPUT_NAME
    v.output_name = OUTPUT_NAME
    return template_common.render_jinja(
        SIM_TYPE,
        v,
        template_common.PARAMETERS_PYTHON_FILE,
    )

sirepo/pkcli/myapp.py

# -*- coding: utf-8 -*-
"""Wrapper to run myapp from the command line.

:copyright: Copyright (c) 2017 RadiaSoft LLC.  All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""
from __future__ import absolute_import, division, print_function
from pykern import pkio
from pykern import pksubprocess
from pykern.pkdebug import pkdp, pkdc, pkdlog
from sirepo import simulation_db
from sirepo.template import template_common
import csv
import sirepo.template.myapp as template
import sys


_SCHEMA = simulation_db.get_schema(template.SIM_TYPE)


def run(cfg_dir):
    with pkio.save_chdir(cfg_dir):
	try:
            pksubprocess.check_call_with_signals(
                [sys.executable, template_common.PARAMETERS_PYTHON_FILE],
            )
        except Exception as e:
	    pkdlog('script failed: dir={} err={}', cfg_dir, e)
	    simulation_db.write_result({
                'error': 'program error occured',
            })
            return
        data = simulation_db.read_json(template_common.INPUT_BASE_NAME)
        if data.report == 'heightWeightReport':
            res = _report(
		'Dog Height and Weight Over Time',
                ('height', 'weight'),
                data,
	    )
	else:
            raise AssertionError('unknown report: {}'.format(data.report))
    simulation_db.write_result(res)


def _csv_to_cols():
    with open(template.OUTPUT_NAME, 'r') as f:
	rows = csv.reader(f)
        headers = rows.next()
        cols = [[] for _ in headers]
        for row in rows:
            for i, c in enumerate(row):
	        cols[i].append(float(c))
    return dict((k.lower(), cols[i]) for i, k in enumerate(headers))


def _label(field):
    return _SCHEMA.model.dog[field][0]


def _plot(dog, field, cols):
    return {
	'name': field,
	'label': _label(field),
	'points': cols[field],
    }


def _report(title, fields, data):
    dog = data.models.dog
    cols = _csv_to_cols()
    x_points = cols['year']
    plots = [_plot(dog, f, cols) for f in fields]
    return {
        'title': title,
        'x_range': [x_points[0], x_points[-1]],
        'y_label': _label(fields[0]) if len(fields) == 1 else '',
        'x_label': 'Age (years)',
        'x_points': x_points,
        'plots': plots,
        'y_range': template_common.compute_plot_color_and_range(plots),
    }

Now the application is ready to be added to the sirepo configuration files:

sirepo/feature_config.py

...
#: Codes on dev and alpha:
_NON_PROD_FOSS_CODES = frozenset(('myapp', ))

sirepo/package_data/static/json/schema-common.json

    "appInfo": {
        "myapp": {
            "longName": "MyApp",
            "shortName": "MyApp"
        },
    ...

The app is ready to run. Start the sirepo server from the command line:

$ sirepo service http

Then open your web browser http://localhost:8000/myapp

Clone this wiki locally