Skip to content

Commit b344ef0

Browse files
committed
Fix bug in console_scripts file
Add validate_model_submission method for validating repository submissions Add description and contact_email fields to the model schema
1 parent 246c771 commit b344ef0

File tree

10 files changed

+146
-48
lines changed

10 files changed

+146
-48
lines changed

HISTORY.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ History
1717
* Added methods for waiting on a function to return True
1818
* Refactored multitasking classes to use __slots__ for improved memory performance
1919
* Added tools for building yggdrasil docker containers and documentation on those tools
20-
* Added repository_url parameter to model YAML schema that allows for a repository to be specified in the YAML (in addition to via the command line as before)
20+
* Added repository_url, description, and contact_email parameters to model YAML schema that allows for a repository to be specified in the YAML (in addition to via the command line as before)
2121
* Added model_only and model_submission options to yggdrasil.yamfile.parse_yaml method and YAML validation CLI
2222
* Added generated documentation on command line utilities
2323
* Added a glossary of terms
2424
* Included downloadable versions of the schemas in the documentation
2525
* Moved console_scripts list into a text file
2626
* Note deprecation of the old GUI in the docs for the 2018 & 2019 hackathons
27+
* Added validate_model_submission method for validating model repository submissions
2728

2829
1.7.0 (2021-08-26) Support for MPI communicators, MPI execution, and pika >= 1.0.0
2930
------------------

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ include *.svg
44
include *.txt
55
recursive-include yggdrasil *.cfg
66
recursive-include yggdrasil *.yml
7+
recursive-include yggdrasil *.yaml
78
recursive-include yggdrasil *.sh
89
recursive-include yggdrasil *.m
910
recursive-include yggdrasil *.R

console_scripts.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ ygginstall=yggdrasil.command_line:ygginstall
2323
yggclean=yggdrasil.command_line:yggclean
2424
yggmodelform=yggdrasil.command_line:yggmodelform
2525
yggdevup=yggdrasil.command_line:yggdevup
26-
ygggha=yggdrasil.command_line:generate_gha_workflow'
26+
ygggha=yggdrasil.command_line:generate_gha_workflow

yggdrasil/.ygg_schema.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ definitions:
181181
type: array
182182
required:
183183
- datatype
184-
- commtype
185184
- name
185+
- commtype
186186
title: comm_base
187187
type: object
188188
- anyOf:
@@ -370,8 +370,8 @@ definitions:
370370
- $ref: '#/definitions/transform'
371371
type: array
372372
required:
373-
- outputs
374373
- inputs
374+
- outputs
375375
title: connection_base
376376
type: object
377377
- anyOf:
@@ -705,8 +705,8 @@ definitions:
705705
type: string
706706
required:
707707
- filetype
708-
- working_dir
709708
- name
709+
- working_dir
710710
title: file_base
711711
type: object
712712
- anyOf:
@@ -1319,6 +1319,10 @@ definitions:
13191319
description: Build type/configuration that should be built. Defaults to
13201320
'Release'.
13211321
type: string
1322+
contact_email:
1323+
description: Email address that should be used to contact the maintainer
1324+
of the model. This parameter is only used in the model repository.
1325+
type: string
13221326
copies:
13231327
default: 1
13241328
description: The number of copies of the model that should be created. Defaults
@@ -1332,6 +1336,10 @@ definitions:
13321336
use any of the files located there since OSR always assumes the included
13331337
file paths are relative. Defaults to False.
13341338
type: boolean
1339+
description:
1340+
description: Description of the model. This parameter is only used in the
1341+
model repository or when providing the model as a service.
1342+
type: string
13351343
driver:
13361344
description: '[DEPRECATED] Name of driver class that should be used.'
13371345
type: string
@@ -1793,10 +1801,10 @@ definitions:
17931801
is used.
17941802
type: string
17951803
required:
1796-
- language
17971804
- args
1798-
- working_dir
17991805
- name
1806+
- working_dir
1807+
- language
18001808
title: model_base
18011809
type: object
18021810
- anyOf:

yggdrasil/drivers/ModelDriver.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ class ModelDriver(Driver):
173173
the model source code. If provided, relative paths in the model
174174
YAML definition will be considered relative to the repository root
175175
directory.
176+
description (str, optional): Description of the model. This parameter
177+
is only used in the model repository or when providing the model
178+
as a service.
179+
contact_email (str, optional): Email address that should be used to
180+
contact the maintainer of the model. This parameter is only used
181+
in the model repository.
176182
**kwargs: Additional keyword arguments are passed to parent class.
177183
178184
Class Attributes:
@@ -281,6 +287,12 @@ class ModelDriver(Driver):
281287
source code. If provided, relative paths in the model YAML
282288
definition will be considered relative to the repository root
283289
directory.
290+
description (str): Description of the model. This parameter is only
291+
used in the model repository or when providing the model as a
292+
service.
293+
contact_email (str): Email address that should be used to contact the
294+
maintainer of the model. This parameter is only used in the model
295+
repository.
284296
285297
Raises:
286298
RuntimeError: If both with_strace and with_valgrind are True.
@@ -380,7 +392,9 @@ class ModelDriver(Driver):
380392
'logging_level': {'type': 'string', 'default': ''},
381393
'allow_threading': {'type': 'boolean'},
382394
'copies': {'type': 'integer', 'default': 1, 'minimum': 1},
383-
'repository_url': {'type': 'string'}}
395+
'repository_url': {'type': 'string'},
396+
'description': {'type': 'string'},
397+
'contact_email': {'type': 'string'}}
384398
_schema_excluded_from_class = ['name', 'language', 'args', 'working_dir']
385399
_schema_excluded_from_class_validation = ['inputs', 'outputs']
386400

yggdrasil/schema.py

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,46 @@ def form_schema(self):
886886
out = convert_extended2base(out)
887887
return out
888888

889+
@property
890+
def model_form_schema_props(self):
891+
r"""dict: Information about how properties should be modified for the
892+
model form schema."""
893+
prop = {
894+
# 'add': {},
895+
'replace': {
896+
'comm': {
897+
'transform': {
898+
"type": "array",
899+
"items": {"$ref": "#/definitions/transform"}}}},
900+
'required': {
901+
'model': ['args', 'inputs', 'outputs', 'description',
902+
'repository_url']},
903+
'remove': {
904+
'comm': ['is_default', 'length_map', 'serializer',
905+
'address', 'dont_copy', 'for_service',
906+
'send_converter', 'recv_converter', 'client_id',
907+
'cookies', 'host', 'params', 'port'],
908+
'file': ['is_default', 'length_map',
909+
'wait_for_creation', 'working_dir',
910+
'read_meth', 'in_temp',
911+
'serializer', 'datatype',
912+
'address', 'dont_copy', 'for_service',
913+
'send_converter', 'recv_converter', 'client_id',
914+
'cookies', 'host', 'params', 'port'],
915+
'model': ['client_of', 'is_server', 'preserve_cache',
916+
'products', 'source_products', 'working_dir',
917+
'overwrite', 'skip_interpreter', 'copies',
918+
'timesync', 'with_strace', 'with_valgrind',
919+
'valgrind_flags', 'additional_variables',
920+
'aggregation', 'interpolation', 'synonyms',
921+
'driver']},
922+
'order': {
923+
'model': ['name', 'repository_url', 'contact_email',
924+
'language', 'description', 'args', 'inputs',
925+
'outputs']},
926+
}
927+
return prop
928+
889929
@property
890930
def model_form_schema(self):
891931
r"""dict: Schema for generating a model YAML form."""
@@ -934,61 +974,30 @@ def model_form_schema(self):
934974
out['definitions'][x]['properties'][k].pop('oneOf', None)
935975
out['definitions'][x]['properties'][k].update(
936976
{"$ref": "#/definitions/serializer"})
937-
prop_add = {
938-
'model': {'contact_email': {'type': 'string'}}}
939-
prop_replace = {
940-
'comm': {
941-
'transform': {
942-
"type": "array",
943-
"items": {"$ref": "#/definitions/transform"}}}}
944-
prop_required = {
945-
'model': ['args', 'inputs', 'outputs', 'repository_url']}
946-
prop_remove = {
947-
'comm': ['is_default', 'length_map', 'serializer',
948-
'address', 'dont_copy', 'for_service',
949-
'send_converter', 'recv_converter', 'client_id',
950-
'cookies', 'host', 'params', 'port'],
951-
'file': ['is_default', 'length_map',
952-
'wait_for_creation', 'working_dir',
953-
'read_meth', 'in_temp',
954-
'serializer', 'datatype',
955-
'address', 'dont_copy', 'for_service',
956-
'send_converter', 'recv_converter', 'client_id',
957-
'cookies', 'host', 'params', 'port'],
958-
'model': ['client_of', 'is_server', 'preserve_cache',
959-
'products', 'source_products', 'working_dir',
960-
'overwrite', 'skip_interpreter', 'copies',
961-
'timesync', 'with_strace', 'with_valgrind',
962-
'valgrind_flags', 'additional_variables',
963-
'aggregation', 'interpolation', 'synonyms',
964-
'driver']}
965-
prop_order = {
966-
'model': ['name', 'repository_url', 'contact_email', 'language',
967-
'args', 'inputs', 'outputs']
968-
}
977+
prop = self.model_form_schema_props
969978
for k in ['inputs', 'outputs']:
970979
out['definitions']['model']['properties'][k].pop('default', None)
971980
desc = out['definitions']['model']['properties'][k]['description'].split(
972981
' A full description')[0]
973982
out['definitions']['model']['properties'][k]['description'] = desc
974983
out['definitions']['model']['properties']['args']['minItems'] = 1
975-
for k, rlist in prop_remove.items():
984+
for k, rlist in prop['remove'].items():
976985
for p in rlist:
977986
out['definitions'][k]['properties'].pop(p, None)
978-
for k, rdict in prop_replace.items():
987+
for k, rdict in prop['replace'].items():
979988
for r in rdict.keys():
980989
if 'description' in out['definitions'][k]['properties'][r]:
981990
rdict[r]['description'] = (
982991
out['definitions'][k]['properties'][r]['description'])
983992
out['definitions'][k]['properties'].update(rdict)
984-
for k, rlist in prop_required.items():
993+
for k, rlist in prop['required'].items():
985994
out['definitions'][k].setdefault('required', [])
986995
for p in rlist:
987996
if p not in out['definitions'][k]['required']:
988997
out['definitions'][k]['required'].append(p)
989-
for k, adict in prop_add.items():
990-
out['definitions'][k]['properties'].update(adict)
991-
for k, rlist in prop_order.items():
998+
# for k, adict in prop['add'].items():
999+
# out['definitions'][k]['properties'].update(adict)
1000+
for k, rlist in prop['order'].items():
9921001
for i, p in enumerate(rlist):
9931002
out['definitions'][k]['properties'][p]['propertyOrder'] = i
9941003
out.update(out['definitions'].pop('model'))

yggdrasil/services.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,3 +1112,28 @@ def add(self, name, yamls=None, **kwargs):
11121112
f" Registry:\n{old}\n New:\n{new}")
11131113
registry[k] = v
11141114
self.save(registry)
1115+
1116+
1117+
def validate_model_submission(fname):
1118+
r"""Validate a YAML file according to the standards for submission to
1119+
the yggdrasil model repository.
1120+
1121+
Args:
1122+
fname (str): YAML file to validate.
1123+
1124+
"""
1125+
import glob
1126+
from yggdrasil import yamlfile, runner
1127+
# 1-2. YAML syntax and schema
1128+
yml = yamlfile.parse_yaml(fname, model_submission=True)
1129+
# 3a. LICENSE
1130+
repo_dir = yml['models'][0]['working_dir']
1131+
patterns = ['LICENSE', 'LICENSE.*']
1132+
for x in patterns:
1133+
if ((glob.glob(os.path.join(repo_dir, x.upper()))
1134+
or glob.glob(os.path.join(repo_dir, x.lower())))):
1135+
break
1136+
else:
1137+
raise RuntimeError("Model repository does not contain a LICENSE file.")
1138+
# 4. Run
1139+
runner.run(fname)

yggdrasil/tests/test_services.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import logging
99
from contextlib import contextmanager
1010
from yggdrasil.services import (
11-
IntegrationServiceManager, create_service_manager_class, ServerError)
11+
IntegrationServiceManager, create_service_manager_class, ServerError,
12+
validate_model_submission)
1213
from yggdrasil.examples import yamls as ex_yamls
1314
from yggdrasil.tests import assert_raises, requires_language
1415
from yggdrasil import runner, import_as_function, platform
@@ -331,3 +332,18 @@ def test_calling_service_as_function(self, running_service):
331332
fmodel.stop()
332333
finally:
333334
cli.registry.remove(name)
335+
336+
337+
def test_validate_model_submission():
338+
r"""Test validate_model_submission"""
339+
import git
340+
try:
341+
fname = os.path.join(os.path.dirname(__file__), 'yamls',
342+
'FakePlant.yaml')
343+
validate_model_submission(fname)
344+
os.remove(os.path.join('cropsinsilico', 'example-fakemodel',
345+
'LICENSE'))
346+
assert_raises(RuntimeError, validate_model_submission, fname)
347+
finally:
348+
if os.path.isfile('cropsinsilico/example-fakemodel/fakemodel.yml'):
349+
git.rmtree("cropsinsilico")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
model:
2+
name: FakePlant
3+
args:
4+
- src/fakemodel.py
5+
- --yggdrasil
6+
language: python
7+
inputs:
8+
- name: photosynthesis_rate
9+
commtype: default
10+
datatype:
11+
type: bytes
12+
default_file:
13+
name: ./Input/input.txt
14+
filetype: table
15+
outputs:
16+
- name: growth_rate
17+
commtype: default
18+
datatype:
19+
type: bytes
20+
default_file:
21+
name: ./Output/output.txt
22+
filetype: table
23+
repository_url: https://github.com/cropsinsilico/example-fakemodel
24+
description: Example model submission

yggdrasil/yamlfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def parse_yaml(files, complete_partial=False, partial_commtype=None,
295295
existing = parse_component(yml, k[:-1], existing=existing)
296296
# Exit early
297297
if model_only:
298-
return existing
298+
return yml_norm
299299
# Add stand-in model that uses unpaired channels
300300
if complete_partial:
301301
existing = complete_partial_integration(

0 commit comments

Comments
 (0)