Repository: cropsinsilico/yggdrasil (language: python)

Issue:
Specify new models via a web form
Using the yaml schema as a base, generate a json schema that can be used with a form generator to create the web page on the fly.


Discussion / hints:
Currently investigating which technologies will fit our needs and are compatible with our schema.
The current schema will likely need to be restructured (at least in its json form) to be compatible with any current form generator.

Fix patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index bb60acf0f..cc6820e22 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -2,6 +2,17 @@
 History
 =======
 
+1.8.1 (2021-10-15) Minor updates to support model submission form development
+------------------
+
+* Added --model_repository option to the integration-service-manager CLI
+* Preload models from the model repository into the service manager registry
+* Added validation_command option to model schema that can be used to validate a run on completion via the 'validate' runner option
+* Added dependencies and additional_dependencies options to model schema that can be used to specify packages that should be installed for a model
+* Refactor registration to use constants to prevent failure on circular import and speed up import time
+* Migrate constants into the constants module
+* Fix various bugs introduced by updates to GHA images and dependencies by pinning or updating use (e.g. jsonschema, libroadrunner, mac version, R version)
+
 1.8.0 (2021-09-15) Support for REST API based communicators, running integrations as services, and connecting to remote integration services
 ------------------
 
diff --git a/MANIFEST.in b/MANIFEST.in
index f80b5c655..4c79a4992 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -20,6 +20,7 @@ recursive-include yggdrasil *.json
 recursive-include yggdrasil *.f90
 recursive-include yggdrasil *.xml
 recursive-include yggdrasil/examples *.py
+recursive-include yggdrasil/examples *.rst
 recursive-include yggdrasil/demos *.py
 recursive-include yggdrasil/tests/scripts *.py
 recursive-include yggdrasil/examples Makefile_linux
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 34b25ea75..c57519a36 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -83,7 +83,7 @@ Then the Dockerfile that will be able to run those services is
 
 .. code-block:: docker
 
-   FROM cropsinsilico/yggdrasil-service:1.7.0
+   FROM cropsinsilico/yggdrasil-service:v1.8.0
    COPY services.yml .
    COPY foo ./foo
    COPY bar ./bar
diff --git a/recipe/meta.yaml b/recipe/meta.yaml
index 26131759d..034a190ef 100644
--- a/recipe/meta.yaml
+++ b/recipe/meta.yaml
@@ -60,7 +60,7 @@ requirements:
     - git
     - GitPython <=3.1.20  # [py==36]
     - GitPython  # [py>36]
-    - jsonschema
+    - jsonschema==3.2.0
     - matplotlib-base
     - numpy >=1.13.0
     - pandas
diff --git a/requirements.txt b/requirements.txt
index 1ab8d129a..a18ca2512 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
 chevron
 flask
-jsonschema>=3
+jsonschema==3.2.0
 matplotlib
 numpy>=1.13.0
 pandas
diff --git a/requirements_development.txt b/requirements_development.txt
index 3a2166050..f66d8af4a 100644
--- a/requirements_development.txt
+++ b/requirements_development.txt
@@ -1,2 +1,3 @@
 black
 git  # [conda]
+PyGithub
diff --git a/utils/build_docker.py b/utils/build_docker.py
index 0e3427ce0..29aa8cdef 100644
--- a/utils/build_docker.py
+++ b/utils/build_docker.py
@@ -7,7 +7,7 @@
 
 
 def build(dockerfile, tag, flags=[], repo='cropsinsilico/yggdrasil',
-          context=_utils_dir):
+          context=_utils_dir, disable_latest=False):
     r"""Build a docker image.
 
     Args:
@@ -20,11 +20,17 @@ def build(dockerfile, tag, flags=[], repo='cropsinsilico/yggdrasil',
         context (str, optional): Directory that should be provided as the
             context for the image. Defaults to the directory containing this
             script.
+        disable_latest (bool, optional): If True, the new image will not
+            be tagged 'latest' in addition to the provided tag value. Defaults
+            to False.
 
     """
     args = ['docker', 'build', '-t', f'{repo}:{tag}', '-f', dockerfile] + flags
     args.append(context)
     subprocess.call(args)
+    if not disable_latest:
+        args = ['docker', 'tag', f'{repo}:{tag}', f"{repo}:latest"]
+        subprocess.call(args)
 
 
 def push_image(tag, repo='cropsinsilico/yggdrasil'):
@@ -140,6 +146,10 @@ def params_service(params):
     parser.add_argument(
         "--push", action="store_true",
         help="After successfully building the image, push it to DockerHub.")
+    parser.add_argument(
+        "--disable-latest", action="store_true",
+        help=("Don't tag the new image as 'latest' in addition to the "
+              "version/commit specific tag."))
     subparsers = parser.add_subparsers(
         dest="type",
         help=("Type of docker image that should be built "
@@ -163,8 +173,11 @@ def params_service(params):
         params = params_executable(params)
     elif args.type == 'service':
         params = params_service(params)
+    params.setdefault('disable_latest', args.disable_latest)
     dockerfile = params.pop('dockerfile')
     tag = params.pop('tag')
     build(dockerfile, tag, **params)
     if args.push:
         push_image(tag, repo=params['repo'])
+        if not params['disable_latest']:
+            push_image('latest', repo=params['repo'])
diff --git a/utils/update_form_schema.py b/utils/update_form_schema.py
new file mode 100644
index 000000000..0ecf57b7b
--- /dev/null
+++ b/utils/update_form_schema.py
@@ -0,0 +1,46 @@
+import os
+import json
+import argparse
+from github import Github
+from yggdrasil import __version__
+from yggdrasil.schema import get_model_form_schema
+
+
+def update_schema(token):
+    r"""Update the model form schema via pull request to the
+    cropsinsilico/model_submission_form repository.
+
+    Args:
+        token (str): Github authentication token that should be used.
+
+    """
+    contents = json.dumps(get_model_form_schema(), indent='    ')
+    ver = __version__
+    repo_name = "cropsinsilico/model_submission_form"
+    msg = f"Update model form schema to version for yggdrasil {ver}"
+    branch = f"schema-update-{ver}"
+    schema = "static/model.json"
+    gh = Github(token)
+    repo = gh.get_repo(repo_name)
+    main = repo.get_branch('main')
+    schema_sha = repo.get_contents(schema).sha
+    repo.create_git_ref(f"refs/heads/{branch}", main.commit.sha)
+    repo.update_file(schema, msg, contents, schema_sha, branch=branch)
+    repo.create_pull(title=f"Update schema to {ver}",
+                     body=msg, head=branch, base="main")
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        "Update the model form schema via pull request to the"
+        "cropsinsilico/model_submission_form repository.")
+    parser.add_argument(
+        '--token',
+        help=("Github authentication token that should be used to open the "
+              "pull request. The token must have write access to the "
+              "cropsinsilico/model_submission_form repository."))
+    args = parser.parse_args()
+    if args.token is None:
+        args.token = os.environ.get('YGGDRASIL_UPDATE_TOKEN', None)
+    assert(args.token)
+    update_schema = update_schema(args.token)
diff --git a/yggdrasil/.ygg_schema.yml b/yggdrasil/.ygg_schema.yml
index 315a5ea20..aac3b4a81 100644
--- a/yggdrasil/.ygg_schema.yml
+++ b/yggdrasil/.ygg_schema.yml
@@ -180,44 +180,55 @@ definitions:
             type: string
           type: array
       required:
+      - commtype
       - datatype
       - name
-      - commtype
       title: comm_base
       type: object
     - anyOf:
       - additionalProperties: true
-        description: Schema for comm component ['mpi'] subtype.
+        description: Schema for comm component ['buffer'] subtype.
         properties:
           commtype:
             default: default
-            description: MPI communicator.
+            description: Communication mechanism that should be used.
             enum:
-            - mpi
+            - buffer
             type: string
-        title: MPIComm
+        title: yggdrasil.communication.BufferComm.BufferComm
         type: object
       - additionalProperties: true
-        description: Schema for comm component ['rmq'] subtype.
+        description: Schema for comm component ['default'] subtype.
         properties:
           commtype:
             default: default
-            description: RabbitMQ connection.
+            description: Communication mechanism selected based on the current platform.
             enum:
-            - rmq
+            - default
             type: string
-        title: RMQComm
+        title: yggdrasil.communication.DefaultComm.DefaultComm
         type: object
       - additionalProperties: true
-        description: Schema for comm component ['buffer'] subtype.
+        description: Schema for comm component ['ipc'] subtype.
         properties:
           commtype:
             default: default
-            description: Communication mechanism that should be used.
+            description: Interprocess communication (IPC) queue.
             enum:
-            - buffer
+            - ipc
+            type: string
+        title: yggdrasil.communication.IPCComm.IPCComm
+        type: object
+      - additionalProperties: true
+        description: Schema for comm component ['mpi'] subtype.
+        properties:
+          commtype:
+            default: default
+            description: MPI communicator.
+            enum:
+            - mpi
             type: string
-        title: BufferComm
+        title: yggdrasil.communication.MPIComm.MPIComm
         type: object
       - additionalProperties: true
         description: Schema for comm component ['rest'] subtype.
@@ -242,21 +253,7 @@ definitions:
             type: object
           port:
             type: int
-        title: RESTComm
-        type: object
-      - additionalProperties: true
-        description: Schema for comm component ['value'] subtype.
-        properties:
-          commtype:
-            default: default
-            description: Constant value.
-            enum:
-            - value
-            type: string
-          count:
-            default: 1
-            type: integer
-        title: ValueComm
+        title: yggdrasil.communication.RESTComm.RESTComm
         type: object
       - additionalProperties: true
         description: Schema for comm component ['rmq_async'] subtype.
@@ -267,29 +264,32 @@ definitions:
             enum:
             - rmq_async
             type: string
-        title: RMQAsyncComm
+        title: yggdrasil.communication.RMQAsyncComm.RMQAsyncComm
         type: object
       - additionalProperties: true
-        description: Schema for comm component ['ipc'] subtype.
+        description: Schema for comm component ['rmq'] subtype.
         properties:
           commtype:
             default: default
-            description: Interprocess communication (IPC) queue.
+            description: RabbitMQ connection.
             enum:
-            - ipc
+            - rmq
             type: string
-        title: IPCComm
+        title: yggdrasil.communication.RMQComm.RMQComm
         type: object
       - additionalProperties: true
-        description: Schema for comm component ['default'] subtype.
+        description: Schema for comm component ['value'] subtype.
         properties:
           commtype:
             default: default
-            description: Communication mechanism selected based on the current platform.
+            description: Constant value.
             enum:
-            - default
+            - value
             type: string
-        title: DefaultComm
+          count:
+            default: 1
+            type: integer
+        title: yggdrasil.communication.ValueComm.ValueComm
         type: object
       - additionalProperties: true
         description: Schema for comm component ['zmq'] subtype.
@@ -300,7 +300,7 @@ definitions:
             enum:
             - zmq
             type: string
-        title: ZMQComm
+        title: yggdrasil.communication.ZMQComm.ZMQComm
         type: object
     description: Schema for comm components.
     title: comm
@@ -384,69 +384,69 @@ definitions:
             enum:
             - connection
             type: string
-        title: ConnectionDriver
+        title: yggdrasil.drivers.ConnectionDriver.ConnectionDriver
         type: object
       - additionalProperties: true
-        description: Schema for connection component ['rpc_response'] subtype.
+        description: Schema for connection component ['file_input'] subtype.
         properties:
           connection_type:
-            description: Connection between one or more comms/files and one or more
-              comms/files.
+            description: Connection between a file and a model.
             enum:
-            - rpc_response
+            - file_input
             type: string
-        title: RPCResponseDriver
+        title: yggdrasil.drivers.FileInputDriver.FileInputDriver
         type: object
       - additionalProperties: true
-        description: Schema for connection component ['rpc_request'] subtype.
+        description: Schema for connection component ['file_output'] subtype.
         properties:
           connection_type:
-            description: Connection between one or more comms/files and one or more
-              comms/files.
+            description: Connection between a model and a file.
             enum:
-            - rpc_request
+            - file_output
             type: string
-        title: RPCRequestDriver
+        title: yggdrasil.drivers.FileOutputDriver.FileOutputDriver
         type: object
       - additionalProperties: true
-        description: Schema for connection component ['output'] subtype.
+        description: Schema for connection component ['input'] subtype.
         properties:
           connection_type:
-            description: Connection between a model and one or more comms/files.
+            description: Connection between one or more comms/files and a model.
             enum:
-            - output
+            - input
             type: string
-        title: OutputDriver
+        title: yggdrasil.drivers.InputDriver.InputDriver
         type: object
       - additionalProperties: true
-        description: Schema for connection component ['file_output'] subtype.
+        description: Schema for connection component ['output'] subtype.
         properties:
           connection_type:
-            description: Connection between a model and a file.
+            description: Connection between a model and one or more comms/files.
             enum:
-            - file_output
+            - output
             type: string
-        title: FileOutputDriver
+        title: yggdrasil.drivers.OutputDriver.OutputDriver
         type: object
       - additionalProperties: true
-        description: Schema for connection component ['input'] subtype.
+        description: Schema for connection component ['rpc_request'] subtype.
         properties:
           connection_type:
-            description: Connection between one or more comms/files and a model.
+            description: Connection between one or more comms/files and one or more
+              comms/files.
             enum:
-            - input
+            - rpc_request
             type: string
-        title: InputDriver
+        title: yggdrasil.drivers.RPCRequestDriver.RPCRequestDriver
         type: object
       - additionalProperties: true
-        description: Schema for connection component ['file_input'] subtype.
+        description: Schema for connection component ['rpc_response'] subtype.
         properties:
           connection_type:
-            description: Connection between a file and a model.
+            description: Connection between one or more comms/files and one or more
+              comms/files.
             enum:
-            - file_input
+            - rpc_response
             type: string
-        title: FileInputDriver
+        title: yggdrasil.drivers.RPCResponseDriver.RPCResponseDriver
         type: object
     description: Schema for connection components.
     title: connection
@@ -710,6 +710,105 @@ definitions:
       title: file_base
       type: object
     - anyOf:
+      - additionalProperties: true
+        description: Schema for file component ['ascii'] subtype.
+        properties:
+          comment:
+            default: '# '
+            description: One or more characters indicating a comment. Defaults to
+              '# '.
+            type: string
+          datatype:
+            description: JSON schema defining the type of object that the serializer
+              will be used to serialize/deserialize. Defaults to default_datatype.
+            type: schema
+          filetype:
+            default: binary
+            description: This file is read/written as encoded text one line at a time.
+            enum:
+            - ascii
+            type: string
+          newline:
+            default: '
+
+              '
+            description: One or more characters indicating a newline. Defaults to
+              '\n'.
+            type: string
+        title: yggdrasil.communication.AsciiFileComm.AsciiFileComm
+        type: object
+      - additionalProperties: true
+        description: Schema for file component ['map'] subtype.
+        properties:
+          comment:
+            default: '# '
+            description: One or more characters indicating a comment. Defaults to
+              '# '.
+            type: string
+          datatype:
+            description: JSON schema defining the type of object that the serializer
+              will be used to serialize/deserialize. Defaults to default_datatype.
+            type: schema
+          delimiter:
+            default: "\t"
+            description: Delimiter that should be used to separate name/value pairs
+              in the map. Defaults to \t.
+            type: string
+          filetype:
+            default: binary
+            description: The file contains a key/value mapping with one key/value
+              pair per line and separated by some delimiter.
+            enum:
+            - map
+            type: string
+          newline:
+            default: '
+
+              '
+            description: One or more characters indicating a newline. Defaults to
+              '\n'.
+            type: string
+        title: yggdrasil.communication.AsciiMapComm.AsciiMapComm
+        type: object
+      - additionalProperties: true
+        description: Schema for file component ['table'] subtype.
+        properties:
+          comment:
+            default: '# '
+            description: One or more characters indicating a comment. Defaults to
+              '# '.
+            type: string
+          datatype:
+            description: JSON schema defining the type of object that the serializer
+              will be used to serialize/deserialize. Defaults to default_datatype.
+            type: schema
+          delimiter:
+            default: "\t"
+            description: Character(s) that should be used to separate columns. Defaults
+              to '\t'.
+            type: string
+          filetype:
+            default: binary
+            description: The file is an ASCII table that will be read/written one
+              row at a time. If ``as_array`` is ``True``, the table will be read/written
+              all at once.
+            enum:
+            - table
+            type: string
+          newline:
+            default: '
+
+              '
+            description: One or more characters indicating a newline. Defaults to
+              '\n'.
+            type: string
+          use_astropy:
+            default: false
+            description: If True, the astropy package will be used to serialize/deserialize
+              table. Defaults to False.
+            type: boolean
+        title: yggdrasil.communication.AsciiTableComm.AsciiTableComm
+        type: object
       - additionalProperties: true
         description: Schema for file component ['binary'] subtype.
         properties:
@@ -739,10 +838,10 @@ definitions:
               type: instance
         required:
         - serializer
-        title: FileComm
+        title: yggdrasil.communication.FileComm.FileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['map'] subtype.
+        description: Schema for file component ['json'] subtype.
         properties:
           comment:
             default: '# '
@@ -753,18 +852,19 @@ definitions:
             description: JSON schema defining the type of object that the serializer
               will be used to serialize/deserialize. Defaults to default_datatype.
             type: schema
-          delimiter:
-            default: "\t"
-            description: Delimiter that should be used to separate name/value pairs
-              in the map. Defaults to \t.
-            type: string
           filetype:
             default: binary
-            description: The file contains a key/value mapping with one key/value
-              pair per line and separated by some delimiter.
+            description: The file contains a JSON serialized object.
             enum:
-            - map
+            - json
             type: string
+          indent:
+            default: "\t"
+            description: String or number of spaces that should be used to indent
+              each level within the seiralized structure. Defaults to '\t'.
+            type:
+            - string
+            - int
           newline:
             default: '
 
@@ -772,10 +872,15 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: AsciiMapComm
+          sort_keys:
+            default: true
+            description: If True, the serialization of dictionaries will be in key
+              sorted order. Defaults to True.
+            type: boolean
+        title: yggdrasil.communication.JSONFileComm.JSONFileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['ply'] subtype.
+        description: Schema for file component ['mat'] subtype.
         properties:
           comment:
             default: '# '
@@ -788,10 +893,10 @@ definitions:
             type: schema
           filetype:
             default: binary
-            description: The file is in the `Ply <http://paulbourke.net/dataformats/ply/>`_
-              data format for 3D structures.
+            description: The file is a Matlab .mat file containing one or more serialized
+              Matlab variables.
             enum:
-            - ply
+            - mat
             type: string
           newline:
             default: '
@@ -800,7 +905,7 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: PlyFileComm
+        title: yggdrasil.communication.MatFileComm.MatFileComm
         type: object
       - additionalProperties: true
         description: Schema for file component ['netcdf'] subtype.
@@ -846,10 +951,10 @@ definitions:
             - 1
             - 2
             type: integer
-        title: NetCDFFileComm
+        title: yggdrasil.communication.NetCDFFileComm.NetCDFFileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['wofost'] subtype.
+        description: Schema for file component ['obj'] subtype.
         properties:
           comment:
             default: '# '
@@ -860,16 +965,12 @@ definitions:
             description: JSON schema defining the type of object that the serializer
               will be used to serialize/deserialize. Defaults to default_datatype.
             type: schema
-          delimiter:
-            default: ' = '
-            description: Delimiter that should be used to separate name/value pairs
-              in the map. Defaults to \t.
-            type: string
           filetype:
             default: binary
-            description: The file is a WOFOST parameter file.
+            description: The file is in the `Obj <http://paulbourke.net/dataformats/obj/>`_
+              data format for 3D structures.
             enum:
-            - wofost
+            - obj
             type: string
           newline:
             default: '
@@ -878,10 +979,10 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: WOFOSTParamFileComm
+        title: yggdrasil.communication.ObjFileComm.ObjFileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['table'] subtype.
+        description: Schema for file component ['pandas'] subtype.
         properties:
           comment:
             default: '# '
@@ -899,11 +1000,9 @@ definitions:
             type: string
           filetype:
             default: binary
-            description: The file is an ASCII table that will be read/written one
-              row at a time. If ``as_array`` is ``True``, the table will be read/written
-              all at once.
+            description: The file is a Pandas frame output as a table.
             enum:
-            - table
+            - pandas
             type: string
           newline:
             default: '
@@ -912,15 +1011,25 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
+          no_header:
+            default: false
+            description: If True, headers will not be read or serialized from/to tables.
+              Defaults to False.
+            type: boolean
+          str_as_bytes:
+            default: false
+            description: If True, strings in columns are read as bytes. Defaults to
+              False.
+            type: boolean
           use_astropy:
             default: false
             description: If True, the astropy package will be used to serialize/deserialize
               table. Defaults to False.
             type: boolean
-        title: AsciiTableComm
+        title: yggdrasil.communication.PandasFileComm.PandasFileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['mat'] subtype.
+        description: Schema for file component ['pickle'] subtype.
         properties:
           comment:
             default: '# '
@@ -933,10 +1042,9 @@ definitions:
             type: schema
           filetype:
             default: binary
-            description: The file is a Matlab .mat file containing one or more serialized
-              Matlab variables.
+            description: The file contains one or more pickled Python objects.
             enum:
-            - mat
+            - pickle
             type: string
           newline:
             default: '
@@ -945,10 +1053,10 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: MatFileComm
+        title: yggdrasil.communication.PickleFileComm.PickleFileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['ascii'] subtype.
+        description: Schema for file component ['ply'] subtype.
         properties:
           comment:
             default: '# '
@@ -961,9 +1069,10 @@ definitions:
             type: schema
           filetype:
             default: binary
-            description: This file is read/written as encoded text one line at a time.
+            description: The file is in the `Ply <http://paulbourke.net/dataformats/ply/>`_
+              data format for 3D structures.
             enum:
-            - ascii
+            - ply
             type: string
           newline:
             default: '
@@ -972,10 +1081,10 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: AsciiFileComm
+        title: yggdrasil.communication.PlyFileComm.PlyFileComm
         type: object
       - additionalProperties: true
-        description: Schema for file component ['pandas'] subtype.
+        description: Schema for file component ['wofost'] subtype.
         properties:
           comment:
             default: '# '
@@ -987,58 +1096,15 @@ definitions:
               will be used to serialize/deserialize. Defaults to default_datatype.
             type: schema
           delimiter:
-            default: "\t"
-            description: Character(s) that should be used to separate columns. Defaults
-              to '\t'.
-            type: string
-          filetype:
-            default: binary
-            description: The file is a Pandas frame output as a table.
-            enum:
-            - pandas
-            type: string
-          newline:
-            default: '
-
-              '
-            description: One or more characters indicating a newline. Defaults to
-              '\n'.
-            type: string
-          no_header:
-            default: false
-            description: If True, headers will not be read or serialized from/to tables.
-              Defaults to False.
-            type: boolean
-          str_as_bytes:
-            default: false
-            description: If True, strings in columns are read as bytes. Defaults to
-              False.
-            type: boolean
-          use_astropy:
-            default: false
-            description: If True, the astropy package will be used to serialize/deserialize
-              table. Defaults to False.
-            type: boolean
-        title: PandasFileComm
-        type: object
-      - additionalProperties: true
-        description: Schema for file component ['obj'] subtype.
-        properties:
-          comment:
-            default: '# '
-            description: One or more characters indicating a comment. Defaults to
-              '# '.
+            default: ' = '
+            description: Delimiter that should be used to separate name/value pairs
+              in the map. Defaults to \t.
             type: string
-          datatype:
-            description: JSON schema defining the type of object that the serializer
-              will be used to serialize/deserialize. Defaults to default_datatype.
-            type: schema
           filetype:
             default: binary
-            description: The file is in the `Obj <http://paulbourke.net/dataformats/obj/>`_
-              data format for 3D structures.
+            description: The file is a WOFOST parameter file.
             enum:
-            - obj
+            - wofost
             type: string
           newline:
             default: '
@@ -1047,7 +1113,7 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: ObjFileComm
+        title: yggdrasil.communication.WOFOSTParamFileComm.WOFOSTParamFileComm
         type: object
       - additionalProperties: true
         description: Schema for file component ['yaml'] subtype.
@@ -1092,73 +1158,7 @@ definitions:
             description: One or more characters indicating a newline. Defaults to
               '\n'.
             type: string
-        title: YAMLFileComm
-        type: object
-      - additionalProperties: true
-        description: Schema for file component ['json'] subtype.
-        properties:
-          comment:
-            default: '# '
-            description: One or more characters indicating a comment. Defaults to
-              '# '.
-            type: string
-          datatype:
-            description: JSON schema defining the type of object that the serializer
-              will be used to serialize/deserialize. Defaults to default_datatype.
-            type: schema
-          filetype:
-            default: binary
-            description: The file contains a JSON serialized object.
-            enum:
-            - json
-            type: string
-          indent:
-            default: "\t"
-            description: String or number of spaces that should be used to indent
-              each level within the seiralized structure. Defaults to '\t'.
-            type:
-            - string
-            - int
-          newline:
-            default: '
-
-              '
-            description: One or more characters indicating a newline. Defaults to
-              '\n'.
-            type: string
-          sort_keys:
-            default: true
-            description: If True, the serialization of dictionaries will be in key
-              sorted order. Defaults to True.
-            type: boolean
-        title: JSONFileComm
-        type: object
-      - additionalProperties: true
-        description: Schema for file component ['pickle'] subtype.
-        properties:
-          comment:
-            default: '# '
-            description: One or more characters indicating a comment. Defaults to
-              '# '.
-            type: string
-          datatype:
-            description: JSON schema defining the type of object that the serializer
-              will be used to serialize/deserialize. Defaults to default_datatype.
-            type: schema
-          filetype:
-            default: binary
-            description: The file contains one or more pickled Python objects.
-            enum:
-            - pickle
-            type: string
-          newline:
-            default: '
-
-              '
-            description: One or more characters indicating a newline. Defaults to
-              '\n'.
-            type: string
-        title: PickleFileComm
+        title: yggdrasil.communication.YAMLFileComm.YAMLFileComm
         type: object
     description: Schema for file components.
     title: file
@@ -1204,25 +1204,7 @@ definitions:
           filtertype:
             enum:
             - direct
-        title: DirectFilter
-        type: object
-      - additionalProperties: true
-        description: Schema for filter component ['statement'] subtype.
-        properties:
-          filtertype:
-            enum:
-            - statement
-          statement:
-            description: Python statement in terms of the message as represented by
-              the string "%x%" that should evaluate to a boolean, True if the message
-              should pass through the filter, False if it should not. The statement
-              should only use a limited set of builtins and the math library (See
-              yggdrasil.tools.safe_eval). If more complex relationships are required,
-              use the FunctionFilter class.
-            type: string
-        required:
-        - statement
-        title: StatementFilter
+        title: yggdrasil.communication.filters.DirectFilter.DirectFilter
         type: object
       - additionalProperties: true
         description: Schema for filter component ['function'] subtype.
@@ -1241,7 +1223,25 @@ definitions:
             type: function
         required:
         - function
-        title: FunctionFilter
+        title: yggdrasil.communication.filters.FunctionFilter.FunctionFilter
+        type: object
+      - additionalProperties: true
+        description: Schema for filter component ['statement'] subtype.
+        properties:
+          filtertype:
+            enum:
+            - statement
+          statement:
+            description: Python statement in terms of the message as represented by
+              the string "%x%" that should evaluate to a boolean, True if the message
+              should pass through the filter, False if it should not. The statement
+              should only use a limited set of builtins and the math library (See
+              yggdrasil.tools.safe_eval). If more complex relationships are required,
+              use the FunctionFilter class.
+            type: string
+        required:
+        - statement
+        title: yggdrasil.communication.filters.StatementFilter.StatementFilter
         type: object
     description: Schema for filter components.
     title: filter
@@ -1253,6 +1253,26 @@ definitions:
         - args
       description: Base schema for all subtypes of model components.
       properties:
+        additional_dependencies:
+          additionalProperties:
+            items:
+              oneOf:
+              - type: string
+              - additionalProperties: false
+                properties:
+                  arguments:
+                    type: string
+                  package:
+                    type: string
+                  package_manager:
+                    type: string
+                required:
+                - package
+                type: object
+            type: array
+          description: A mapping between languages and lists of packages in those
+            languages that are required by the model.
+          type: object
         additional_variables:
           additionalProperties:
             items:
@@ -1280,9 +1300,12 @@ definitions:
             that should be passed as input to the model program or language executable
             (e.g. source code or configuration file for a domain specific language).
           items:
+            minLength: 1
             type: string
           type: array
         builddir:
+          description: Directory where the build should be saved. Defaults to <sourcedir>/build.
+            It can be relative to working_dir or absolute.
           type: string
         buildfile:
           type: string
@@ -1336,6 +1359,28 @@ definitions:
             use any of the files located there since OSR always assumes the included
             file paths are relative. Defaults to False.
           type: boolean
+        dependencies:
+          description: A list of packages required by the model that are written in
+            the same language as the model. If the package requires dependencies outside
+            the language of the model. use the additional_dependencies parameter to
+            provide them. If you need a version of the package from a specific package
+            manager, a mapping with 'package' and 'package_manager' fields can be
+            provided instead of just the name of the package.
+          items:
+            oneOf:
+            - type: string
+            - additionalProperties: false
+              properties:
+                arguments:
+                  type: string
+                package:
+                  type: string
+                package_manager:
+                  type: string
+              required:
+              - package
+              type: object
+          type: array
         description:
           description: Description of the model. This parameter is only used in the
             model repository or when providing the model as a service.
@@ -1561,6 +1606,11 @@ definitions:
           items:
             type: string
           type: array
+        repository_commit:
+          description: Commit that should be checked out in the model repository specified
+            by repository_url. If not provided, the most recent commit on the default
+            branch will be used.
+          type: string
         repository_url:
           description: URL for the git repository containing the model source code.
             If provided, relative paths in the model YAML definition will be considered
@@ -1787,6 +1837,11 @@ definitions:
           items:
             type: string
           type: array
+        validation_command:
+          description: Path to a validation command that can be used to verify that
+            the model ran as expected. A non-zero return code is taken to indicate
+            failure.
+          type: string
         with_strace:
           default: false
           description: If True, the command is run with strace (on Linux) or dtrace
@@ -1802,16 +1857,18 @@ definitions:
           type: string
       required:
       - args
+      - language
       - name
       - working_dir
-      - language
       title: model_base
       type: object
     - anyOf:
       - additionalProperties: true
-        description: Schema for model component ['make'] subtype.
+        description: Schema for model component ['cmake'] subtype.
         properties:
           builddir:
+            description: Directory where the build should be saved. Defaults to <sourcedir>/build.
+              It can be relative to working_dir or absolute.
             type: string
           buildfile:
             type: string
@@ -1830,6 +1887,11 @@ definitions:
             items:
               type: string
             type: array
+          configuration:
+            default: Release
+            description: Build type/configuration that should be built. Defaults to
+              'Release'.
+            type: string
           env_compiler:
             description: Environment variable where the compiler executable should
               be stored for use within the Makefile. If not provided, this will be
@@ -1852,9 +1914,9 @@ definitions:
             type: string
           language:
             default: executable
-            description: Model is written in C/C++ and has a Makefile for compilation.
+            description: Model is written in C/C++ and has a CMake build system.
             enum:
-            - make
+            - cmake
             type: string
           linker:
             description: Command or path to executable that should be used to link
@@ -1871,16 +1933,6 @@ definitions:
             items:
               type: string
             type: array
-          makedir:
-            description: Directory where make should be invoked from if it is not
-              the same as the directory containing the makefile. Defaults to directory
-              containing makefile if provided, otherwise working_dir.
-            type: string
-          makefile:
-            default: Makefile
-            description: Path to make file either absolute, relative to makedir (if
-              provided), or relative to working_dir. Defaults to Makefile.
-            type: string
           source_files:
             default: []
             description: Source files that should be compiled into an executable.
@@ -1889,6 +1941,11 @@ definitions:
             items:
               type: string
             type: array
+          sourcedir:
+            description: Source directory to call cmake on. If not provided it is
+              set to working_dir. This should be the directory containing the CMakeLists.txt
+              file. It can be relative to working_dir or absolute.
+            type: string
           target:
             description: Make target that should be built to create the model executable.
               Defaults to None.
@@ -1918,118 +1975,31 @@ definitions:
             items:
               type: string
             type: array
-        title: MakeModelDriver
-        type: object
-      - additionalProperties: true
-        description: Schema for model component ['mpi'] subtype.
-        properties:
-          language:
-            default: executable
-            description: Model is being run on another MPI process and this driver
-              is used as as stand-in to monitor it on the root process.
-            enum:
-            - mpi
-            type: string
-        title: MPIPartnerModel
+        title: yggdrasil.drivers.CMakeModelDriver.CMakeModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['python'] subtype.
+        description: Schema for model component ['c'] subtype.
         properties:
-          interpreter:
-            description: Name or path of interpreter executable that should be used
-              to run the model. If not provided, the interpreter will be determined
-              based on configuration options for the language (if present) and the
-              default_interpreter class attribute.
+          compiler:
+            description: Command or path to executable that should be used to compile
+              the model. If not provided, the compiler will be determined based on
+              configuration options for the language (if present) and the registered
+              compilers that are available on the current operating system.
             type: string
-          interpreter_flags:
+          compiler_flags:
             default: []
-            description: Flags that should be passed to the interpreter when running
-              the model. If not provided, the flags are determined based on configuration
-              options for the language (if present) and the default_interpreter_flags
-              class attribute.
+            description: Flags that should be passed to the compiler during compilation.
+              If nto provided, the compiler flags will be determined based on configuration
+              options for the language (if present), the compiler defaults, and the
+              default_compiler_flags class attribute.
             items:
               type: string
             type: array
           language:
             default: executable
-            description: Model is written in Python.
+            description: Model is written in C.
             enum:
-            - python
-            type: string
-          skip_interpreter:
-            default: false
-            description: If True, no interpreter will be added to the arguments. This
-              should only be used for subclasses that will not be invoking the model
-              via the command line. Defaults to False.
-            type: boolean
-        title: PythonModelDriver
-        type: object
-      - additionalProperties: true
-        description: Schema for model component ['lpy'] subtype.
-        properties:
-          interpreter:
-            description: Name or path of interpreter executable that should be used
-              to run the model. If not provided, the interpreter will be determined
-              based on configuration options for the language (if present) and the
-              default_interpreter class attribute.
-            type: string
-          interpreter_flags:
-            default: []
-            description: Flags that should be passed to the interpreter when running
-              the model. If not provided, the flags are determined based on configuration
-              options for the language (if present) and the default_interpreter_flags
-              class attribute.
-            items:
-              type: string
-            type: array
-          language:
-            default: executable
-            description: Model is an LPy system.
-            enum:
-            - lpy
-            type: string
-          skip_interpreter:
-            default: false
-            description: If True, no interpreter will be added to the arguments. This
-              should only be used for subclasses that will not be invoking the model
-              via the command line. Defaults to False.
-            type: boolean
-        title: LPyModelDriver
-        type: object
-      - additionalProperties: true
-        description: Schema for model component ['executable'] subtype.
-        properties:
-          language:
-            default: executable
-            description: Model is an executable.
-            enum:
-            - executable
-            type: string
-        title: ExecutableModelDriver
-        type: object
-      - additionalProperties: true
-        description: Schema for model component ['c'] subtype.
-        properties:
-          compiler:
-            description: Command or path to executable that should be used to compile
-              the model. If not provided, the compiler will be determined based on
-              configuration options for the language (if present) and the registered
-              compilers that are available on the current operating system.
-            type: string
-          compiler_flags:
-            default: []
-            description: Flags that should be passed to the compiler during compilation.
-              If nto provided, the compiler flags will be determined based on configuration
-              options for the language (if present), the compiler defaults, and the
-              default_compiler_flags class attribute.
-            items:
-              type: string
-            type: array
-          language:
-            default: executable
-            description: Model is written in C.
-            enum:
-            - c
+            - c
             type: string
           linker:
             description: Command or path to executable that should be used to link
@@ -2054,7 +2024,7 @@ definitions:
             items:
               type: string
             type: array
-        title: CModelDriver
+        title: yggdrasil.drivers.CModelDriver.CModelDriver
         type: object
       - additionalProperties: true
         description: Schema for model component ['c++', 'cpp', 'cxx'] subtype.
@@ -2105,52 +2075,10 @@ definitions:
             items:
               type: string
             type: array
-        title: CPPModelDriver
-        type: object
-      - additionalProperties: true
-        description: Schema for model component ['osr'] subtype.
-        properties:
-          copy_xml_to_osr:
-            default: false
-            description: If True, the XML file(s) will be copied to the OSR repository
-              InputFiles direcitory before running. This is necessary if the XML file(s)
-              use any of the files located there since OSR always assumes the included
-              file paths are relative. Defaults to False.
-            type: boolean
-          language:
-            default: executable
-            description: Model is an OSR model.
-            enum:
-            - osr
-            type: string
-          sync_vars_in:
-            default: []
-            description: Variables that should be synchronized from other models.
-              Defaults to [].
-            items:
-              type: string
-            type: array
-          sync_vars_out:
-            default: []
-            description: Variables that should be synchronized to other models. Defaults
-              to [].
-            items:
-              type: string
-            type: array
-          update_interval:
-            additionalProperties:
-              type: float
-            default:
-              timesync: 1.0
-            description: Max simulation interval at which synchronization should occur
-              (in days). Defaults to 1.0 if not provided. If the XML input file loads
-              additional export modules that output at a shorter rate, the existing
-              table of values will be extrapolated.
-            type: object
-        title: OSRModelDriver
+        title: yggdrasil.drivers.CPPModelDriver.CPPModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['matlab'] subtype.
+        description: Schema for model component ['dummy'] subtype.
         properties:
           interpreter:
             description: Name or path of interpreter executable that should be used
@@ -2169,9 +2097,10 @@ definitions:
             type: array
           language:
             default: executable
-            description: Model is written in Matlab.
+            description: The programming language that the model is written in. A
+              list of available languages can be found :ref:`here <schema_table_model_subtype_rst>`.
             enum:
-            - matlab
+            - dummy
             type: string
           skip_interpreter:
             default: false
@@ -2179,163 +2108,78 @@ definitions:
               should only be used for subclasses that will not be invoking the model
               via the command line. Defaults to False.
             type: boolean
-          use_symunit:
-            default: false
-            description: If True, input/output variables with units will be represented
-              in Matlab using symunit. Defaults to False.
-            type: boolean
-        title: MatlabModelDriver
+        title: yggdrasil.drivers.DummyModelDriver.DummyModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['R', 'r'] subtype.
+        description: Schema for model component ['executable'] subtype.
         properties:
-          interpreter:
-            description: Name or path of interpreter executable that should be used
-              to run the model. If not provided, the interpreter will be determined
-              based on configuration options for the language (if present) and the
-              default_interpreter class attribute.
-            type: string
-          interpreter_flags:
-            default: []
-            description: Flags that should be passed to the interpreter when running
-              the model. If not provided, the flags are determined based on configuration
-              options for the language (if present) and the default_interpreter_flags
-              class attribute.
-            items:
-              type: string
-            type: array
           language:
             default: executable
-            description: Model is written in R.
+            description: Model is an executable.
             enum:
-            - R
-            - r
+            - executable
             type: string
-          skip_interpreter:
-            default: false
-            description: If True, no interpreter will be added to the arguments. This
-              should only be used for subclasses that will not be invoking the model
-              via the command line. Defaults to False.
-            type: boolean
-        title: RModelDriver
+        title: yggdrasil.drivers.ExecutableModelDriver.ExecutableModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['timesync'] subtype.
+        description: Schema for model component ['fortran'] subtype.
         properties:
-          additional_variables:
-            additionalProperties:
-              items:
-                type: string
-              type: array
-            default: {}
-            type: object
-          aggregation:
-            anyOf:
-            - type: function
-            - type: string
-            - additionalProperties:
-                anyOf:
-                - type: function
-                - type: string
-              type: object
-            default: mean
-          interpolation:
-            anyOf:
-            - type: string
-            - additionalProperties:
-                oneOf:
-                - type: string
-                - properties:
-                    method:
-                      type: string
-                  required:
-                  - method
-                  type: object
-              type: object
-            - properties:
-                method:
-                  type: string
-              required:
-              - method
-              type: object
-            default: index
-          interpreter:
-            description: Name or path of interpreter executable that should be used
-              to run the model. If not provided, the interpreter will be determined
-              based on configuration options for the language (if present) and the
-              default_interpreter class attribute.
+          compiler:
+            description: Command or path to executable that should be used to compile
+              the model. If not provided, the compiler will be determined based on
+              configuration options for the language (if present) and the registered
+              compilers that are available on the current operating system.
             type: string
-          interpreter_flags:
+          compiler_flags:
             default: []
-            description: Flags that should be passed to the interpreter when running
-              the model. If not provided, the flags are determined based on configuration
-              options for the language (if present) and the default_interpreter_flags
-              class attribute.
+            description: Flags that should be passed to the compiler during compilation.
+              If nto provided, the compiler flags will be determined based on configuration
+              options for the language (if present), the compiler defaults, and the
+              default_compiler_flags class attribute.
             items:
               type: string
             type: array
           language:
             default: executable
-            description: Model is dedicated to synchronizingtimesteps between other
-              models.
+            description: Model is written in Fortran.
             enum:
-            - timesync
+            - fortran
             type: string
-          skip_interpreter:
-            default: false
-            description: If True, no interpreter will be added to the arguments. This
-              should only be used for subclasses that will not be invoking the model
-              via the command line. Defaults to False.
-            type: boolean
-          synonyms:
-            additionalProperties:
-              additionalProperties:
-                anyOf:
-                - type: string
-                - properties:
-                    alt:
-                      anyOf:
-                      - type: string
-                      - items:
-                          type: string
-                        type: array
-                    alt2base:
-                      type: function
-                    base2alt:
-                      type: function
-                  required:
-                  - alt
-                  - alt2base
-                  - base2alt
-                  type: object
-              type: object
-            default: {}
-            description: 'Mapping from model names to mappings from base variables
-              names to information about one or more alternate variable names used
-              by the named model that should be converted to the base variable. Values
-              for providing information about alternate variables can either be strings
-              (implies equivalence with the base variable in everything but name and
-              units) or mappings with the keys:'
-            type: object
-        title: TimeSyncModelDriver
+          linker:
+            description: Command or path to executable that should be used to link
+              the model. If not provided, the linker will be determined based on configuration
+              options for the language (if present) and the registered linkers that
+              are available on the current operating system
+            type: string
+          linker_flags:
+            default: []
+            description: Flags that should be passed to the linker during compilation.
+              If nto provided, the linker flags will be determined based on configuration
+              options for the language (if present), the linker defaults, and the
+              default_linker_flags class attribute.
+            items:
+              type: string
+            type: array
+          source_files:
+            default: []
+            description: Source files that should be compiled into an executable.
+              Defaults to an empty list and the driver will search for a source file
+              based on the model executable (the first model argument).
+            items:
+              type: string
+            type: array
+          standard:
+            default: f2003
+            description: Fortran standard that should be used. Defaults to 'f2003'.
+            enum:
+            - f2003
+            - f2008
+            type: string
+        title: yggdrasil.drivers.FortranModelDriver.FortranModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['sbml'] subtype.
+        description: Schema for model component ['lpy'] subtype.
         properties:
-          integrator:
-            default: cvode
-            description: Name of integrator that should be used. Valid options include
-              ['cvode', 'gillespie', 'rk4', 'rk45']. Defaults to 'cvode'.
-            enum:
-            - cvode
-            - gillespie
-            - rk4
-            - rk45
-            type: string
-          integrator_settings:
-            default: {}
-            description: Settings for the integrator. Defaults to empty dict.
-            type: object
           interpreter:
             description: Name or path of interpreter executable that should be used
               to run the model. If not provided, the interpreter will be determined
@@ -2353,57 +2197,34 @@ definitions:
             type: array
           language:
             default: executable
-            description: Model is an SBML model.
+            description: Model is an LPy system.
             enum:
-            - sbml
+            - lpy
             type: string
-          only_output_final_step:
-            default: false
-            description: If True, only the final timestep is output. Defaults to False.
-            type: boolean
-          reset:
-            default: false
-            description: If True, the simulation will be reset to it's initial values
-              before each call (including the start time). Defaults to False.
-            type: boolean
-          selections:
-            default: []
-            description: Variables to include in the output. Defaults to None and
-              the time/floating selections will be returned.
-            items:
-              type: string
-            type: array
           skip_interpreter:
             default: false
             description: If True, no interpreter will be added to the arguments. This
               should only be used for subclasses that will not be invoking the model
               via the command line. Defaults to False.
             type: boolean
-          skip_start_time:
-            default: false
-            description: If True, the results for the initial time step will not be
-              output. Defaults to False. This option is ignored if only_output_final_step
-              is True.
-            type: boolean
-          start_time:
-            default: 0.0
-            description: Time that simulation should be started from. If 'reset' is
-              True, the start time will always be the provided value, otherwise, the
-              start time will be the end of the previous call after the first call.
-              Defaults to 0.0.
-            type: number
-          steps:
-            default: 1
-            description: Number of steps that should be output. Defaults to None.
-            type: integer
-        title: SBMLModelDriver
+        title: yggdrasil.drivers.LPyModelDriver.LPyModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['cmake'] subtype.
+        description: Schema for model component ['mpi'] subtype.
+        properties:
+          language:
+            default: executable
+            description: Model is being run on another MPI process and this driver
+              is used as as stand-in to monitor it on the root process.
+            enum:
+            - mpi
+            type: string
+        title: yggdrasil.drivers.MPIPartnerModel.MPIPartnerModel
+        type: object
+      - additionalProperties: true
+        description: Schema for model component ['make'] subtype.
         properties:
           builddir:
-            description: Directory where the build should be saved. Defaults to <sourcedir>/build.
-              It can be relative to working_dir or absolute.
             type: string
           buildfile:
             type: string
@@ -2422,11 +2243,6 @@ definitions:
             items:
               type: string
             type: array
-          configuration:
-            default: Release
-            description: Build type/configuration that should be built. Defaults to
-              'Release'.
-            type: string
           env_compiler:
             description: Environment variable where the compiler executable should
               be stored for use within the Makefile. If not provided, this will be
@@ -2449,9 +2265,9 @@ definitions:
             type: string
           language:
             default: executable
-            description: Model is written in C/C++ and has a CMake build system.
+            description: Model is written in C/C++ and has a Makefile for compilation.
             enum:
-            - cmake
+            - make
             type: string
           linker:
             description: Command or path to executable that should be used to link
@@ -2468,6 +2284,16 @@ definitions:
             items:
               type: string
             type: array
+          makedir:
+            description: Directory where make should be invoked from if it is not
+              the same as the directory containing the makefile. Defaults to directory
+              containing makefile if provided, otherwise working_dir.
+            type: string
+          makefile:
+            default: Makefile
+            description: Path to make file either absolute, relative to makedir (if
+              provided), or relative to working_dir. Defaults to Makefile.
+            type: string
           source_files:
             default: []
             description: Source files that should be compiled into an executable.
@@ -2476,11 +2302,6 @@ definitions:
             items:
               type: string
             type: array
-          sourcedir:
-            description: Source directory to call cmake on. If not provided it is
-              set to working_dir. This should be the directory containing the CMakeLists.txt
-              file. It can be relative to working_dir or absolute.
-            type: string
           target:
             description: Make target that should be built to create the model executable.
               Defaults to None.
@@ -2510,67 +2331,271 @@ definitions:
             items:
               type: string
             type: array
-        title: CMakeModelDriver
+        title: yggdrasil.drivers.MakeModelDriver.MakeModelDriver
+        type: object
+      - additionalProperties: true
+        description: Schema for model component ['matlab'] subtype.
+        properties:
+          interpreter:
+            description: Name or path of interpreter executable that should be used
+              to run the model. If not provided, the interpreter will be determined
+              based on configuration options for the language (if present) and the
+              default_interpreter class attribute.
+            type: string
+          interpreter_flags:
+            default: []
+            description: Flags that should be passed to the interpreter when running
+              the model. If not provided, the flags are determined based on configuration
+              options for the language (if present) and the default_interpreter_flags
+              class attribute.
+            items:
+              type: string
+            type: array
+          language:
+            default: executable
+            description: Model is written in Matlab.
+            enum:
+            - matlab
+            type: string
+          skip_interpreter:
+            default: false
+            description: If True, no interpreter will be added to the arguments. This
+              should only be used for subclasses that will not be invoking the model
+              via the command line. Defaults to False.
+            type: boolean
+          use_symunit:
+            default: false
+            description: If True, input/output variables with units will be represented
+              in Matlab using symunit. Defaults to False.
+            type: boolean
+        title: yggdrasil.drivers.MatlabModelDriver.MatlabModelDriver
+        type: object
+      - additionalProperties: true
+        description: Schema for model component ['osr'] subtype.
+        properties:
+          copy_xml_to_osr:
+            default: false
+            description: If True, the XML file(s) will be copied to the OSR repository
+              InputFiles direcitory before running. This is necessary if the XML file(s)
+              use any of the files located there since OSR always assumes the included
+              file paths are relative. Defaults to False.
+            type: boolean
+          language:
+            default: executable
+            description: Model is an OSR model.
+            enum:
+            - osr
+            type: string
+          sync_vars_in:
+            default: []
+            description: Variables that should be synchronized from other models.
+              Defaults to [].
+            items:
+              type: string
+            type: array
+          sync_vars_out:
+            default: []
+            description: Variables that should be synchronized to other models. Defaults
+              to [].
+            items:
+              type: string
+            type: array
+          update_interval:
+            additionalProperties:
+              type: float
+            default:
+              timesync: 1.0
+            description: Max simulation interval at which synchronization should occur
+              (in days). Defaults to 1.0 if not provided. If the XML input file loads
+              additional export modules that output at a shorter rate, the existing
+              table of values will be extrapolated.
+            type: object
+        title: yggdrasil.drivers.OSRModelDriver.OSRModelDriver
+        type: object
+      - additionalProperties: true
+        description: Schema for model component ['python'] subtype.
+        properties:
+          interpreter:
+            description: Name or path of interpreter executable that should be used
+              to run the model. If not provided, the interpreter will be determined
+              based on configuration options for the language (if present) and the
+              default_interpreter class attribute.
+            type: string
+          interpreter_flags:
+            default: []
+            description: Flags that should be passed to the interpreter when running
+              the model. If not provided, the flags are determined based on configuration
+              options for the language (if present) and the default_interpreter_flags
+              class attribute.
+            items:
+              type: string
+            type: array
+          language:
+            default: executable
+            description: Model is written in Python.
+            enum:
+            - python
+            type: string
+          skip_interpreter:
+            default: false
+            description: If True, no interpreter will be added to the arguments. This
+              should only be used for subclasses that will not be invoking the model
+              via the command line. Defaults to False.
+            type: boolean
+        title: yggdrasil.drivers.PythonModelDriver.PythonModelDriver
+        type: object
+      - additionalProperties: true
+        description: Schema for model component ['R', 'r'] subtype.
+        properties:
+          interpreter:
+            description: Name or path of interpreter executable that should be used
+              to run the model. If not provided, the interpreter will be determined
+              based on configuration options for the language (if present) and the
+              default_interpreter class attribute.
+            type: string
+          interpreter_flags:
+            default: []
+            description: Flags that should be passed to the interpreter when running
+              the model. If not provided, the flags are determined based on configuration
+              options for the language (if present) and the default_interpreter_flags
+              class attribute.
+            items:
+              type: string
+            type: array
+          language:
+            default: executable
+            description: Model is written in R.
+            enum:
+            - R
+            - r
+            type: string
+          skip_interpreter:
+            default: false
+            description: If True, no interpreter will be added to the arguments. This
+              should only be used for subclasses that will not be invoking the model
+              via the command line. Defaults to False.
+            type: boolean
+        title: yggdrasil.drivers.RModelDriver.RModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['fortran'] subtype.
+        description: Schema for model component ['sbml'] subtype.
         properties:
-          compiler:
-            description: Command or path to executable that should be used to compile
-              the model. If not provided, the compiler will be determined based on
-              configuration options for the language (if present) and the registered
-              compilers that are available on the current operating system.
+          integrator:
+            default: cvode
+            description: Name of integrator that should be used. Valid options include
+              ['cvode', 'gillespie', 'rk4', 'rk45']. Defaults to 'cvode'.
+            enum:
+            - cvode
+            - gillespie
+            - rk4
+            - rk45
             type: string
-          compiler_flags:
+          integrator_settings:
+            default: {}
+            description: Settings for the integrator. Defaults to empty dict.
+            type: object
+          interpreter:
+            description: Name or path of interpreter executable that should be used
+              to run the model. If not provided, the interpreter will be determined
+              based on configuration options for the language (if present) and the
+              default_interpreter class attribute.
+            type: string
+          interpreter_flags:
             default: []
-            description: Flags that should be passed to the compiler during compilation.
-              If nto provided, the compiler flags will be determined based on configuration
-              options for the language (if present), the compiler defaults, and the
-              default_compiler_flags class attribute.
+            description: Flags that should be passed to the interpreter when running
+              the model. If not provided, the flags are determined based on configuration
+              options for the language (if present) and the default_interpreter_flags
+              class attribute.
             items:
               type: string
             type: array
           language:
             default: executable
-            description: Model is written in Fortran.
+            description: Model is an SBML model.
             enum:
-            - fortran
-            type: string
-          linker:
-            description: Command or path to executable that should be used to link
-              the model. If not provided, the linker will be determined based on configuration
-              options for the language (if present) and the registered linkers that
-              are available on the current operating system
+            - sbml
             type: string
-          linker_flags:
-            default: []
-            description: Flags that should be passed to the linker during compilation.
-              If nto provided, the linker flags will be determined based on configuration
-              options for the language (if present), the linker defaults, and the
-              default_linker_flags class attribute.
-            items:
-              type: string
-            type: array
-          source_files:
+          only_output_final_step:
+            default: false
+            description: If True, only the final timestep is output. Defaults to False.
+            type: boolean
+          reset:
+            default: false
+            description: If True, the simulation will be reset to it's initial values
+              before each call (including the start time). Defaults to False.
+            type: boolean
+          selections:
             default: []
-            description: Source files that should be compiled into an executable.
-              Defaults to an empty list and the driver will search for a source file
-              based on the model executable (the first model argument).
+            description: Variables to include in the output. Defaults to None and
+              the time/floating selections will be returned.
             items:
               type: string
             type: array
-          standard:
-            default: f2003
-            description: Fortran standard that should be used. Defaults to 'f2003'.
-            enum:
-            - f2003
-            - f2008
-            type: string
-        title: FortranModelDriver
+          skip_interpreter:
+            default: false
+            description: If True, no interpreter will be added to the arguments. This
+              should only be used for subclasses that will not be invoking the model
+              via the command line. Defaults to False.
+            type: boolean
+          skip_start_time:
+            default: false
+            description: If True, the results for the initial time step will not be
+              output. Defaults to False. This option is ignored if only_output_final_step
+              is True.
+            type: boolean
+          start_time:
+            default: 0.0
+            description: Time that simulation should be started from. If 'reset' is
+              True, the start time will always be the provided value, otherwise, the
+              start time will be the end of the previous call after the first call.
+              Defaults to 0.0.
+            type: number
+          steps:
+            default: 1
+            description: Number of steps that should be output. Defaults to None.
+            type: integer
+        title: yggdrasil.drivers.SBMLModelDriver.SBMLModelDriver
         type: object
       - additionalProperties: true
-        description: Schema for model component ['dummy'] subtype.
+        description: Schema for model component ['timesync'] subtype.
         properties:
+          additional_variables:
+            additionalProperties:
+              items:
+                type: string
+              type: array
+            default: {}
+            type: object
+          aggregation:
+            anyOf:
+            - type: function
+            - type: string
+            - additionalProperties:
+                anyOf:
+                - type: function
+                - type: string
+              type: object
+            default: mean
+          interpolation:
+            anyOf:
+            - type: string
+            - additionalProperties:
+                oneOf:
+                - type: string
+                - properties:
+                    method:
+                      type: string
+                  required:
+                  - method
+                  type: object
+              type: object
+            - properties:
+                method:
+                  type: string
+              required:
+              - method
+              type: object
+            default: index
           interpreter:
             description: Name or path of interpreter executable that should be used
               to run the model. If not provided, the interpreter will be determined
@@ -2588,10 +2613,10 @@ definitions:
             type: array
           language:
             default: executable
-            description: The programming language that the model is written in. A
-              list of available languages can be found :ref:`here <schema_table_model_subtype_rst>`.
+            description: Model is dedicated to synchronizingtimesteps between other
+              models.
             enum:
-            - dummy
+            - timesync
             type: string
           skip_interpreter:
             default: false
@@ -2599,7 +2624,37 @@ definitions:
               should only be used for subclasses that will not be invoking the model
               via the command line. Defaults to False.
             type: boolean
-        title: DummyModelDriver
+          synonyms:
+            additionalProperties:
+              additionalProperties:
+                anyOf:
+                - type: string
+                - properties:
+                    alt:
+                      anyOf:
+                      - type: string
+                      - items:
+                          type: string
+                        type: array
+                    alt2base:
+                      type: function
+                    base2alt:
+                      type: function
+                  required:
+                  - alt
+                  - alt2base
+                  - base2alt
+                  type: object
+              type: object
+            default: {}
+            description: 'Mapping from model names to mappings from base variables
+              names to information about one or more alternate variable names used
+              by the named model that should be converted to the base variable. Values
+              for providing information about alternate variables can either be strings
+              (implies equivalence with the base variable in everything but name and
+              units) or mappings with the keys:'
+            type: object
+        title: yggdrasil.drivers.TimeSyncModelDriver.TimeSyncModelDriver
         type: object
     description: Schema for model components.
     title: model
@@ -2634,8 +2689,8 @@ definitions:
           type: boolean
         delimiter:
           default: "\t"
-          description: Character(s) that should be used to separate columns. Defaults
-            to '\t'.
+          description: Delimiter that should be used to separate name/value pairs
+            in the map. Defaults to \t.
           type: string
         encoded_datatype:
           description: JSON schema describing the type that serialized objects should
@@ -2731,17 +2786,21 @@ definitions:
       type: object
     - anyOf:
       - additionalProperties: true
-        description: Schema for serializer component ['default'] subtype.
+        description: Schema for serializer component ['map'] subtype.
         properties:
+          delimiter:
+            default: "\t"
+            description: Delimiter that should be used to separate name/value pairs
+              in the map. Defaults to \t.
+            type: string
           seritype:
             default: default
-            description: Default serializer that uses |yggdrasil|'s extended JSON
-              serialization based on a provided type definition (See discussion :ref:`here
-              <serialization_rst>`).
+            description: Serialzation of mapping between key/value pairs with one
+              pair per line and using a character delimiter to separate keys and values.
             enum:
-            - default
+            - map
             type: string
-        title: DefaultSerialize
+        title: yggdrasil.serialize.AsciiMapSerialize.AsciiMapSerialize
         type: object
       - additionalProperties: true
         description: Schema for serializer component ['table'] subtype.
@@ -2786,56 +2845,31 @@ definitions:
             description: If True, the astropy package will be used to serialize/deserialize
               table. Defaults to False.
             type: boolean
-        title: AsciiTableSerialize
-        type: object
-      - additionalProperties: true
-        description: Schema for serializer component ['pandas'] subtype.
-        properties:
-          delimiter:
-            default: "\t"
-            description: Character(s) that should be used to separate columns. Defaults
-              to '\t'.
-            type: string
-          field_names:
-            description: The names of fields in the format string. If not provided,
-              names are set based on the order of the fields in the format string.
-            items:
-              type: string
-            type: array
-          field_units:
-            description: The units of fields in the format string. If not provided,
-              all fields are assumed to be dimensionless.
-            items:
-              type: string
-            type: array
-          format_str:
-            description: If provided, this string will be used to format messages
-              from a list of arguments and parse messages to get a list of arguments
-              in C printf/scanf style. Defaults to None and messages are assumed to
-              already be bytes.
-            type: string
-          no_header:
-            default: false
-            description: If True, headers will not be read or serialized from/to tables.
-              Defaults to False.
-            type: boolean
-          seritype:
-            default: default
-            description: Serializes tables using the pandas package.
-            enum:
-            - pandas
-            type: string
-          str_as_bytes:
-            default: false
-            description: If True, strings in columns are read as bytes. Defaults to
-              False.
-            type: boolean
-          use_astropy:
-            default: false
-            description: If True, the astropy package will be used to serialize/deserialize
-              table. Defaults to False.
-            type: boolean
-        title: PandasSerialize
+        title: yggdrasil.serialize.AsciiTableSerialize.AsciiTableSerialize
+        type: object
+      - additionalProperties: true
+        description: Schema for serializer component ['default'] subtype.
+        properties:
+          seritype:
+            default: default
+            description: Default serializer that uses |yggdrasil|'s extended JSON
+              serialization based on a provided type definition (See discussion :ref:`here
+              <serialization_rst>`).
+            enum:
+            - default
+            type: string
+        title: yggdrasil.serialize.DefaultSerialize.DefaultSerialize
+        type: object
+      - additionalProperties: true
+        description: Schema for serializer component ['direct'] subtype.
+        properties:
+          seritype:
+            default: default
+            description: Direct serialization of bytes.
+            enum:
+            - direct
+            type: string
+        title: yggdrasil.serialize.DirectSerialize.DirectSerialize
         type: object
       - additionalProperties: true
         description: Schema for serializer component ['functional'] subtype.
@@ -2863,29 +2897,7 @@ definitions:
             enum:
             - functional
             type: string
-        title: FunctionalSerialize
-        type: object
-      - additionalProperties: true
-        description: Schema for serializer component ['mat'] subtype.
-        properties:
-          seritype:
-            default: default
-            description: Serializes objects using the Matlab .mat format.
-            enum:
-            - mat
-            type: string
-        title: MatSerialize
-        type: object
-      - additionalProperties: true
-        description: Schema for serializer component ['direct'] subtype.
-        properties:
-          seritype:
-            default: default
-            description: Direct serialization of bytes.
-            enum:
-            - direct
-            type: string
-        title: DirectSerialize
+        title: yggdrasil.serialize.FunctionalSerialize.FunctionalSerialize
         type: object
       - additionalProperties: true
         description: Schema for serializer component ['json'] subtype.
@@ -2908,46 +2920,78 @@ definitions:
             description: If True, the serialization of dictionaries will be in key
               sorted order. Defaults to True.
             type: boolean
-        title: JSONSerialize
+        title: yggdrasil.serialize.JSONSerialize.JSONSerialize
         type: object
       - additionalProperties: true
-        description: Schema for serializer component ['ply'] subtype.
+        description: Schema for serializer component ['mat'] subtype.
         properties:
           seritype:
             default: default
-            description: Serialize 3D structures using Ply format.
+            description: Serializes objects using the Matlab .mat format.
             enum:
-            - ply
+            - mat
             type: string
-        title: PlySerialize
+        title: yggdrasil.serialize.MatSerialize.MatSerialize
         type: object
       - additionalProperties: true
-        description: Schema for serializer component ['map'] subtype.
+        description: Schema for serializer component ['obj'] subtype.
         properties:
-          delimiter:
-            default: "\t"
-            description: Delimiter that should be used to separate name/value pairs
-              in the map. Defaults to \t.
-            type: string
           seritype:
             default: default
-            description: Serialzation of mapping between key/value pairs with one
-              pair per line and using a character delimiter to separate keys and values.
+            description: Serialize 3D structures using Obj format.
             enum:
-            - map
+            - obj
             type: string
-        title: AsciiMapSerialize
+        title: yggdrasil.serialize.ObjSerialize.ObjSerialize
         type: object
       - additionalProperties: true
-        description: Schema for serializer component ['obj'] subtype.
+        description: Schema for serializer component ['pandas'] subtype.
         properties:
+          delimiter:
+            default: "\t"
+            description: Character(s) that should be used to separate columns. Defaults
+              to '\t'.
+            type: string
+          field_names:
+            description: The names of fields in the format string. If not provided,
+              names are set based on the order of the fields in the format string.
+            items:
+              type: string
+            type: array
+          field_units:
+            description: The units of fields in the format string. If not provided,
+              all fields are assumed to be dimensionless.
+            items:
+              type: string
+            type: array
+          format_str:
+            description: If provided, this string will be used to format messages
+              from a list of arguments and parse messages to get a list of arguments
+              in C printf/scanf style. Defaults to None and messages are assumed to
+              already be bytes.
+            type: string
+          no_header:
+            default: false
+            description: If True, headers will not be read or serialized from/to tables.
+              Defaults to False.
+            type: boolean
           seritype:
             default: default
-            description: Serialize 3D structures using Obj format.
+            description: Serializes tables using the pandas package.
             enum:
-            - obj
+            - pandas
             type: string
-        title: ObjSerialize
+          str_as_bytes:
+            default: false
+            description: If True, strings in columns are read as bytes. Defaults to
+              False.
+            type: boolean
+          use_astropy:
+            default: false
+            description: If True, the astropy package will be used to serialize/deserialize
+              table. Defaults to False.
+            type: boolean
+        title: yggdrasil.serialize.PandasSerialize.PandasSerialize
         type: object
       - additionalProperties: true
         description: Schema for serializer component ['pickle'] subtype.
@@ -2958,7 +3002,35 @@ definitions:
             enum:
             - pickle
             type: string
-        title: PickleSerialize
+        title: yggdrasil.serialize.PickleSerialize.PickleSerialize
+        type: object
+      - additionalProperties: true
+        description: Schema for serializer component ['ply'] subtype.
+        properties:
+          seritype:
+            default: default
+            description: Serialize 3D structures using Ply format.
+            enum:
+            - ply
+            type: string
+        title: yggdrasil.serialize.PlySerialize.PlySerialize
+        type: object
+      - additionalProperties: true
+        description: Schema for serializer component ['wofost'] subtype.
+        properties:
+          delimiter:
+            default: ' = '
+            description: Delimiter that should be used to separate name/value pairs
+              in the map. Defaults to \t.
+            type: string
+          seritype:
+            default: default
+            description: Serialization of mapping between keys and scalar or array
+              values as used in the WOFOST parameter files.
+            enum:
+            - wofost
+            type: string
+        title: yggdrasil.serialize.WOFOSTParamSerialize.WOFOSTParamSerialize
         type: object
       - additionalProperties: true
         description: Schema for serializer component ['yaml'] subtype.
@@ -2987,24 +3059,7 @@ definitions:
             enum:
             - yaml
             type: string
-        title: YAMLSerialize
-        type: object
-      - additionalProperties: true
-        description: Schema for serializer component ['wofost'] subtype.
-        properties:
-          delimiter:
-            default: ' = '
-            description: Delimiter that should be used to separate name/value pairs
-              in the map. Defaults to \t.
-            type: string
-          seritype:
-            default: default
-            description: Serialization of mapping between keys and scalar or array
-              values as used in the WOFOST parameter files.
-            enum:
-            - wofost
-            type: string
-        title: WOFOSTParamSerialize
+        title: yggdrasil.serialize.YAMLSerialize.YAMLSerialize
         type: object
     description: Schema for serializer components.
     title: serializer
@@ -3089,53 +3144,27 @@ definitions:
           transformtype:
             enum:
             - array
-        title: ArrayTransform
+        title: yggdrasil.communication.transforms.ArrayTransform.ArrayTransform
         type: object
       - additionalProperties: true
-        description: Schema for transform component ['pandas'] subtype.
+        description: Schema for transform component ['direct'] subtype.
         properties:
-          field_names:
-            items:
-              type: string
-            type: array
           transformtype:
             enum:
-            - pandas
-        title: PandasTransform
+            - direct
+        title: yggdrasil.communication.transforms.DirectTransform.DirectTransform
         type: object
       - additionalProperties: true
-        description: Schema for transform component ['select_fields'] subtype.
+        description: Schema for transform component ['filter'] subtype.
         properties:
-          original_order:
-            description: The original order of fields that should be used for selecting
-              from lists/tuples.
-            items:
-              type: string
-            type: array
-          selected:
-            description: A list of fields that should be selected.
-            items:
-              type: string
-            type: array
-          single_as_scalar:
-            description: If True and only a single field is selected, the transformed
-              messages will be scalars rather than arrays with single elements. Defaults
-              to False.
-            type: boolean
+          filter:
+            $ref: '#/definitions/filter'
           transformtype:
             enum:
-            - select_fields
+            - filter
         required:
-        - selected
-        title: SelectFieldsTransform
-        type: object
-      - additionalProperties: true
-        description: Schema for transform component ['iterate'] subtype.
-        properties:
-          transformtype:
-            enum:
-            - iterate
-        title: IterateTransform
+        - filter
+        title: yggdrasil.communication.transforms.FilterTransform.FilterTransform
         type: object
       - additionalProperties: true
         description: Schema for transform component ['function'] subtype.
@@ -3153,32 +3182,15 @@ definitions:
             - function
         required:
         - function
-        title: FunctionTransform
-        type: object
-      - additionalProperties: true
-        description: Schema for transform component ['statement'] subtype.
-        properties:
-          statement:
-            description: Python statement in terms of the message as represented by
-              the string "%x%" that should evaluate to the transformed message. The
-              statement should only use a limited set of builtins and the math library
-              (See yggdrasil.tools.safe_eval). If more complex relationships are required,
-              use the FunctionTransform class.
-            type: string
-          transformtype:
-            enum:
-            - statement
-        required:
-        - statement
-        title: StatementTransform
+        title: yggdrasil.communication.transforms.FunctionTransform.FunctionTransform
         type: object
       - additionalProperties: true
-        description: Schema for transform component ['direct'] subtype.
+        description: Schema for transform component ['iterate'] subtype.
         properties:
           transformtype:
             enum:
-            - direct
-        title: DirectTransform
+            - iterate
+        title: yggdrasil.communication.transforms.IterateTransform.IterateTransform
         type: object
       - additionalProperties: true
         description: Schema for transform component ['map_fields'] subtype.
@@ -3193,19 +3205,62 @@ definitions:
             - map_fields
         required:
         - map
-        title: MapFieldsTransform
+        title: yggdrasil.communication.transforms.MapFieldsTransform.MapFieldsTransform
         type: object
       - additionalProperties: true
-        description: Schema for transform component ['filter'] subtype.
+        description: Schema for transform component ['pandas'] subtype.
         properties:
-          filter:
-            $ref: '#/definitions/filter'
+          field_names:
+            items:
+              type: string
+            type: array
           transformtype:
             enum:
-            - filter
+            - pandas
+        title: yggdrasil.communication.transforms.PandasTransform.PandasTransform
+        type: object
+      - additionalProperties: true
+        description: Schema for transform component ['select_fields'] subtype.
+        properties:
+          original_order:
+            description: The original order of fields that should be used for selecting
+              from lists/tuples.
+            items:
+              type: string
+            type: array
+          selected:
+            description: A list of fields that should be selected.
+            items:
+              type: string
+            type: array
+          single_as_scalar:
+            description: If True and only a single field is selected, the transformed
+              messages will be scalars rather than arrays with single elements. Defaults
+              to False.
+            type: boolean
+          transformtype:
+            enum:
+            - select_fields
         required:
-        - filter
-        title: FilterTransform
+        - selected
+        title: yggdrasil.communication.transforms.SelectFieldsTransform.SelectFieldsTransform
+        type: object
+      - additionalProperties: true
+        description: Schema for transform component ['statement'] subtype.
+        properties:
+          statement:
+            description: Python statement in terms of the message as represented by
+              the string "%x%" that should evaluate to the transformed message. The
+              statement should only use a limited set of builtins and the math library
+              (See yggdrasil.tools.safe_eval). If more complex relationships are required,
+              use the FunctionTransform class.
+            type: string
+          transformtype:
+            enum:
+            - statement
+        required:
+        - statement
+        title: yggdrasil.communication.transforms.StatementTransform.StatementTransform
         type: object
     description: Schema for transform components.
     title: transform
diff --git a/yggdrasil/command_line.py b/yggdrasil/command_line.py
index 67c3b1cfd..405714334 100755
--- a/yggdrasil/command_line.py
+++ b/yggdrasil/command_line.py
@@ -7,7 +7,9 @@
 import argparse
 import pprint
 import shutil
-from yggdrasil.constants import LANGUAGES, LANGUAGES_WITH_ALIASES
+from yggdrasil import constants
+LANGUAGES = getattr(constants, 'LANGUAGES', {})
+LANGUAGES_WITH_ALIASES = getattr(constants, 'LANGUAGES_WITH_ALIASES', {})
 
 
 logger = logging.getLogger(__name__)
@@ -139,7 +141,7 @@ def parse_args(cls, parser, args=None, allow_unknown=False,
                 if isinstance(v_flag, list):
                     v.extend(v_flag)
                 if (len(v) == 0) or ('all' in v):
-                    setattr(args, k, LANGUAGES['all'])
+                    setattr(args, k, LANGUAGES.get('all', []))
                     args.all_languages = True
         return args
 
@@ -225,7 +227,11 @@ class yggrun(SubCommand):
           'help': 'Number of MPI processes to run on.'}),
         (('--mpi-tag-start', ),
          {'type': int, 'default': 0,
-          'help': 'Tag that MPI communications should start at.'})]
+          'help': 'Tag that MPI communications should start at.'}),
+        (('--validate', ),
+         {'action': 'store_true',
+          'help': ('Validate the run via model validation commands on '
+                   'completion.')})]
 
     @classmethod
     def add_arguments(cls, parser, **kwargs):
@@ -252,7 +258,8 @@ def func(cls, args):
         with config.parser_config(args):
             runner.run(args.yamlfile, ygg_debug_prefix=prog,
                        production_run=args.production_run,
-                       mpi_tag_start=args.mpi_tag_start)
+                       mpi_tag_start=args.mpi_tag_start,
+                       validate=args.validate)
 
 
 class integration_service_manager(SubCommand):
@@ -328,7 +335,17 @@ class integration_service_manager(SubCommand):
                                    'registered integration.')}),
                         (('--with-coverage', ),
                          {'action': 'store_true',
-                          'help': ('Enable coverage cleanup for testing.')})]),
+                          'help': ('Enable coverage cleanup for testing.')}),
+                        (('--model-repository', ),
+                         {'type': str,
+                          'help': ('URL for a directory in a Git repository '
+                                   'containing models that should be loaded '
+                                   'into the service manager registry.')}),
+                        (('--log-level', ),
+                         {'type': int,
+                          'help': ('Level of logging that should be '
+                                   'performed for the service manager '
+                                   'application.')})]),
                 ArgumentParser(
                     name='stop',
                     help=('Stop an integration service manager or '
@@ -405,7 +422,10 @@ def func(cls, args):
                 if not x.is_running:
                     x.start_server(
                         remote_url=getattr(args, 'remote_url', None),
-                        with_coverage=getattr(args, 'with_coverage', False))
+                        with_coverage=getattr(args, 'with_coverage', False),
+                        model_repository=getattr(args, 'model_repository',
+                                                 None),
+                        log_level=getattr(args, 'log_level', None))
             else:
                 x.send_request(integration_name,
                                yamls=integration_yamls,
@@ -450,7 +470,7 @@ class ygginfo(SubCommand):
             description='Compilation tool types to get info about.',
             arguments=[
                 (('language', ),
-                 {'choices': LANGUAGES_WITH_ALIASES['compiled'],
+                 {'choices': LANGUAGES_WITH_ALIASES.get('compiled', []),
                   'type': str.lower,
                   'help': 'Language to get tool information for.'}),
                 (('--toolname', ),
@@ -832,14 +852,18 @@ class validate_yaml(SubCommand):
                    'ensuring that it is part of a complete integration.')}),
         (('--model-submission', ),
          {'action': 'store_true',
-          'help': ('Validate a YAML against the schema for submissions to '
-                   'the yggdrasil model repository.')})]
+          'help': ('Validate a YAML against the requirements for '
+                   'submissions to the yggdrasil model repository.')})]
 
     @classmethod
     def func(cls, args):
-        from yggdrasil import yamlfile
-        yamlfile.parse_yaml(args.yamlfile, model_only=args.model_only,
-                            model_submission=args.model_submission)
+        if args.model_submission:
+            from yggdrasil.services import validate_model_submission
+            validate_model_submission(args.yamlfile)
+        else:
+            from yggdrasil import yamlfile
+            yamlfile.parse_yaml(args.yamlfile, model_only=args.model_only,
+                                model_submission=args.model_submission)
         logger.info("Validation succesful.")
 
 
@@ -856,7 +880,8 @@ class yggcc(SubCommand):
                    "R package.")}),
         (('--language', ),
          {'default': None,
-          'choices': [None] + LANGUAGES_WITH_ALIASES['compiled'] + ['R', 'r'],
+          'choices': ([None] + LANGUAGES_WITH_ALIASES.get('compiled', [])
+                      + ['R', 'r']),
           'help': ("Language of the source code. If not provided, "
                    "the language will be determined from the "
                    "source extension.")}),
@@ -899,8 +924,8 @@ class yggcompile(SubCommand):
     arguments = [
         (('language', ),
          {'nargs': '*', 'default': ['all'],
-          # 'choices': (['all'] + LANGUAGES_WITH_ALIASES['compiled']
-          #             + LANGUAGES_WITH_ALIASES['compiled_dsl']),
+          # 'choices': (['all'] + LANGUAGES_WITH_ALIASES.get('compiled', [])
+          #             + LANGUAGES_WITH_ALIASES.get('compiled_dsl', [])),
           'help': ("One or more languages to compile dependencies "
                    "for.")}),
         (('--toolname', ),
@@ -935,7 +960,7 @@ class yggclean(SubCommand):
     arguments = [
         (('language', ),
          {'nargs': '*', 'default': ['all'],
-          # 'choices': ['all'] + LANGUAGES_WITH_ALIASES['all'],
+          # 'choices': ['all'] + LANGUAGES_WITH_ALIASES.get('all', []),
           'help': ("One or more languages to clean up dependencies "
                    "for.")})]
 
@@ -1076,12 +1101,12 @@ class update_config(SubCommand):
     arguments = (
         [(('languages', ),
           {'nargs': '*',
-           # 'choices': ['all'] + LANGUAGES_WITH_ALIASES['all'],
+           # 'choices': ['all'] + LANGUAGES_WITH_ALIASES.get('all', []),
            'default': [],
            'help': 'One or more languages that should be configured.'}),
          (('--languages', ),
           {'nargs': '+', 'dest': 'languages_flag',
-           # 'choices': ['all'] + LANGUAGES_WITH_ALIASES['all'],
+           # 'choices': ['all'] + LANGUAGES_WITH_ALIASES.get('all', []),
            'default': [],
            'help': 'One or more languages that should be configured.'}),
          (('--show-file', ),
@@ -1095,11 +1120,11 @@ class update_config(SubCommand):
            'help': 'Overwrite the existing file.'}),
          (('--disable-languages', ),
           {'nargs': '+', 'default': [],
-           'choices': LANGUAGES_WITH_ALIASES['all'],
+           'choices': LANGUAGES_WITH_ALIASES.get('all', []),
            'help': 'One or more languages that should be disabled.'}),
          (('--enable-languages', ),
           {'nargs': '+', 'default': [],
-           'choices': LANGUAGES_WITH_ALIASES['all'],
+           'choices': LANGUAGES_WITH_ALIASES.get('all', []),
            'help': 'One or more languages that should be enabled.'}),
          (('--quiet', '-q'),
           {'action': 'store_true',
@@ -1120,15 +1145,15 @@ class update_config(SubCommand):
         + [(('--%s-compiler' % k, ),
             {'help': ('Name or path to compiler that should be used to compile '
                       'models written in %s.' % k)})
-           for k in LANGUAGES['compiled']]
+           for k in LANGUAGES.get('compiled', [])]
         + [(('--%s-linker' % k, ),
             {'help': ('Name or path to linker that should be used to link '
                       'models written in %s.' % k)})
-           for k in LANGUAGES['compiled']]
+           for k in LANGUAGES.get('compiled', [])]
         + [(('--%s-archiver' % k, ),
             {'help': ('Name or path to archiver that should be used to create '
                       'static libraries for models written in %s.' % k)})
-           for k in LANGUAGES['compiled']]
+           for k in LANGUAGES.get('compiled', [])]
     )
     # TODO: Move these into the language directories?
     language_arguments = {
@@ -1189,7 +1214,7 @@ def add_arguments(cls, parser, **kwargs):
         if preargs.languages_flag:
             prelang += preargs.languages_flag
         if (len(prelang) == 0) or ('all' in prelang):
-            prelang = LANGUAGES['all']
+            prelang = LANGUAGES.get('all', [])
         # TODO: The languages could be subparsers
         for k, v in cls.language_arguments.items():
             if k in prelang:
@@ -1221,7 +1246,7 @@ def func(cls, args):
                     lang_kwargs.setdefault(k, {})
                     lang_kwargs[k][name] = getattr(args, name)
         for x in ['compiler', 'linker', 'archiver']:
-            for k in LANGUAGES['compiled']:
+            for k in LANGUAGES.get('compiled', []):
                 if getattr(args, '%s_%s' % (k, x), None):
                     lang_kwargs.setdefault(k, {})
                     lang_kwargs[k][x] = getattr(args, '%s_%s' % (k, x))
@@ -1269,7 +1294,8 @@ def func(cls, args):
                 os.remove(schema._schema_fname)
             schema.clear_schema()
             schema.init_schema()
-        schema.update_constants()
+        else:
+            schema.update_constants()
 
 
 class yggmodelform(SubCommand):
diff --git a/yggdrasil/communication/CommBase.py b/yggdrasil/communication/CommBase.py
index 12771c35e..3b74d3f94 100755
--- a/yggdrasil/communication/CommBase.py
+++ b/yggdrasil/communication/CommBase.py
@@ -7,13 +7,13 @@
 import time
 import collections
 import numpy as np
-from yggdrasil import tools, multitasking
-from yggdrasil.tools import YGG_MSG_EOF
+from yggdrasil import tools, multitasking, constants
 from yggdrasil.communication import (
     new_comm, get_comm, determine_suffix, TemporaryCommunicationError,
     import_comm, check_env_for_address)
-from yggdrasil.components import import_component, create_component
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError, type2numpy
+from yggdrasil.components import (
+    import_component, create_component, ComponentError)
+from yggdrasil.metaschema import MetaschemaTypeError, type2numpy
 from yggdrasil.metaschema.datatypes.MetaschemaType import MetaschemaType
 from yggdrasil.communication.transforms.TransformBase import TransformBase
 from yggdrasil.serialize import consolidate_array
@@ -33,10 +33,6 @@
 FLAG_EMPTY = 6
 
 
-YGG_CLIENT_INI = b'YGG_BEGIN_CLIENT'
-YGG_CLIENT_EOF = b'YGG_END_CLIENT'
-
-
 class NeverMatch(Exception):
     'An exception class that is never raised by any code anywhere'
 
@@ -576,7 +572,6 @@ class CommBase(tools.YggClass):
         'for_service': {'type': 'boolean', 'default': False}}
     _schema_excluded_from_class = ['name']
     _default_serializer = 'default'
-    _default_serializer_class = None
     _schema_excluded_from_class_validation = ['datatype']
     is_file = False
     _maxMsgSize = 0
@@ -751,12 +746,8 @@ def _init_before_open(self, **kwargs):
         if self.serializer is None:
             # Get serializer class
             if seri_cls is None:
-                if (((seri_kws['seritype'] == self._default_serializer)
-                     and (self._default_serializer_class is not None))):
-                    seri_cls = self._default_serializer_class
-                else:
-                    seri_cls = import_component('serializer',
-                                                subtype=seri_kws['seritype'])
+                seri_cls = import_component('serializer',
+                                            subtype=seri_kws['seritype'])
             # Recover keyword arguments for serializer passed to comm class
             for k in seri_cls.seri_kws():
                 if k in kwargs:
@@ -783,7 +774,7 @@ def _init_before_open(self, **kwargs):
                 if isinstance(iv, str):
                     try:
                         iv = create_component('transform', subtype=iv)
-                    except ValueError:
+                    except ComponentError:
                         iv = None
                 elif isinstance(iv, dict):
                     from yggdrasil.schema import get_schema
@@ -811,15 +802,6 @@ def _init_before_open(self, **kwargs):
                               subtype=filter_schema.identify_subtype(self.filter))
             self.filter = create_component('filter', **filter_kws)
 
-    @staticmethod
-    def before_registration(cls):
-        r"""Operations that should be performed to modify class attributes prior
-        to registration."""
-        tools.YggClass.before_registration(cls)
-        cls._default_serializer_class = import_component('serializer',
-                                                         cls._default_serializer,
-                                                         without_schema=True)
-
     @classmethod
     def get_testing_options(cls, serializer=None, **kwargs):
         r"""Method to return a dictionary of testing options for this class.
@@ -842,13 +824,13 @@ def get_testing_options(cls, serializer=None, **kwargs):
         """
         if serializer is None:
             serializer = cls._default_serializer
-        if (((serializer == cls._default_serializer)
-             and (cls._default_serializer_class is not None))):
-            seri_cls = cls._default_serializer_class
-        else:
-            seri_cls = import_component('serializer', serializer)
+        seri_cls = import_component('serializer', serializer)
         out_seri = seri_cls.get_testing_options(**kwargs)
-        out = {'kwargs': out_seri['kwargs'],
+        out = {'attributes': ['name', 'address', 'direction',
+                              'serializer', 'recv_timeout',
+                              'close_on_eof_recv', 'opp_address',
+                              'opp_comms', 'maxMsgSize'],
+               'kwargs': out_seri['kwargs'],
                'send': copy.deepcopy(out_seri['objects']),
                'msg': out_seri['objects'][0],
                'contents': out_seri['contents'],
@@ -876,11 +858,11 @@ def get_status_message(self, nindent=0, extra_lines_before=None,
         Args:
             nindent (int, optional): Number of tabs that should be used to
                 indent each line. Defaults to 0.
-            extra_lines_before (list, optional): Additional lines that should be
-                added to the beginning of the default print message. Defaults to
-                empty list if not provided.
-            extra_lines_after (list, optional): Additional lines that should be
-                added to the end of the default print message. Defaults to
+            extra_lines_before (list, optional): Additional lines that should
+                be added to the beginning of the default print message.
+                Defaults to empty list if not provided.
+            extra_lines_after (list, optional): Additional lines that should
+                be added to the end of the default print message. Defaults to
                 empty list if not provided.
                 
         Returns:
@@ -973,7 +955,7 @@ def is_installed(cls, language=None):
                 try:
                     drv = import_component('model', language)
                     out = drv.is_comm_installed(commtype=cls._commtype)
-                except ValueError:
+                except ComponentError:
                     out = False
         return out
 
@@ -1378,7 +1360,7 @@ def n_msg_send_drain(self):
     @property
     def eof_msg(self):
         r"""str: Message indicating EOF."""
-        return YGG_MSG_EOF
+        return constants.YGG_MSG_EOF
 
     def is_eof(self, msg):
         r"""Determine if a message is an EOF.
@@ -1532,9 +1514,9 @@ def is_empty(self, msg, emsg):
             bool: True if the object is empty, False otherwise.
 
         """
-        from yggdrasil.tests import assert_equal
         try:
-            assert_equal(msg, emsg, dont_print_diff=True)
+            from yggdrasil.tests import assert_equal
+            assert_equal(msg, emsg)
         except AssertionError:
             return False
         return True
@@ -2025,7 +2007,7 @@ def prepare_message(self, *args, header_kwargs=None, skip_serialization=False,
         # Make duplicates
         once_per_partner = ((msg.flag == FLAG_EOF)
                             or (isinstance(msg.args, bytes)
-                                and (msg.args == YGG_CLIENT_EOF)))
+                                and (msg.args == constants.YGG_CLIENT_EOF)))
         if once_per_partner and (self.partner_copies > 1):
             self.debug("Sending %s to %d model(s)", msg.args,
                        self.partner_copies)
diff --git a/yggdrasil/communication/FileComm.py b/yggdrasil/communication/FileComm.py
index 8d44f7b62..369b7f01e 100755
--- a/yggdrasil/communication/FileComm.py
+++ b/yggdrasil/communication/FileComm.py
@@ -4,6 +4,7 @@
 from yggdrasil import platform, tools
 from yggdrasil.serialize.SerializeBase import SerializeBase
 from yggdrasil.communication import CommBase
+from yggdrasil.components import import_component
 
 
 class FileComm(CommBase.CommBase):
@@ -80,7 +81,7 @@ class FileComm(CommBase.CommBase):
                                   'class': SerializeBase}],
                        'default': {'seritype': 'direct'}}}
     _schema_excluded_from_inherit = (
-        ['commtype', 'datatype', 'read_meth', 'serializer', 'default_value']
+        ['commtype', 'datatype', 'read_meth', 'serializer']
         + CommBase.CommBase._model_schema_prop)
     _schema_excluded_from_class_validation = ['serializer']
     _schema_base_class = None
@@ -139,9 +140,17 @@ def before_registration(cls):
         # Add serializer properties to schema
         if cls._filetype != 'binary':
             assert('serializer' not in cls._schema_properties)
-            cls._schema_properties.update(
-                cls._default_serializer_class._schema_properties)
-            del cls._schema_properties['seritype']
+            # if registration_in_progress():
+            seri = import_component('serializer', cls._default_serializer)
+            new = seri._schema_properties
+            # else:
+            #     from yggdrasil.schema import get_schema
+            #     schema = get_schema()
+            #     new = schema['file'].get_subtype_schema(
+            #         cls._default_serializer)['properties']
+            cls._schema_properties.update(new)
+            for k in ['driver', 'args', 'seritype']:
+                cls._schema_properties.pop(k, None)
         cls._commtype = cls._filetype
 
     @classmethod
@@ -182,7 +191,7 @@ def get_testing_options(cls, read_meth=None, **kwargs):
                 out['contents'] += comment
                 out['recv_partial'].append([])
         else:
-            seri_cls = cls._default_serializer_class
+            seri_cls = import_component('serializer', cls._default_serializer)
             if seri_cls.concats_as_str:
                 out['recv_partial'] = [[x] for x in out['recv']]
                 out['recv'] = seri_cls.concatenate(out['recv'], **out['kwargs'])
diff --git a/yggdrasil/communication/ForkComm.py b/yggdrasil/communication/ForkComm.py
index a8adde20d..1ae6ee9ea 100644
--- a/yggdrasil/communication/ForkComm.py
+++ b/yggdrasil/communication/ForkComm.py
@@ -157,17 +157,26 @@ def disconnect(self):
             x.disconnect()
         super(ForkComm, self).disconnect()
         
-    def printStatus(self, nindent=0, return_str=False, **kwargs):
-        r"""Print status of the communicator."""
-        out = super(ForkComm, self).printStatus(nindent=nindent,
-                                                return_str=return_str,
-                                                **kwargs)
-        for x in self.comm_list:
-            x_out = x.printStatus(nindent=nindent + 1, return_str=return_str)
-            if return_str:
-                out += '\n' + x_out
-        return out
+    def get_status_message(self, **kwargs):
+        r"""Return lines composing a status message.
+        
+        Args:
+            **kwargs: Keyword arguments are passed on to the parent class's
+                method.
+                
+        Returns:
+            tuple(list, prefix): Lines composing the status message and the
+                prefix string used for the last message.
 
+        """
+        nindent = kwargs.get('nindent', 0)
+        extra_lines_after = ['%-15s: %s' % ('pattern', self.pattern)]
+        for x in self.comm_list:
+            extra_lines_after += x.get_status_message(nindent=nindent + 1)[0]
+        extra_lines_after += kwargs.get('extra_lines_after', [])
+        kwargs['extra_lines_after'] = extra_lines_after
+        return super(ForkComm, self).get_status_message(**kwargs)
+        
     def __len__(self):
         return len(self.comm_list)
 
diff --git a/yggdrasil/communication/ServerComm.py b/yggdrasil/communication/ServerComm.py
index 585d5b3a8..199e8817b 100644
--- a/yggdrasil/communication/ServerComm.py
+++ b/yggdrasil/communication/ServerComm.py
@@ -1,6 +1,7 @@
 import os
 import uuid
 from collections import OrderedDict
+from yggdrasil import constants
 from yggdrasil.communication import CommBase, get_comm, import_comm
 
 
@@ -362,7 +363,7 @@ def finalize_message(self, msg, **kwargs):
         """
         def check_for_client_info(msg):
             if msg.flag == CommBase.FLAG_SUCCESS:
-                if isinstance(msg.args, bytes) and (msg.args == CommBase.YGG_CLIENT_EOF):
+                if isinstance(msg.args, bytes) and (msg.args == constants.YGG_CLIENT_EOF):
                     self.debug("Client signed off: %s", msg.header['model'])
                     self.closed_clients.append(msg.header['model'])
                     msg.flag = CommBase.FLAG_SKIP
diff --git a/yggdrasil/communication/transforms/ArrayTransform.py b/yggdrasil/communication/transforms/ArrayTransform.py
index 766f31bfd..667ba4d3c 100644
--- a/yggdrasil/communication/transforms/ArrayTransform.py
+++ b/yggdrasil/communication/transforms/ArrayTransform.py
@@ -1,12 +1,11 @@
 import numpy as np
 import copy
 import pandas
+from yggdrasil import constants
 from yggdrasil.communication.transforms.TransformBase import TransformBase
-from yggdrasil.metaschema.datatypes import type2numpy
+from yggdrasil.metaschema import type2numpy
 from yggdrasil.serialize import (
     consolidate_array, pandas2numpy, numpy2pandas, dict2list)
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    _valid_types, _flexible_types)
 
 
 class ArrayTransform(TransformBase):
@@ -63,7 +62,7 @@ def get_summary(cls, x, subtype=False):
                 s = (s,)
             t = '1darray'
         elif ((x['type'] == 'scalar')
-              or (x['type'] in _valid_types)):
+              or (x['type'] in constants.VALID_TYPES)):
             s = (1,)
             t = 'scalar'
         else:
@@ -71,11 +70,11 @@ def get_summary(cls, x, subtype=False):
                                   "to array elements.") % x['type'])
         subt = x.get('subtype', x['type'])
         title = x.get('title', None)
-        assert(subt in _valid_types)
+        assert(subt in constants.VALID_TYPES)
         if subtype:
             out = {'type': t, 'subtype': subt,
                    'shape': s, 'title': title}
-            if subt not in _flexible_types:
+            if subt not in constants.FLEXIBLE_TYPES:
                 out['precision'] = x.get('precision', 0)
         else:
             out = {'type': t, 'shape': s}
@@ -264,7 +263,7 @@ def transform_array_items(cls, items, order=None):
                     subtype=x.get('subtype', x['type']))
                for x in base_types]
         for i, x in enumerate(out):
-            if x['subtype'] in _flexible_types:
+            if x['subtype'] in constants.FLEXIBLE_TYPES:
                 x['precision'] = max(
                     [y['items'][i].get('precision', 0) for y in items])
                 if x['precision'] == 0:
diff --git a/yggdrasil/components.py b/yggdrasil/components.py
index 7c71c3c67..fe42bf131 100644
--- a/yggdrasil/components.py
+++ b/yggdrasil/components.py
@@ -4,39 +4,19 @@
 import six
 import inspect
 import importlib
-# import warnings
+import contextlib
 import weakref
 from collections import OrderedDict
 from yggdrasil.doctools import docs2args
 
 
 _registry = {}
-_registry_defaults = {}
-_registry_base_classes = {}
-_registry_class2subtype = {}
 _registry_complete = False
-_comptype2key = {'comm': 'commtype',
-                 'file': 'filetype',
-                 'model': 'language',
-                 'connection': 'connection_type',
-                 # 'datatype': None,
-                 'serializer': 'seritype',
-                 'filter': 'filtertype',
-                 'transform': 'transformtype'}
-# 'compiler': 'toolname',
-# 'linker': 'toolname',
-# 'archiver': 'toolname'}
-_comptype2mod = {'serializer': 'serialize',
-                 'comm': 'communication',
-                 'file': 'communication',
-                 'model': 'drivers',
-                 'connection': 'drivers',
-                 'filter': 'communication.filters',
-                 'transform': 'communication.transforms'}
-# 'datatype': ['metaschema', 'datatypes'],
-# 'compiler': 'drivers',
-# 'linker': 'drivers',
-# 'archiver': 'drivers'}
+
+
+class ComponentError(BaseException):
+    r"""Error raised when there is a problem import a component."""
+    pass
 
 
 class ClassRegistry(OrderedDict):
@@ -46,7 +26,6 @@ def __init__(self, *args, import_function=None, **kwargs):
         module = inspect.getmodule(inspect.stack()[1][0])
         self._module = module.__name__
         self._directory = os.path.dirname(module.__file__)
-        # self._schema_directory = os.path.join(self._directory, 'schemas')
         self._import_function = import_function
         self._imported = False
         super(ClassRegistry, self).__init__(*args, **kwargs)
@@ -56,7 +35,7 @@ def import_classes(self):
         if self._imported:
             return
         self._imported = True
-        for x in glob.glob(os.path.join(self._directory, '*.py')):
+        for x in sorted(glob.glob(os.path.join(self._directory, '*.py'))):
             mod = os.path.basename(x)[:-3]
             if not mod.startswith('__'):
                 importlib.import_module(self._module + '.%s' % mod)
@@ -98,52 +77,72 @@ def has_entry(self, key):
         return super(ClassRegistry, self).__contains__(key)
 
 
-def init_registry():
+def registration_in_progress():
+    r"""Determine if a registration is in progress."""
+    return bool(os.environ.get('YGGDRASIL_REGISTRATION_IN_PROGRESS', None))
+
+
+@contextlib.contextmanager
+def registering(recurse=False):
+    r"""Context for preforming registration."""
+    if not recurse:
+        assert(not registration_in_progress())
+    try:
+        previous = os.environ.get('YGGDRASIL_REGISTRATION_IN_PROGRESS', None)
+        os.environ['YGGDRASIL_REGISTRATION_IN_PROGRESS'] = '1'
+        yield
+    finally:
+        if previous is None:
+            if 'YGGDRASIL_REGISTRATION_IN_PROGRESS' in os.environ:
+                del os.environ['YGGDRASIL_REGISTRATION_IN_PROGRESS']
+        else:
+            os.environ['YGGDRASIL_REGISTRATION_IN_PROGRESS'] = previous
+
+
+def init_registry(recurse=False):
     r"""Initialize the registries and schema."""
+    from yggdrasil.tools import import_all_modules
     global _registry
     global _registry_complete
-    if not _registry_complete:
-        comp_list = []
-        mod_list = []
-        for k, v in _comptype2mod.items():
-            if v not in mod_list:
-                comp_list.append(k)
-                mod_list.append(v)
-        for k in comp_list:
-            import_all_components(k)
+    with registering(recurse=recurse):
+        import_all_modules(exclude=['yggdrasil.examples',
+                                    'yggdrasil.languages',
+                                    'yggdrasil.interface',
+                                    'yggdrasil.timing'],
+                           do_first=['yggdrasil.serialize'])
         _registry_complete = True
     return _registry
 
 
-# This dosn't work as desried because classes that have already been imported
-# will not call registration on second import
-# def clear_registry():
-#     r"""Reset registries."""
-#     global _registry
-#     global _registry_defaults
-#     global _registry_class2subtype
-#     global _registry_complete
-#     _registry = {}
-#     _registry_defaults = {}
-#     _registry_class2subtype = {}
-#     _registry_complete = False
+def get_registry(comptype=None):
+    r"""Get the registry that should be used for looking up components.
+
+    Args:
+        comptype (str, optional): The name of a component to get the
+            registry for. Defaults to None and the entire registry will be
+            returned.
+
+    """
+    global _registry
+    if registration_in_progress():
+        out = _registry
+    else:
+        from yggdrasil import constants
+        out = constants.COMPONENT_REGISTRY
+    if comptype:
+        if comptype not in out:  # pragma: debug
+            raise Exception(f"Importing a component type that has not yet "
+                            f"been registered: {comptype}")
+        out = out[comptype]
+    return out
+
 
-    
 def suspend_registry():
     r"""Suspend the registry by storing the global registries in a dictionary."""
     global _registry
-    global _registry_defaults
-    global _registry_base_classes
-    global _registry_class2subtype
     global _registry_complete
-    out = {'_registry': _registry, '_registry_defaults': _registry_defaults,
-           '_registry_base_classes': _registry_base_classes,
-           '_registry_class2subtype': _registry_class2subtype,
-           '_registry_complete': _registry_complete}
+    out = {'_registry': _registry, '_registry_complete': _registry_complete}
     _registry = {}
-    _registry_defaults = {}
-    _registry_base_classes = {}
-    _registry_class2subtype = {}
     _registry_complete = False
     return out
 
@@ -151,59 +150,21 @@ def suspend_registry():
 def restore_registry(reg_dict):
     r"""Restore the registry to values in the provided dictionary."""
     global _registry
-    global _registry_defaults
-    global _registry_base_classes
-    global _registry_class2subtype
     global _registry_complete
     _registry = reg_dict['_registry']
-    _registry_defaults = reg_dict['_registry_defaults']
-    _registry_base_classes = reg_dict['_registry_base_classes']
-    _registry_class2subtype = reg_dict['_registry_class2subtype']
     _registry_complete = reg_dict['_registry_complete']
 
 
-def import_all_components(comptype):
-    r"""Dynamically import all component classes for a component type.
-
-    Args:
-        comptype (str): Component type.
-
-    """
-    # Get module and directory
-    mod = copy.deepcopy(_comptype2mod[comptype])
-    moddir = os.path.join(*copy.deepcopy(_comptype2mod[comptype]).split('.'))
-    # The next three lines will be required if there are ever any components
-    # nested in multiple directories (e.g. metaschema/datatypes)
-    # if isinstance(mod, list):
-    #     mod = '.'.join(mod)
-    #     moddir = os.path.join(*moddir)
-    moddir = os.path.join(os.path.dirname(__file__), moddir)
-    modbase = importlib.import_module('yggdrasil.%s' % mod)
-    non_comp = [os.path.splitext(x)[0] for x in
-                getattr(modbase, '_non_component_modules', [])]
-    # Import all files
-    for x in glob.glob(os.path.join(moddir, '*.py')):
-        xbase = os.path.splitext(os.path.basename(x))[0]
-        if (not xbase.startswith('__')) and (xbase not in non_comp):
-            importlib.import_module('yggdrasil.%s.%s'
-                                    % (mod, xbase))
-
-
-def import_component(comptype, subtype=None, without_schema=False,
-                     **kwargs):
+def import_component(comptype, subtype=None, **kwargs):
     r"""Dynamically import a component by name.
 
     Args:
         comptype (str): Component type.
-        subtype (str, optional): Component subtype. If subtype is not one of the
-            registered subtypes for the specified comptype, subtype is treated
-            as the name of class. Defaults to None if not provided and the
-            default subtype defined in the schema for the specified component
-            will be used.
-        without_schema (bool, optional): If True, the schema is not used to
-            import the component and subtype must be the name of a component
-            class. Defaults to False. subtype must be provided if without_schema
-            is True.
+        subtype (str, optional): Component subtype. If subtype is not one of
+            the registered subtypes for the specified comptype, subtype is
+            treated as the name of class. Defaults to None if not provided and
+            the default subtype defined in the schema for the specified
+            component will be used.
         **kwargs: Additional keyword arguments are used to determine the
             subtype if it is None.
 
@@ -211,70 +172,39 @@ def import_component(comptype, subtype=None, without_schema=False,
         class: Component class.
 
     Raises:
-        ValueError: If subtype is not provided, but without_schema is True.
-        ValueError: If comptype is not a registered component type.
-        ValueError: If subtype is not a registered subtype or the name of a
-            registered subtype class for the specified comptype.
+        ComponentError: If comptype is not a registered component type.
+        ComponentError: If subtype is not a registered subtype or the name of
+            a registered subtype class for the specified comptype.
             
 
     """
-    # Get module
-    mod = _comptype2mod[comptype]
-    # if isinstance(mod, list):
-    #     mod = '.'.join(mod)
-    # Set direct import shortcuts for unregistered classes
-    if (((subtype is None) and (comptype in _comptype2key)
-         and (_comptype2key[comptype] in kwargs))):
-        subtype = kwargs[_comptype2key[comptype]]
+    registry = get_registry(comptype=comptype)
+    if subtype is None:
+        subtype = kwargs.get(registry["key"], None)
     if (comptype == 'comm') and (subtype is None):
         subtype = 'DefaultComm'
-    if (((comptype in ['comm', 'file']) and (subtype is not None)
-         and ((subtype == 'CommBase') or subtype.endswith('Comm')))):
-        without_schema = True
-    # Set default based on registry to avoid schema if possible
-    if (subtype is None) and (comptype in _registry_defaults):
-        subtype = _registry_defaults.get(comptype, None)
-    # Check registered components to prevent importing multiple times
-    if subtype in _registry.get(comptype, {}):
-        out_cls = _registry[comptype][subtype]
-    elif subtype in _registry_class2subtype.get(comptype, {}):
-        out_cls = _registry[comptype][_registry_class2subtype[comptype][subtype]]
+    if subtype is None:
+        subtype = registry["default"]
+    if subtype in registry["subtypes"]:
+        class_name = registry["subtypes"][subtype]
     else:
-        # Get class name
-        if without_schema:
-            if subtype is None:  # pragma: debug
-                raise ValueError("subtype must be provided if without_schema is True.")
-            class_name = subtype
-        else:
-            from yggdrasil.schema import get_schema
-            s = get_schema().get(comptype, None)
-            if s is None:  # pragma: debug
-                raise ValueError("Unrecognized component type: %s" % comptype)
-            if subtype is None:  # pragma: no cover
-                # This will only be called if the test is run before the component
-                # module is imported
-                subtype = s.default_subtype
-            if subtype in s.class2subtype:
-                class_name = subtype
-            else:
-                class_name = s.subtype2class.get(subtype, None)
-                if class_name is None:
-                    # Attempt file since they are subclass of comm
-                    if (comptype == 'comm'):
-                        try:
-                            return import_component('file', subtype)
-                        except ValueError:
-                            pass
-                    raise ValueError("Unrecognized %s subtype: %s"
-                                     % (comptype, subtype))
+        class_name = subtype
+    # Check registered components to prevent importing multiple times
+    if class_name not in registry.get("classes", {}):
+        registry.setdefault("classes", {})
         try:
-            out_mod = importlib.import_module('yggdrasil.%s.%s' % (mod, class_name))
-        except ImportError:  # pragma: debug
-            import_all_components(comptype)
-            return import_component(comptype, subtype=subtype,
-                                    without_schema=without_schema,
-                                    **kwargs)
-        out_cls = getattr(out_mod, class_name)
+            registry["classes"][class_name] = getattr(
+                importlib.import_module(f"{registry['module']}.{class_name}"),
+                class_name)
+        except ImportError:
+            if comptype == 'comm':
+                try:
+                    return import_component('file', subtype, **kwargs)
+                except ComponentError:
+                    pass
+            raise ComponentError(f"Could not locate a {comptype} component "
+                                 f"{subtype}.")
+    out_cls = registry["classes"][class_name]
     # Check for an aliased class
     if hasattr(out_cls, '_get_alias'):
         out_cls = out_cls._get_alias()
@@ -303,13 +233,13 @@ def create_component(comptype, subtype=None, **kwargs):
             options.
     
     Raises:
-        ValueError: If comptype is not a registered component type.
+        ComponentError: If comptype is not a registered component type.
 
     """
     from yggdrasil.schema import get_schema
     s = get_schema().get(comptype, None)
     if s is None:  # pragma: debug
-        raise ValueError("Unrecognized component type: %s" % comptype)
+        raise ComponentError("Unrecognized component type: %s" % comptype)
     if s.subtype_key in kwargs:
         subtype = kwargs[s.subtype_key]
     if subtype is None:
@@ -318,18 +248,13 @@ def create_component(comptype, subtype=None, **kwargs):
     return cls(**kwargs)
 
 
-def get_component_base_class(comptype, subtype=None, without_schema=False,
-                             **kwargs):
+def get_component_base_class(comptype, subtype=None, **kwargs):
     r"""Determine the base class for a component type.
 
     Args:
         comptype (str): The name of a component to test against.
         subtype (str, optional): Subtype to use to determine the component
             base class. Defaults to None.
-        without_schema (bool, optional): If True, the schema is not used to
-            import the component and subtype must be the name of a component
-            class. Defaults to False. subtype must be provided if without_schema
-            is True.
         **kwargs: Additional keyword arguments are used to determine the
             subtype if it is None.
     
@@ -337,33 +262,20 @@ def get_component_base_class(comptype, subtype=None, without_schema=False,
         ComponentBase: Component base class.
 
     """
-    if comptype in _registry_base_classes:
-        base_class_name = _registry_base_classes[comptype]
-    else:  # pragma: no cover
-        # Only called during initial import which is not covered by
-        # the test runner
-        default_class = import_component(comptype, subtype=subtype,
-                                         without_schema=without_schema,
-                                         **kwargs)
-        base_class_name = default_class._schema_base_class
-    base_class = import_component(comptype, subtype=base_class_name,
-                                  without_schema=True)
-    return base_class
-
-
-def isinstance_component(x, comptype, subtype=None, without_schema=False,
-                         **kwargs):
+    registry = get_registry(comptype=comptype)
+    base_class_name = registry['base']
+    return import_component(comptype, subtype=base_class_name, **kwargs)
+
+
+def isinstance_component(x, comptype, subtype=None, **kwargs):
     r"""Determine if an object is an instance of a component type.
 
     Args:
         x (object): Object to test.
-        comptype (str, list): The name of one or more components to test against.
+        comptype (str, list): The name of one or more components to test
+            against.
         subtype (str, optional): Subtype to use to determine the component
             base class. Defaults to None.
-        without_schema (bool, optional): If True, the schema is not used to
-            import the component and subtype must be the name of a component
-            class. Defaults to False. subtype must be provided if without_schema
-            is True.
         **kwargs: Additional keyword arguments are used to determine the
             subtype if it is None.
 
@@ -378,7 +290,6 @@ def isinstance_component(x, comptype, subtype=None, without_schema=False,
         else:
             return False
     base_class = get_component_base_class(comptype, subtype=subtype,
-                                          without_schema=without_schema,
                                           **kwargs)
     return isinstance(x, base_class)
 
@@ -474,12 +385,13 @@ def __new__(meta, name, bases, class_dict):
                 args_dict = docs2args(x.__doc__)
                 for k, v in cls._schema_properties.items():
                     if k in args_dict:
-                        v.setdefault('description', args_dict[k]['description'])
+                        v.setdefault('description',
+                                     args_dict[k]['description'])
             # Determine base class
-            global _registry_base_classes
             if cls._schema_base_class is None:
-                if cls._schema_type in _registry_base_classes:
-                    cls._schema_base_class = _registry_base_classes[cls._schema_type]
+                reg = get_registry()
+                if cls._schema_type in reg:
+                    cls._schema_base_class = reg[cls._schema_type]['base']
                 else:
                     base_comp = cls.__name__
                     for i, x in enumerate(cls.__mro__):
@@ -487,29 +399,30 @@ def __new__(meta, name, bases, class_dict):
                             break
                         base_comp = x.__name__
                     else:  # pragma: debug
-                        raise RuntimeError(("Could not determine base class for %s "
-                                            "from %s.") % (cls, bases))
+                        raise RuntimeError(
+                            f"Could not determine base class for {cls} "
+                            f"from {bases}.")
                     cls._schema_base_class = base_comp
             # Register
             global _registry
-            global _registry_defaults
-            global _registry_class2subtype
             yaml_typ = cls._schema_type
             default_subtype = cls._schema_properties.get(
                 cls._schema_subtype_key, {}).get('default',
                                                  cls._schema_subtype_default)
             if yaml_typ not in _registry:
-                _registry[yaml_typ] = OrderedDict()
-                _registry_defaults[yaml_typ] = default_subtype
-                _registry_base_classes[yaml_typ] = cls._schema_base_class
-                _registry_class2subtype[yaml_typ] = {}
+                _registry[yaml_typ] = OrderedDict([
+                    ("classes", OrderedDict()),
+                    ("module", '.'.join(cls.__module__.split('.')[:-1])),
+                    ("default", default_subtype),
+                    ("base", cls._schema_base_class),
+                    ("key", cls._schema_subtype_key),
+                    ("subtypes", {})])
             elif default_subtype is not None:
-                assert(_registry_defaults[yaml_typ] == default_subtype)
-            if cls.__name__ not in _registry[yaml_typ]:
-                _registry[yaml_typ][cls.__name__] = cls
-                _registry_class2subtype[yaml_typ][subtype] = cls.__name__
-            if not (os.environ.get('YGG_RUNNING_YGGSCHEMA', 'None').lower()
-                    in ['true', '1']):
+                assert(_registry[yaml_typ]["default"] == default_subtype)
+            if cls.__name__ not in _registry[yaml_typ]["classes"]:
+                _registry[yaml_typ]["classes"][cls.__name__] = cls
+                _registry[yaml_typ]["subtypes"][subtype] = cls.__name__
+            if not registration_in_progress():
                 cls.after_registration(cls)
                 cls.finalize_registration(cls)
         return cls
@@ -693,19 +606,19 @@ def __init__(self, skip_component_schema_normalization=None, **kwargs):
     def before_registration(cls):
         r"""Operations that should be performed to modify class attributes prior
         to registration. These actions will still be performed if the environment
-        variable YGG_RUNNING_YGGSCHEMA is set."""
+        variable YGGDRASIL_REGISTRATION_IN_PROGRESS is set."""
         pass
 
     @staticmethod
     def after_registration(cls):
         r"""Operations that should be preformed to modify class attributes after
         registration. These actions will not be performed if the environment
-        variable YGG_RUNNING_YGGSCHEMA is set."""
+        variable YGGDRASIL_REGISTRATION_IN_PROGRESS is set."""
         pass
 
     @staticmethod
     def finalize_registration(cls):
         r"""Final operations to perform after a class has been fully initialized.
         These actions will not be performed if the environment variable
-        YGG_RUNNING_YGGSCHEMA is set."""
+        YGGDRASIL_REGISTRATION_IN_PROGRESS is set."""
         pass
diff --git a/yggdrasil/constants.py b/yggdrasil/constants.py
index 1a8551cb9..e755846c5 100644
--- a/yggdrasil/constants.py
+++ b/yggdrasil/constants.py
@@ -1,4 +1,84 @@
 """Constants used by yggdrasil."""
+import numpy as np
+from collections import OrderedDict
+# No other yggdrasil modules should be import here
+# TODO: Move platform constants into this module?
+from yggdrasil import platform
+
+
+# Type related constants
+NUMPY_NUMERIC_TYPES = [
+    'int',
+    'uint',
+    'float',
+    'complex',
+]
+NUMPY_STRING_TYPES = [
+    'bytes',
+    'unicode',
+    'str',
+]
+NUMPY_TYPES = NUMPY_NUMERIC_TYPES + NUMPY_STRING_TYPES
+FLEXIBLE_TYPES = [
+    'string',
+    'bytes',
+    'unicode',
+]
+PYTHON_SCALARS = OrderedDict([
+    ('float', [float]),
+    ('int', [int]),
+    ('uint', []),
+    ('complex', [complex]),
+    ('bytes', [bytes]),
+    ('unicode', [str]),
+])
+VALID_TYPES = OrderedDict([(k, k) for k in NUMPY_NUMERIC_TYPES])
+VALID_TYPES.update([
+    ('bytes', 'bytes'),
+    ('unicode', 'str'),
+])
+NUMPY_PRECISIONS = {
+    'float': [16, 32, 64],
+    'int': [8, 16, 32, 64],
+    'uint': [8, 16, 32, 64],
+    'complex': [64, 128],
+}
+if not platform._is_win:
+    # Not available on windows
+    NUMPY_PRECISIONS['float'].append(128)
+    NUMPY_PRECISIONS['complex'].append(256)
+for T, T_NP in VALID_TYPES.items():
+    PYTHON_SCALARS[T].append(np.dtype(T_NP).type)
+    if T in NUMPY_PRECISIONS:
+        PYTHON_SCALARS[T] += [np.dtype(T_NP + str(P)).type
+                              for P in NUMPY_PRECISIONS[T]]
+PYTHON_SCALARS['int'].append(np.signedinteger)
+PYTHON_SCALARS['uint'].append(np.unsignedinteger)
+ALL_PYTHON_SCALARS = []
+for k, v in PYTHON_SCALARS.items():
+    PYTHON_SCALARS[k] = tuple(v)
+    ALL_PYTHON_SCALARS += list(v)
+ALL_PYTHON_SCALARS = tuple(set(ALL_PYTHON_SCALARS))
+ALL_PYTHON_ARRAYS = (np.ndarray,)
+
+
+# Serialization constants
+FMT_CHAR = b'%'
+YGG_MSG_HEAD = b'YGG_MSG_HEAD'
+DEFAULT_COMMENT = b'# '
+DEFAULT_DELIMITER = b'\t'
+DEFAULT_NEWLINE = b'\n'
+FMT_CHAR_STR = FMT_CHAR.decode("utf-8")
+DEFAULT_COMMENT_STR = DEFAULT_COMMENT.decode("utf-8")
+DEFAULT_DELIMITER_STR = DEFAULT_DELIMITER.decode("utf-8")
+DEFAULT_NEWLINE_STR = DEFAULT_NEWLINE.decode("utf-8")
+
+
+# Communication constants
+YGG_MSG_EOF = b'EOF!!!'
+YGG_MSG_BUF = 1024 * 2
+YGG_CLIENT_INI = b'YGG_BEGIN_CLIENT'
+YGG_CLIENT_EOF = b'YGG_END_CLIENT'
 
 
 # ======================================================
@@ -6,6 +86,140 @@
 # is generated by yggdrasil.schema.update_constants
 # ======================================================
 
+
+# Component registry
+COMPONENT_REGISTRY = {
+    'comm': {
+        'base': 'CommBase',
+        'default': 'default',
+        'key': 'commtype',
+        'module': 'yggdrasil.communication',
+        'subtypes': {
+            'buffer': 'BufferComm',
+            'default': 'DefaultComm',
+            'ipc': 'IPCComm',
+            'mpi': 'MPIComm',
+            'rest': 'RESTComm',
+            'rmq': 'RMQComm',
+            'rmq_async': 'RMQAsyncComm',
+            'value': 'ValueComm',
+            'zmq': 'ZMQComm',
+        },
+    },
+    'connection': {
+        'base': 'ConnectionDriver',
+        'default': None,
+        'key': 'connection_type',
+        'module': 'yggdrasil.drivers',
+        'subtypes': {
+            'connection': 'ConnectionDriver',
+            'file_input': 'FileInputDriver',
+            'file_output': 'FileOutputDriver',
+            'input': 'InputDriver',
+            'output': 'OutputDriver',
+            'rpc_request': 'RPCRequestDriver',
+            'rpc_response': 'RPCResponseDriver',
+        },
+    },
+    'file': {
+        'base': 'FileComm',
+        'default': 'binary',
+        'key': 'filetype',
+        'module': 'yggdrasil.communication',
+        'subtypes': {
+            'ascii': 'AsciiFileComm',
+            'binary': 'FileComm',
+            'json': 'JSONFileComm',
+            'map': 'AsciiMapComm',
+            'mat': 'MatFileComm',
+            'netcdf': 'NetCDFFileComm',
+            'obj': 'ObjFileComm',
+            'pandas': 'PandasFileComm',
+            'pickle': 'PickleFileComm',
+            'ply': 'PlyFileComm',
+            'table': 'AsciiTableComm',
+            'wofost': 'WOFOSTParamFileComm',
+            'yaml': 'YAMLFileComm',
+        },
+    },
+    'filter': {
+        'base': 'FilterBase',
+        'default': None,
+        'key': 'filtertype',
+        'module': 'yggdrasil.communication.filters',
+        'subtypes': {
+            'direct': 'DirectFilter',
+            'function': 'FunctionFilter',
+            'statement': 'StatementFilter',
+        },
+    },
+    'model': {
+        'base': 'ModelDriver',
+        'default': 'executable',
+        'key': 'language',
+        'module': 'yggdrasil.drivers',
+        'subtypes': {
+            'R': 'RModelDriver',
+            'c': 'CModelDriver',
+            'c++': 'CPPModelDriver',
+            'cmake': 'CMakeModelDriver',
+            'cpp': 'CPPModelDriver',
+            'cxx': 'CPPModelDriver',
+            'dummy': 'DummyModelDriver',
+            'executable': 'ExecutableModelDriver',
+            'fortran': 'FortranModelDriver',
+            'lpy': 'LPyModelDriver',
+            'make': 'MakeModelDriver',
+            'matlab': 'MatlabModelDriver',
+            'mpi': 'MPIPartnerModel',
+            'osr': 'OSRModelDriver',
+            'python': 'PythonModelDriver',
+            'r': 'RModelDriver',
+            'sbml': 'SBMLModelDriver',
+            'timesync': 'TimeSyncModelDriver',
+        },
+    },
+    'serializer': {
+        'base': 'SerializeBase',
+        'default': 'default',
+        'key': 'seritype',
+        'module': 'yggdrasil.serialize',
+        'subtypes': {
+            'default': 'DefaultSerialize',
+            'direct': 'DirectSerialize',
+            'functional': 'FunctionalSerialize',
+            'json': 'JSONSerialize',
+            'map': 'AsciiMapSerialize',
+            'mat': 'MatSerialize',
+            'obj': 'ObjSerialize',
+            'pandas': 'PandasSerialize',
+            'pickle': 'PickleSerialize',
+            'ply': 'PlySerialize',
+            'table': 'AsciiTableSerialize',
+            'wofost': 'WOFOSTParamSerialize',
+            'yaml': 'YAMLSerialize',
+        },
+    },
+    'transform': {
+        'base': 'TransformBase',
+        'default': None,
+        'key': 'transformtype',
+        'module': 'yggdrasil.communication.transforms',
+        'subtypes': {
+            'array': 'ArrayTransform',
+            'direct': 'DirectTransform',
+            'filter': 'FilterTransform',
+            'function': 'FunctionTransform',
+            'iterate': 'IterateTransform',
+            'map_fields': 'MapFieldsTransform',
+            'pandas': 'PandasTransform',
+            'select_fields': 'SelectFieldsTransform',
+            'statement': 'StatementTransform',
+        },
+    },
+}
+
+# Language driver constants
 LANG2EXT = {
     'R': '.R',
     'c': '.c',
@@ -45,7 +259,7 @@
     'compiled': [
         'c', 'c++', 'cpp', 'cxx', 'fortran'],
     'interpreted': [
-        'R', 'r', 'matlab', 'python'],
+        'R', 'matlab', 'python', 'r'],
     'build': [
         'cmake', 'make'],
     'dsl': [
diff --git a/yggdrasil/drivers/CModelDriver.py b/yggdrasil/drivers/CModelDriver.py
index ba50b9fb1..478036324 100755
--- a/yggdrasil/drivers/CModelDriver.py
+++ b/yggdrasil/drivers/CModelDriver.py
@@ -7,12 +7,10 @@
 import numpy as np
 import sysconfig
 from collections import OrderedDict
-from yggdrasil import platform, tools
+from yggdrasil import platform, tools, constants
 from yggdrasil.drivers.CompiledModelDriver import (
     CompiledModelDriver, CompilerBase, LinkerBase, ArchiverBase,
     get_compilation_tool)
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    _valid_types)
 from yggdrasil.languages import get_language_dir
 from yggdrasil.config import ygg_cfg
 from numpy import distutils as numpy_distutils
@@ -145,17 +143,7 @@ def get_search_path(cls, *args, **kwargs):
             list: List of paths that the tools will search.
 
         """
-        cfg = kwargs.get('cfg', ygg_cfg)
-        libtype = kwargs.get('libtype', None)
-        out = super(CompilerBase, cls).get_search_path(*args, **kwargs)
-        macos_sdkroot = cfg.get('c', 'macos_sdkroot', _osx_sysroot)
-        if platform._is_mac and (macos_sdkroot is not None):
-            base_path = os.path.join(macos_sdkroot, 'usr')
-            assert(libtype == 'include')
-            new = os.path.join(base_path, 'include')
-            if new not in out:
-                out.append(new)
-        return out
+        return super(CompilerBase, cls).get_search_path(*args, **kwargs)
 
 
 class GCCCompiler(CCompilerBase):
@@ -1052,7 +1040,7 @@ def parse_var_definition(cls, io, value, **kwargs):
                 x['datatype']['shape'] = [
                     int(float(s.strip('[]')))
                     for s in x.pop('shape').split('][')]
-                assert(x['datatype']['subtype'] in _valid_types)
+                assert(x['datatype']['subtype'] in constants.VALID_TYPES)
                 if len(x['datatype']['shape']) == 1:
                     x['datatype']['length'] = x['datatype'].pop(
                         'shape')[0]
@@ -1353,7 +1341,7 @@ def get_json_type(cls, native_type):
             if nptr > 0:
                 out['subtype'] = out['type']
                 out['type'] = '1darray'
-        if out['type'] in _valid_types:
+        if out['type'] in constants.VALID_TYPES:
             out['subtype'] = out['type']
             out['type'] = 'scalar'
         return out
diff --git a/yggdrasil/drivers/CompiledModelDriver.py b/yggdrasil/drivers/CompiledModelDriver.py
index 700b855ad..526b43160 100644
--- a/yggdrasil/drivers/CompiledModelDriver.py
+++ b/yggdrasil/drivers/CompiledModelDriver.py
@@ -902,13 +902,36 @@ def get_search_path(cls, env_only=False, libtype=None, cfg=None):
         else:
             base_paths = ['/usr', os.path.join('/usr', 'local')]
         if platform._is_mac:
-            base_paths.append('/Library/Developer/CommandLineTools/usr')
+            macos_sdkroot = cfg.get('c', 'macos_sdkroot', None)
+            base_paths += [
+                '/Library/Developer/CommandLineTools/usr',
+                # XCode >= 12
+                '/Applications/Xcode.app/Contents/Developer/'
+                'Toolchains/XcodeDefault.xctoolchain/usr']
+            if macos_sdkroot is not None:
+                base_paths.append(os.path.join(macos_sdkroot, 'usr'))
+                if 'Platforms' in macos_sdkroot:
+                    base_paths.append(
+                        os.path.join(
+                            macos_sdkroot.split('/Platforms', 1)[0],
+                            'Toolchains/XcodeDefault.xctoolchain/usr'))
         if libtype == 'include':
             suffix = 'include'
         else:
             suffix = 'lib'
         for base in base_paths:
             paths.append(os.path.join(base, suffix))
+        if platform._is_mac:
+            # Check homebrew llvm
+            # paths.append('/usr/local/Cellar/llvm/')
+            for x in glob.glob(os.path.join(
+                    macos_sdkroot.split('/Platforms', 1)[0], 'Platforms',
+                    '*', '')):
+                if ((('AppleTV' not in x) and ('iPhoneOS' not in x)
+                     and ('WatchOS' not in x))):
+                    paths.append(x)
+            paths += [
+                "/usr/local/Cellar/llvm/"]
         out = []
         for x in paths:
             if x and (x not in out) and os.path.isdir(x):
@@ -2708,19 +2731,19 @@ def get_dependency_library(cls, dep, default=None, libtype=None,
                 if os.path.isfile(libinfo[libtype]):
                     out = libinfo[libtype]
                 else:  # pragma: no cover
-                    out = cls.cfg.get(dep_lang, '%s_%s' % (dep, libtype), None)
-            elif cls.cfg.has_option(dep_lang, '%s_%s' % (dep, libtype)):
-                out = cls.cfg.get(dep_lang, '%s_%s' % (dep, libtype))
+                    out = cls.cfg.get(dep_lang, f'{dep}_{libtype}', None)
+            elif cls.cfg.has_option(dep_lang, f'{dep}_{libtype}'):
+                out = cls.cfg.get(dep_lang, f'{dep}_{libtype}')
             else:
                 libtype_found = []
                 for k in libtype_list:
-                    if cls.cfg.has_option(dep_lang, '%s_%s' % (dep, k)):
+                    if cls.cfg.has_option(dep_lang, f'{dep}_{k}'):
                         libtype_found.append(k)
                 if len(libtype_found) > 0:
-                    raise ValueError(("A '%s' library could not be located for "
-                                      "dependency '%s', but one or more "
-                                      "libraries of types %s were found.")
-                                     % (libtype, dep, libtype_found))
+                    raise ValueError(f"A '{libtype}' library could not be "
+                                     f"located for dependency '{dep}', but "
+                                     f"one or more libraries of types "
+                                     f"{libtype_found} were found.")
             # TODO: CLEANUP
             if platform._is_win and out and out.endswith('.lib'):  # pragma: windows
                 if tool is None:
@@ -3328,7 +3351,7 @@ def configure(cls, cfg, **kwargs):
             cfg.add_section(cls.language)
         for k, v in kwargs.items():
             if k not in ['compiler', 'linker', 'archiver']:  # pragma: debug
-                raise ValueError("Unexpected configuration option: '%s'" % k)
+                raise ValueError(f"Unexpected configuration option: '{k}'")
             vtool = None
             try:
                 vtool = get_compilation_tool(k, v)
@@ -3339,10 +3362,10 @@ def configure(cls, cfg, **kwargs):
                         vtool = vreg
                         break
             if not vtool:  # pragma: debug
-                raise ValueError("Could not locate a %s tool '%s'." % (k, v))
+                raise ValueError(f"Could not locate a {k} tool '{v}'.")
             cfg.set(cls.language, k, vtool.toolname)
             if os.path.isfile(v):
-                cfg.set(cls.language, '%s_executable' % vtool.toolname, v)
+                cfg.set(cls.language, f'{vtool.toolname}_executable', v)
         # Call __func__ to avoid direct invoking of class which dosn't exist
         # in after_registration where this is called
         return ModelDriver.configure.__func__(cls, cfg)
@@ -3429,16 +3452,16 @@ def configure_library(cls, cfg, k):
         for t in v.keys():
             fname = v[t]
             assert(isinstance(fname, str))
-            opt = '%s_%s' % (k, t)
+            opt = f'{k}_{t}'
             if t in ['libtype', 'language']:
                 continue
             elif t in ['include']:
-                desc_end = '%s headers' % k
+                desc_end = f'{k} headers'
             elif t in ['static', 'shared']:
-                desc_end = '%s %s library' % (k, t)
+                desc_end = f'{k} {t} library'
             else:  # pragma: completion
-                desc_end = '%s %s' % (k, t)
-            desc = 'The full path to the directory containing %s.' % desc_end
+                desc_end = f'{k} {t}'
+            desc = f'The full path to the directory containing {desc_end}.'
             if cfg.has_option(k_lang, opt):
                 continue
             if os.path.isabs(fname):
diff --git a/yggdrasil/drivers/FortranModelDriver.py b/yggdrasil/drivers/FortranModelDriver.py
index 9400ff93d..286122a2b 100644
--- a/yggdrasil/drivers/FortranModelDriver.py
+++ b/yggdrasil/drivers/FortranModelDriver.py
@@ -3,14 +3,12 @@
 import copy
 import logging
 from collections import OrderedDict
-from yggdrasil import platform, tools
+from yggdrasil import platform, tools, constants
 from yggdrasil.languages import get_language_dir
 from yggdrasil.drivers import CModelDriver
 from yggdrasil.drivers.CompiledModelDriver import (
     CompilerBase, CompiledModelDriver, get_compilation_tool,
     get_compilation_tool_registry)
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    _valid_types)
 
 
 logger = logging.getLogger(__name__)
@@ -643,7 +641,7 @@ def get_json_type(cls, native_type):
                 out['type'] = 'ndarray'
                 if shape[0] not in '*:':
                     out['shape'] = [int(i) for i in shape]
-        if out['type'] in _valid_types:
+        if out['type'] in constants.VALID_TYPES:
             out['subtype'] = out['type']
             out['type'] = 'scalar'
         return out
@@ -1131,7 +1129,7 @@ def write_type_def(cls, name, datatype, **kwargs):
         if datatype.get('subtype', datatype['type']) in ['bytes', 'unicode']:
             if 'precision' not in datatype:
                 datatype = dict(datatype, precision=0)
-        elif datatype.get('subtype', datatype['type']) in _valid_types:
+        elif datatype.get('subtype', datatype['type']) in constants.VALID_TYPES:
             datatype.setdefault('precision', 32)
         out = super(FortranModelDriver, cls).write_type_def(
             name, datatype, **kwargs)
diff --git a/yggdrasil/drivers/ModelDriver.py b/yggdrasil/drivers/ModelDriver.py
index 32b53607b..13c1cfc00 100755
--- a/yggdrasil/drivers/ModelDriver.py
+++ b/yggdrasil/drivers/ModelDriver.py
@@ -11,12 +11,10 @@
 import asyncio
 from collections import OrderedDict
 from pprint import pformat
-from yggdrasil import platform, tools, languages, multitasking
-from yggdrasil.components import import_component, import_all_components
+from yggdrasil import platform, tools, languages, multitasking, constants
+from yggdrasil.components import import_component
 from yggdrasil.drivers.Driver import Driver
 from yggdrasil.metaschema.datatypes import is_default_typedef
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    _valid_types)
 from queue import Empty
 logger = logging.getLogger(__name__)
 
@@ -45,7 +43,7 @@ def remove_product(product, check_for_source=False, **kwargs):
         RuntimeError: If the product cannot be removed.
 
     """
-    import_all_components('model')
+    tools.import_all_modules('yggdrasil.drivers')
     source_keys = list(_map_language_ext.keys())
     if '.exe' in source_keys:  # pragma: windows
         source_keys.remove('.exe')
@@ -173,12 +171,28 @@ class ModelDriver(Driver):
             the model source code. If provided, relative paths in the model
             YAML definition will be considered relative to the repository root
             directory.
+        repository_commit (str, optional): Commit that should be checked out
+            in the model repository specified by repository_url. If not
+            provided, the most recent commit on the default branch will be used.
         description (str, optional): Description of the model. This parameter
             is only used in the model repository or when providing the model
             as a service.
         contact_email (str, optional): Email address that should be used to
             contact the maintainer of the model. This parameter is only used
             in the model repository.
+        validation_command (str, optional): Path to a validation command that
+            can be used to verify that the model ran as expected. A non-zero
+            return code is taken to indicate failure.
+        dependencies (list, optional): A list of packages required by the
+            model that are written in the same language as the model. If the
+            package requires dependencies outside the language of the model.
+            use the additional_dependencies parameter to provide them. If you
+            need a version of the package from a specific package manager,
+            a mapping with 'package' and 'package_manager' fields can be
+            provided instead of just the name of the package.
+        additional_dependencies (dict, optional): A mapping between languages
+            and lists of packages in those languages that are required by the
+            model.
         **kwargs: Additional keyword arguments are passed to parent class.
 
     Class Attributes:
@@ -287,12 +301,27 @@ class ModelDriver(Driver):
             source code. If provided, relative paths in the model YAML
             definition will be considered relative to the repository root
             directory.
+        repository_commit (str): Commit that should be checked out in the
+            model repository specified by repository_url.
         description (str): Description of the model. This parameter is only
             used in the model repository or when providing the model as a
             service.
         contact_email (str): Email address that should be used to contact the
             maintainer of the model. This parameter is only used in the model
             repository.
+        validation_command (str): Path to a validation command that can be
+            used to verify that the model ran as expected. A non-zero return
+            code is taken to indicate failure.
+        dependencies (list): A list of packages required by the model that are
+            written in the same language as the model. If the package requires
+            dependencies outside the language of the model, use the
+            additional_dependencies parameter to provide them. If you need a
+            version of the package from a specific package manager, a mapping
+            
+            with 'package' and 'package_manager' fields can be provided
+            instead of just the name of the package.
+        additional_dependencies (dict): A mapping between languages and lists
+            of packages in those languages that are required by the model.
 
     Raises:
         RuntimeError: If both with_strace and with_valgrind are True.
@@ -311,7 +340,7 @@ class ModelDriver(Driver):
                          'languages can be found :ref:`here <'
                          'schema_table_model_subtype_rst>`.')},
         'args': {'type': 'array',
-                 'items': {'type': 'string'}},
+                 'items': {'type': 'string', 'minLength': 1}},
         'inputs': {'type': 'array', 'default': [],
                    'items': {'$ref': '#/definitions/comm'},
                    'description': (
@@ -393,8 +422,34 @@ class ModelDriver(Driver):
         'allow_threading': {'type': 'boolean'},
         'copies': {'type': 'integer', 'default': 1, 'minimum': 1},
         'repository_url': {'type': 'string'},
+        'repository_commit': {'type': 'string'},
         'description': {'type': 'string'},
-        'contact_email': {'type': 'string'}}
+        'contact_email': {'type': 'string'},
+        'validation_command': {'type': 'string'},
+        'dependencies': {
+            'type': 'array',
+            'items': {'oneOf': [
+                {'type': 'string'},
+                {'type': 'object',
+                 'required': ['package'],
+                 'properties': {
+                     'package': {'type': 'string'},
+                     'package_manager': {'type': 'string'},
+                     'arguments': {'type': 'string'}},
+                 'additionalProperties': False}]}},
+        'additional_dependencies': {
+            'type': 'object',
+            'additionalProperties': {
+                'type': 'array',
+                'items': {'oneOf': [
+                    {'type': 'string'},
+                    {'type': 'object',
+                     'required': ['package'],
+                     'properties': {
+                         'package': {'type': 'string'},
+                         'package_manager': {'type': 'string'},
+                         'arguments': {'type': 'string'}},
+                     'additionalProperties': False}]}}}}
     _schema_excluded_from_class = ['name', 'language', 'args', 'working_dir']
     _schema_excluded_from_class_validation = ['inputs', 'outputs']
     
@@ -519,6 +574,13 @@ def __init__(self, name, args, model_index=0, copy_index=-1, clients=[],
         if self.function:
             self.wrapper_products.append(args[0])
         self.wrapper_products += self.write_wrappers()
+        # Install dependencies
+        if self.dependencies:
+            self.install_model_dependencies(self.dependencies)
+        if self.additional_dependencies:
+            for language, v in self.additional_dependencies.items():
+                drv = import_component('model', language)
+                drv.install_model_dependencies(v)
 
     @staticmethod
     def before_registration(cls):
@@ -770,6 +832,109 @@ def write_wrappers(self, **kwargs):
 
         """
         return []
+
+    @classmethod
+    def install_model_dependencies(cls, dependencies, always_yes=False):
+        r"""Install any dependencies required by the model.
+
+        Args:
+            dependencies (list): Dependencies that should be installed.
+            always_yes (bool, optional): If True, the package manager will
+                not ask users for input during installation. Defaults to
+                False.
+
+        """
+        packages = {}
+        for x in dependencies:
+            if isinstance(x, str):
+                x = {'package': x}
+            if x.get('arguments', None):
+                cls.install_dependency(always_yes=always_yes, **x)
+            else:
+                packages.setdefault(x.get('package_manager', None), [])
+                packages[x.get('package_manager', None)].append(
+                    x['package'])
+        for k, v in packages.items():
+            cls.install_dependency(v, package_manager=k,
+                                   always_yes=always_yes)
+
+    @classmethod
+    def install_dependency(cls, package=None, package_manager=None,
+                           arguments=None, command=None, always_yes=False):
+        r"""Install a dependency.
+
+        Args:
+            package (str): Name of the package that should be installed. If
+                the package manager supports it, this can include version
+                requirements.
+            package_manager (str, optional): Package manager that should be
+                used to install the package.
+            arguments (str, optional): Additional arguments that should be
+                passed to the package manager.
+            command (list, optional): Command that should be used to
+                install the package.
+            always_yes (bool, optional): If True, the package manager will
+                not ask users for input during installation. Defaults to
+                False.
+
+        """
+        assert(package)
+        if isinstance(package, str):
+            package = package.split()
+        if package_manager is None:
+            if tools.get_conda_prefix():
+                package_manager = 'conda'
+            elif platform._is_mac:
+                package_manager = 'brew'
+            elif platform._is_linux:
+                package_manager = 'apt'
+            elif platform._is_win:
+                package_manager = 'choco'
+        yes_cmd = []
+        cmd_kwargs = {}
+        if command:
+            cmd = copy.copy(command)
+        elif package_manager == 'conda':
+            cmd = ['conda', 'install'] + package
+            if platform._is_win:  # pragma: windows
+                # Conda commands must be run on the shell on windows as it
+                # is implemented as a batch script
+                cmd.insert(0, 'call')
+                cmd_kwargs['shell'] = True
+            yes_cmd = ['-y']
+        elif package_manager == 'brew':
+            cmd = ['brew', 'install'] + package
+        elif package_manager == 'apt':
+            cmd = ['apt-get', 'install'] + package
+            if bool(os.environ.get('GITHUB_ACTIONS', False)):
+                # Only enable sudo for testing, otherwise allow the user to
+                # decide if they want to run yggdrasil with sudo, or just
+                # install the dependencies themselves
+                cmd.insert(0, 'sudo')
+            yes_cmd = ['-y']
+        elif package_manager == 'choco':
+            cmd = ['choco', 'install'] + package
+        elif package_manager == 'vcpkg':
+            cmd = ['vcpkg.exe', 'install', '--triplet', 'x64-windows']
+            cmd += package
+        else:
+            package_managers = {'pip': 'python',
+                                'cran': 'r'}
+            if package_manager in package_managers:
+                drv = import_component(
+                    'model', package_managers[package_manager])
+                return drv.install_dependency(
+                    package=package, package_manager=package_manager,
+                    arguments=arguments, always_yes=always_yes)
+            raise NotImplementedError(f"Unsupported package manager: "
+                                      f"{package_manager}")
+        if arguments:
+            cmd += arguments.split()
+        if always_yes:
+            cmd += yes_cmd
+        if cmd_kwargs.get('shell', False):
+            cmd = ' '.join(cmd)
+        subprocess.check_call(cmd, **cmd_kwargs)
         
     def model_command(self):
         r"""Return the command that should be used to run the model.
@@ -877,6 +1042,13 @@ def run_executable(cls, args, return_process=False, debug_flags=None,
         except (subprocess.CalledProcessError, OSError) as e:  # pragma: debug
             raise RuntimeError("Could not call command '%s': %s"
                                % (' '.join(cmd), e))
+
+    def run_validation(self):
+        r"""Run the validation script for the model."""
+        if not self.validation_command:
+            return
+        subprocess.check_call(self.validation_command.split(),
+                              cwd=self.working_dir)
         
     def run_model(self, return_process=True, **kwargs):
         r"""Run the model. Unless overridden, the model will be run using
@@ -2638,11 +2810,11 @@ def write_type_decl(cls, name, datatype, name_base=None,
                          'precision': 64, 'length': len(datatype['shape'])}},
                     definitions=definitions,
                     requires_freeing=requires_freeing)
-        elif datatype['type'] in ['ply', 'obj', '1darray',
-                                  'scalar', 'boolean', 'null',
-                                  'number', 'integer', 'string',
-                                  'class', 'function', 'instance',
-                                  'schema', 'any'] + list(_valid_types.keys()):
+        elif datatype['type'] in (['ply', 'obj', '1darray', 'scalar',
+                                   'boolean', 'null', 'number', 'integer',
+                                   'string', 'class', 'function', 'instance',
+                                   'schema', 'any']
+                                  + list(constants.VALID_TYPES.keys())):
             pass
         else:  # pragma: debug
             raise ValueError(("Cannot create %s version of type "
@@ -2764,7 +2936,7 @@ def write_type_def(cls, name, datatype, name_base=None,
                 keys['ndim'] = 0
                 keys['shape'] = cls.function_param['null']
             keys['units'] = datatype.get('units', '')
-        elif (typename == 'scalar') or (typename in _valid_types):
+        elif (typename == 'scalar') or (typename in constants.VALID_TYPES):
             keys['subtype'] = datatype.get('subtype', datatype['type'])
             keys['units'] = datatype.get('units', '')
             if keys['subtype'] in ['bytes', 'string', 'unicode']:
diff --git a/yggdrasil/drivers/PythonModelDriver.py b/yggdrasil/drivers/PythonModelDriver.py
index 608b1f77b..12aa83460 100755
--- a/yggdrasil/drivers/PythonModelDriver.py
+++ b/yggdrasil/drivers/PythonModelDriver.py
@@ -225,3 +225,34 @@ def write_finalize_oiter(cls, var):
                  'units.get_units({name}[0]))').format(
                      name=var['name'])]
         return out
+
+    @classmethod
+    def install_dependency(cls, package=None, package_manager=None, **kwargs):
+        r"""Install a dependency.
+
+        Args:
+            package (str): Name of the package that should be installed. If
+                the package manager supports it, this can include version
+                requirements.
+            package_manager (str, optional): Package manager that should be
+                used to install the package.
+            **kwargs: Additional keyword arguments are passed to the parent
+                class.
+
+        """
+        if package_manager in [None, 'pip']:
+            if isinstance(package, str):
+                package = package.split()
+            kwargs.setdefault(
+                'command',
+                [cls.get_interpreter(), '-m', 'pip', 'install'] + package)
+        return super(PythonModelDriver, cls).install_dependency(
+            package, package_manager=package_manager, **kwargs)
+        
+    def run_validation(self):
+        r"""Run the validation script for the model."""
+        if ((self.validation_command
+             and (self.validation_command.split()[0].endswith('.py')))):
+            self.validation_command = (
+                f"{self.get_interpreter()} {self.validation_command}")
+        return super(PythonModelDriver, self).run_validation()
diff --git a/yggdrasil/drivers/RModelDriver.py b/yggdrasil/drivers/RModelDriver.py
index d873fd679..54e8850ac 100644
--- a/yggdrasil/drivers/RModelDriver.py
+++ b/yggdrasil/drivers/RModelDriver.py
@@ -478,3 +478,31 @@ def write_finalize_oiter(cls, var):
             '}',
         ]
         return out
+
+    @classmethod
+    def install_dependency(cls, package, package_manager=None, **kwargs):
+        r"""Install a dependency.
+
+        Args:
+            package (str): Name of the package that should be installed. If
+                the package manager supports it, this can include version
+                requirements.
+            package_manager (str, optional): Package manager that should be
+                used to install the package.
+            **kwargs: Additional keyword arguments are passed to the parent
+                class.
+
+        """
+        if package_manager in [None, 'cran', 'CRAN']:
+            if isinstance(package, list):
+                package = (
+                    'c(' + ', '.join([f"\"{x}\"" for x in package]) + ')')
+            else:
+                package = f'\"{package}\"'
+            kwargs.setdefault(
+                'command',
+                [cls.get_interpreter(), '-e',
+                 (f'\'install.packages({package}, dep=TRUE, '
+                  f'repos=\"http://cloud.r-project.org\")\'')])
+        return super(RModelDriver, cls).install_dependency(
+            package, package_manager=package_manager, **kwargs)
diff --git a/yggdrasil/drivers/RPCRequestDriver.py b/yggdrasil/drivers/RPCRequestDriver.py
index 54bef20f7..feb6b68ac 100644
--- a/yggdrasil/drivers/RPCRequestDriver.py
+++ b/yggdrasil/drivers/RPCRequestDriver.py
@@ -1,3 +1,4 @@
+from yggdrasil import constants
 from yggdrasil.drivers.ConnectionDriver import ConnectionDriver, run_remotely
 from yggdrasil.drivers.RPCResponseDriver import RPCResponseDriver
 from yggdrasil.communication import CommBase
@@ -109,7 +110,7 @@ def remove_model(self, direction, name):
             clients = self.clients
             if (direction == "input") and (name in clients) and (len(clients) > 1):
                 super(RPCRequestDriver, self).send_message(
-                    CommBase.CommMessage(args=CommBase.YGG_CLIENT_EOF,
+                    CommBase.CommMessage(args=constants.YGG_CLIENT_EOF,
                                          flag=CommBase.FLAG_SUCCESS),
                     header_kwargs={'raw': True, 'model': name},
                     skip_processing=True)
@@ -172,7 +173,7 @@ def send_message(self, msg, **kwargs):
         if msg.flag != CommBase.FLAG_EOF:
             # Remove client that signed off
             if ((msg.header.get('raw', False)
-                 and (msg.args == CommBase.YGG_CLIENT_EOF))):  # pragma: intermittent
+                 and (msg.args == constants.YGG_CLIENT_EOF))):  # pragma: intermittent
                 self.remove_model('input', msg.header['model'])
                 return True
             with self.lock:
diff --git a/yggdrasil/drivers/SBMLModelDriver.py b/yggdrasil/drivers/SBMLModelDriver.py
index be49f9c51..8c49e3e62 100644
--- a/yggdrasil/drivers/SBMLModelDriver.py
+++ b/yggdrasil/drivers/SBMLModelDriver.py
@@ -107,7 +107,7 @@ def model_wrapper(cls, model_file, start_time, steps,
             integrator=integrator, integrator_settings=integrator_settings,
         )
         if not selections:
-            selections = list(model.keys())
+            selections = [k for k in model.keys() if not k.startswith('init(')]
         if 'time' not in selections:
             selections = ['time'] + selections
         for k, v in output_map.items():
@@ -194,4 +194,8 @@ def call_model(cls, model, curr_time, end_time, steps,
                              steps=int(steps))
         # Unsupported?
         # variableStep=variable_step)
+        # try:
+        #     out = {k: out[k] for k in out.colnames}
+        # except IndexError:
+        #     out = {k: out[:, i] for i, k in enumerate(out.colnames)}
         return end_time, out
diff --git a/yggdrasil/examples/sbml1/Output/expected.txt b/yggdrasil/examples/sbml1/Output/expected.txt
index 66546638f..42c0e0122 100644
--- a/yggdrasil/examples/sbml1/Output/expected.txt
+++ b/yggdrasil/examples/sbml1/Output/expected.txt
@@ -1,6 +1,6 @@
-# time	S1	S2	S3	S4	X0	X1	[S1]	[S2]	[S3]	[S4]	[X0]	[X1]	compartment	J0_VM1	J0_Keq1	J0_h	J4_V4	J4_KS4	J0	J1	J2	J3	J4	init([S1])	init([S2])	init([S3])	init([S4])	init(S1)	init(S2)	init(S3)	init(S4)	S1'	S2'	S3'	S4'	eigen(S1)	eigenReal(S1)	eigenImag(S1)	eigen(S2)	eigenReal(S2)	eigenImag(S2)	eigen(S3)	eigenReal(S3)	eigenImag(S3)	eigen(S4)	eigenReal(S4)	eigenImag(S4)
-# %g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g
-0	0	0	0	0	10	0	0	0	0	0	10	0	1	10	10	10	2.5	0.5	9.09091	0	0	0	0	0	0	0	0	0	0	0	0	9.09091	0	0	0	0	-18.6453	0	0	-13.0021	0	0	-6.96727	0	0	-3.29967	0
-5	1.2678	0.730189	0.707615	1.1401	10	0	1.2678	0.730189	0.707615	1.1401	10	0	1	10	10	10	2.5	0.5	6.17918	3.74171	2.41474	1.68414	1.73785	0	0	0	0	0	0	0	0	2.43747	1.32697	0.730594	-0.0537064	0	0.0124582	2.20188	0	0.0124582	-2.20188	0	-7.03899	1.64292	0	-7.03899	-1.64292
-5	5	5	0.707615	1.1401	10	0	5	5	0.707615	1.1401	10	0	1	10	10	10	2.5	0.5	4.81979	3.63636	7.24323	1.68414	1.73785	0	0	0	0	0	0	0	0	1.18343	-3.60686	5.55908	-0.0537064	0	-5.39048	0	0	-2.1071	0	0	-0.178106	0.740418	0	-0.178106	-0.740418
-10	0.0498036	0.15262	0.582381	1.81007	10	0	0.0498036	0.15262	0.582381	1.81007	10	0	1	10	10	10	2.5	0.5	0.257216	0.160339	0.208324	0.64958	1.95889	0	0	0	0	0	0	0	0	0.0968772	-0.0479853	-0.441256	-1.30931	0	-11.8326	0	0	-0.906626	1.28292	0	-0.906626	-1.28292	0	-7.02511	0
+# time	S1	S2	S3	S4	X0	X1	[S1]	[S2]	[S3]	[S4]	[X0]	[X1]	compartment	J0_VM1	J0_Keq1	J0_h	J4_V4	J4_KS4	J0	J1	J2	J3	J4	S1'	S2'	S3'	S4'	eigen(S1)	eigenReal(S1)	eigenImag(S1)	eigen(S2)	eigenReal(S2)	eigenImag(S2)	eigen(S3)	eigenReal(S3)	eigenImag(S3)	eigen(S4)	eigenReal(S4)	eigenImag(S4)
+# %g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g
+0	0	0	0	0	10	0	0	0	0	0	10	0	1	10	10	10	2.5	0.5	9.09091	0	0	0	0	9.09091	0	0	0	0	-18.6453	0	0	-13.0021	0	0	-6.96727	0	0	-3.29967	0
+5	1.2678	0.730189	0.707615	1.1401	10	0	1.2678	0.730189	0.707615	1.1401	10	0	1	10	10	10	2.5	0.5	6.17918	3.74171	2.41474	1.68414	1.73785	2.43747	1.32697	0.730594	-0.0537064	0	0.0124582	2.20188	0	0.0124582	-2.20188	0	-7.03899	1.64292	0	-7.03899	-1.64292
+5	5	5	0.707615	1.1401	10	0	5	5	0.707615	1.1401	10	0	1	10	10	10	2.5	0.5	4.81979	3.63636	7.24323	1.68414	1.73785	1.18343	-3.60686	5.55908	-0.0537064	0	-5.39048	0	0	-2.1071	0	0	-0.178106	0.740418	0	-0.178106	-0.740418
+10	0.0498036	0.15262	0.582381	1.81007	10	0	0.0498036	0.15262	0.582381	1.81007	10	0	1	10	10	10	2.5	0.5	0.257216	0.160339	0.208324	0.64958	1.95889	0.0968772	-0.0479853	-0.441256	-1.30931	0	-11.8326	0	0	-0.906626	1.28292	0	-0.906626	-1.28292	0	-7.02511	0
diff --git a/yggdrasil/examples/sbml2/Output/expected0.txt b/yggdrasil/examples/sbml2/Output/expected0.txt
index 82f015283..344217fb3 100644
--- a/yggdrasil/examples/sbml2/Output/expected0.txt
+++ b/yggdrasil/examples/sbml2/Output/expected0.txt
@@ -1,7 +1,7 @@
-# time	S1	S2	S3	S4	X0	X1	[S1]	[S2]	[S3]	[S4]	[X0]	[X1]	compartment	J0_VM1	J0_Keq1	J0_h	J4_V4	J4_KS4	J0	J1	J2	J3	J4	init([S1])	init([S2])	init([S3])	init([S4])	init(S1)	init(S2)	init(S3)	init(S4)	S1'	S2'	S3'	S4'	eigen(S1)	eigenReal(S1)	eigenImag(S1)	eigen(S2)	eigenReal(S2)	eigenImag(S2)	eigen(S3)	eigenReal(S3)	eigenImag(S3)	eigen(S4)	eigenReal(S4)	eigenImag(S4)
-# %g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g
-1	3.15087	1.54712	1.0105	0.948797	10	0	3.15087	1.54712	1.0105	0.948797	10	0	1	10	10	10	2.5	0.5	6.56958	4.98675	3.78067	2.77343	1.63722	0	0	0	0	0	0	0	0	1.58283	1.20608	1.00725	1.13621	0	-0.545008	0.708568	0	-0.545008	-0.708568	0	-3.61427	0	0	-5.93877	0
-2	1.4231	1.87524	1.79238	2.00936	10	0	1.4231	1.87524	1.79238	2.00936	10	0	1	10	10	10	2.5	0.5	0.0908234	2.43828	3.24954	2.89584	2.00187	0	0	0	0	0	0	0	0	-2.34746	-0.811256	0.353695	0.893978	0	-4.32885	0	0	-2.88955	0	0	-0.42144	0.12732	0	-0.42144	-0.12732
-3	0.23736	0.765993	1.559	2.50695	10	0	0.23736	0.765993	1.559	2.50695	10	0	1	10	10	10	2.5	0.5	0.010163	0.420104	1.36599	2.08769	2.0843	0	0	0	0	0	0	0	0	-0.409941	-0.945889	-0.721698	0.00339591	0	-7.00809	0	0	-3.63495	0	0	-1.36335	0	0	-0.0996989	0
-4	0.0626202	0.227289	0.777257	1.97528	10	0	0.0626202	0.227289	0.777257	1.97528	10	0	1	10	10	10	2.5	0.5	0.109183	0.133051	0.358375	1.01851	1.99501	0	0	0	0	0	0	0	0	-0.0238673	-0.225324	-0.660136	-0.976496	0	-10.9185	0	0	-5.78325	0	0	-0.995338	0.228864	0	-0.995338	-0.228864
-5	1.26783	0.730205	0.707624	1.1401	10	0	1.26783	0.730205	0.707624	1.1401	10	0	1	10	10	10	2.5	0.5	6.17918	3.74175	2.41477	1.68417	1.73785	0	0	0	0	0	0	0	0	2.43743	1.32697	0.730603	-0.0536798	0	0.0124502	2.20185	0	0.0124502	-2.20185	0	-7.03892	1.64289	0	-7.03892	-1.64289
+# time	S1	S2	S3	S4	X0	X1	[S1]	[S2]	[S3]	[S4]	[X0]	[X1]	compartment	J0_VM1	J0_Keq1	J0_h	J4_V4	J4_KS4	J0	J1	J2	J3	J4	S1'	S2'	S3'	S4'	eigen(S1)	eigenReal(S1)	eigenImag(S1)	eigen(S2)	eigenReal(S2)	eigenImag(S2)	eigen(S3)	eigenReal(S3)	eigenImag(S3)	eigen(S4)	eigenReal(S4)	eigenImag(S4)
+# %g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g
+1	3.15087	1.54712	1.0105	0.948797	10	0	3.15087	1.54712	1.0105	0.948797	10	0	1	10	10	10	2.5	0.5	6.56958	4.98675	3.78067	2.77343	1.63722	1.58283	1.20608	1.00725	1.13621	0	-0.545008	0.708568	0	-0.545008	-0.708568	0	-3.61427	0	0	-5.93877	0
+2	1.4231	1.87524	1.79238	2.00936	10	0	1.4231	1.87524	1.79238	2.00936	10	0	1	10	10	10	2.5	0.5	0.0908234	2.43828	3.24954	2.89584	2.00187	-2.34746	-0.811256	0.353695	0.893978	0	-4.32885	0	0	-2.88955	0	0	-0.42144	0.12732	0	-0.42144	-0.12732
+3	0.23736	0.765993	1.559	2.50695	10	0	0.23736	0.765993	1.559	2.50695	10	0	1	10	10	10	2.5	0.5	0.010163	0.420104	1.36599	2.08769	2.0843	-0.409941	-0.945889	-0.721698	0.00339591	0	-7.00809	0	0	-3.63495	0	0	-1.36335	0	0	-0.0996989	0
+4	0.0626202	0.227289	0.777257	1.97528	10	0	0.0626202	0.227289	0.777257	1.97528	10	0	1	10	10	10	2.5	0.5	0.109183	0.133051	0.358375	1.01851	1.99501	-0.0238673	-0.225324	-0.660136	-0.976496	0	-10.9185	0	0	-5.78325	0	0	-0.995338	0.228864	0	-0.995338	-0.228864
+5	1.26783	0.730205	0.707624	1.1401	10	0	1.26783	0.730205	0.707624	1.1401	10	0	1	10	10	10	2.5	0.5	6.17918	3.74175	2.41477	1.68417	1.73785	2.43743	1.32697	0.730603	-0.0536798	0	0.0124502	2.20185	0	0.0124502	-2.20185	0	-7.03892	1.64289	0	-7.03892	-1.64289
diff --git a/yggdrasil/examples/sbml2/Output/expected1.txt b/yggdrasil/examples/sbml2/Output/expected1.txt
index 52ca61887..7a0f37d5a 100644
--- a/yggdrasil/examples/sbml2/Output/expected1.txt
+++ b/yggdrasil/examples/sbml2/Output/expected1.txt
@@ -1,7 +1,7 @@
-# time	S1	S2	S3	S4	X0	X1	[S1]	[S2]	[S3]	[S4]	[X0]	[X1]	compartment	J0_VM1	J0_Keq1	J0_h	J4_V4	J4_KS4	J0	J1	J2	J3	J4	init([S1])	init([S2])	init([S3])	init([S4])	init(S1)	init(S2)	init(S3)	init(S4)	S1'	S2'	S3'	S4'	eigen(S1)	eigenReal(S1)	eigenImag(S1)	eigen(S2)	eigenReal(S2)	eigenImag(S2)	eigen(S3)	eigenReal(S3)	eigenImag(S3)	eigen(S4)	eigenReal(S4)	eigenImag(S4)
-# %g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g
-2	1.4231	1.87524	1.79238	2.00936	10	0	1.4231	1.87524	1.79238	2.00936	10	0	1	10	10	10	2.5	0.5	0.0908234	2.43828	3.24954	2.89584	2.00187	0	0	0	0	0	0	0	0	-2.34746	-0.811256	0.353695	0.893978	0	-4.32885	0	0	-2.88955	0	0	-0.42144	0.12732	0	-0.42144	-0.12732
-4	0.0626202	0.227289	0.777257	1.97528	10	0	0.0626202	0.227289	0.777257	1.97528	10	0	1	10	10	10	2.5	0.5	0.109183	0.133051	0.358375	1.01851	1.99501	0	0	0	0	0	0	0	0	-0.0238673	-0.225324	-0.660136	-0.976496	0	-10.9185	0	0	-5.78325	0	0	-0.995338	0.228864	0	-0.995338	-0.228864
-6	1.10813	1.34748	1.36601	1.65329	10	0	1.10813	1.34748	1.36601	1.65329	10	0	1	10	10	10	2.5	0.5	0.600504	2.42687	2.89291	2.57596	1.91949	0	0	0	0	0	0	0	0	-1.82637	-0.466031	0.316941	0.656473	0	-0.245455	1.08378	0	-0.245455	-1.08378	0	-4.73903	0.621603	0	-4.73903	-0.621603
-8	0.441469	0.3727	0.603703	1.31728	10	0	0.441469	0.3727	0.603703	1.31728	10	0	1	10	10	10	2.5	0.5	3.66378	2.02257	1.27484	1.16483	1.81216	0	0	0	0	0	0	0	0	1.64121	0.747733	0.110007	-0.647327	0	0.412044	3.25937	0	0.412044	-3.25937	0	-9.08432	2.69667	0	-9.08432	-2.69667
-10	0.269431	0.678094	1.19936	1.86825	10	0	0.269431	0.678094	1.19936	1.86825	10	0	1	10	10	10	2.5	0.5	0.18842	0.687087	1.52296	2.02995	1.97218	0	0	0	0	0	0	0	0	-0.498667	-0.83587	-0.506996	0.0577685	0	-0.652073	0.714291	0	-0.652073	-0.714291	0	-7.36911	0	0	-4.83401	0
+# time	S1	S2	S3	S4	X0	X1	[S1]	[S2]	[S3]	[S4]	[X0]	[X1]	compartment	J0_VM1	J0_Keq1	J0_h	J4_V4	J4_KS4	J0	J1	J2	J3	J4	S1'	S2'	S3'	S4'	eigen(S1)	eigenReal(S1)	eigenImag(S1)	eigen(S2)	eigenReal(S2)	eigenImag(S2)	eigen(S3)	eigenReal(S3)	eigenImag(S3)	eigen(S4)	eigenReal(S4)	eigenImag(S4)
+# %g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g	%g
+2	1.4231	1.87524	1.79238	2.00936	10	0	1.4231	1.87524	1.79238	2.00936	10	0	1	10	10	10	2.5	0.5	0.0908234	2.43828	3.24954	2.89584	2.00187	-2.34746	-0.811256	0.353695	0.893978	0	-4.32885	0	0	-2.88955	0	0	-0.42144	0.12732	0	-0.42144	-0.12732
+4	0.0626202	0.227289	0.777257	1.97528	10	0	0.0626202	0.227289	0.777257	1.97528	10	0	1	10	10	10	2.5	0.5	0.109183	0.133051	0.358375	1.01851	1.99501	-0.0238673	-0.225324	-0.660136	-0.976496	0	-10.9185	0	0	-5.78325	0	0	-0.995338	0.228864	0	-0.995338	-0.228864
+6	1.10813	1.34748	1.36601	1.65329	10	0	1.10813	1.34748	1.36601	1.65329	10	0	1	10	10	10	2.5	0.5	0.600504	2.42687	2.89291	2.57596	1.91949	-1.82637	-0.466031	0.316941	0.656473	0	-0.245455	1.08378	0	-0.245455	-1.08378	0	-4.73903	0.621603	0	-4.73903	-0.621603
+8	0.441469	0.3727	0.603703	1.31728	10	0	0.441469	0.3727	0.603703	1.31728	10	0	1	10	10	10	2.5	0.5	3.66378	2.02257	1.27484	1.16483	1.81216	1.64121	0.747733	0.110007	-0.647327	0	0.412044	3.25937	0	0.412044	-3.25937	0	-9.08432	2.69667	0	-9.08432	-2.69667
+10	0.269431	0.678094	1.19936	1.86825	10	0	0.269431	0.678094	1.19936	1.86825	10	0	1	10	10	10	2.5	0.5	0.18842	0.687087	1.52296	2.02995	1.97218	-0.498667	-0.83587	-0.506996	0.0577685	0	-0.652073	0.714291	0	-0.652073	-0.714291	0	-7.36911	0	0	-4.83401	0
diff --git a/yggdrasil/languages/Python/YggInterface.py b/yggdrasil/languages/Python/YggInterface.py
index 1d04e4647..6d5b2025e 100755
--- a/yggdrasil/languages/Python/YggInterface.py
+++ b/yggdrasil/languages/Python/YggInterface.py
@@ -1,11 +1,11 @@
 import os
-from yggdrasil import tools
+from yggdrasil import tools, constants
 from yggdrasil.communication.DefaultComm import DefaultComm
 
 
 YGG_MSG_MAX = tools.get_YGG_MSG_MAX()
-YGG_MSG_EOF = tools.YGG_MSG_EOF
-YGG_MSG_BUF = tools.YGG_MSG_BUF
+YGG_MSG_EOF = constants.YGG_MSG_EOF
+YGG_MSG_BUF = constants.YGG_MSG_BUF
 YGG_SERVER_INPUT = os.environ.get('YGG_SERVER_INPUT', False)
 YGG_SERVER_OUTPUT = os.environ.get('YGG_SERVER_OUTPUT', False)
 YGG_MODEL_NAME = os.environ.get('YGG_MODEL_NAME', False)
diff --git a/yggdrasil/metaschema/__init__.py b/yggdrasil/metaschema/__init__.py
index 18f4abe25..31b6d80c7 100644
--- a/yggdrasil/metaschema/__init__.py
+++ b/yggdrasil/metaschema/__init__.py
@@ -2,7 +2,9 @@
 import copy
 import pprint
 import jsonschema
+import numpy as np
 import yggdrasil
+from yggdrasil import constants, units
 from yggdrasil.metaschema.encoder import encode_json, decode_json
 from yggdrasil.metaschema.properties import get_registered_properties
 from yggdrasil.metaschema.datatypes import get_registered_types
@@ -28,6 +30,11 @@
 _base_validator = jsonschema.validators.validator_for(_base_schema)
 
         
+class MetaschemaTypeError(TypeError):
+    r"""Error that should be raised when a class encounters a type it cannot handle."""
+    pass
+
+
 def create_metaschema(overwrite=False):
     r"""Create the meta schema for validating ygg schema.
 
@@ -194,3 +201,90 @@ def normalize_instance(obj, schema, **kwargs):
     cls = get_validator()
     cls.check_schema(schema)
     return cls(schema).normalize(obj, **kwargs)
+
+
+def data2dtype(data):
+    r"""Get numpy data type for an object.
+
+    Args:
+        data (object): Python object.
+
+    Returns:
+        np.dtype: Numpy data type.
+
+    """
+    data_nounits = units.get_data(data)
+    if isinstance(data_nounits, np.ndarray):
+        dtype = data_nounits.dtype
+    elif isinstance(data_nounits, (list, dict, tuple)):
+        raise MetaschemaTypeError
+    elif isinstance(data_nounits,
+                    np.dtype(constants.VALID_TYPES['bytes']).type):
+        dtype = np.array(data_nounits).dtype
+    else:
+        dtype = np.array([data_nounits]).dtype
+    return dtype
+
+
+def definition2dtype(props):
+    r"""Get numpy data type for a type definition.
+
+    Args:
+        props (dict): Type definition properties.
+        
+    Returns:
+        np.dtype: Numpy data type.
+
+    """
+    typename = props.get('subtype', None)
+    if typename is None:
+        typename = props.get('type', None)
+        if typename is None:
+            raise KeyError('Could not find type in dictionary')
+    if ('precision' not in props):
+        if typename in constants.FLEXIBLE_TYPES:
+            out = np.dtype((constants.VALID_TYPES[typename]))
+        else:
+            raise RuntimeError("Precision required for type: '%s'" % typename)
+    elif typename == 'unicode':
+        out = np.dtype((constants.VALID_TYPES[typename],
+                        int(props['precision'] // 32)))
+    elif typename in constants.FLEXIBLE_TYPES:
+        out = np.dtype((constants.VALID_TYPES[typename],
+                        int(props['precision'] // 8)))
+    else:
+        out = np.dtype('%s%d' % (constants.VALID_TYPES[typename],
+                                 int(props['precision'])))
+    return out
+
+
+def type2numpy(typedef):
+    r"""Convert a type definition into a numpy dtype.
+
+    Args:
+        typedef (dict): Type definition.
+
+    Returns:
+        np.dtype: Numpy data type.
+
+    """
+    out = None
+    if ((isinstance(typedef, dict) and ('type' in typedef)
+         and (typedef['type'] == 'array') and ('items' in typedef))):
+        if isinstance(typedef['items'], dict):
+            as_array = (typedef['items']['type'] in ['1darray', 'ndarray'])
+            if as_array:
+                out = definition2dtype(typedef['items'])
+        elif isinstance(typedef['items'], (list, tuple)):
+            as_array = True
+            dtype_list = []
+            field_names = []
+            for i, x in enumerate(typedef['items']):
+                if x['type'] not in ['1darray', 'ndarray']:
+                    as_array = False
+                    break
+                dtype_list.append(definition2dtype(x))
+                field_names.append(x.get('title', 'f%d' % i))
+            if as_array:
+                out = np.dtype(dict(names=field_names, formats=dtype_list))
+    return out
diff --git a/yggdrasil/metaschema/datatypes/ArrayMetaschemaType.py b/yggdrasil/metaschema/datatypes/ArrayMetaschemaType.py
index 7af1097fd..c72ecdd68 100644
--- a/yggdrasil/metaschema/datatypes/ArrayMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/ArrayMetaschemaType.py
@@ -1,7 +1,6 @@
 from yggdrasil import units
 from yggdrasil.metaschema.datatypes.ScalarMetaschemaType import (
     ScalarMetaschemaType)
-from yggdrasil.metaschema.properties import ScalarMetaschemaProperties
 
 
 class OneDArrayMetaschemaType(ScalarMetaschemaType):
@@ -11,7 +10,7 @@ class OneDArrayMetaschemaType(ScalarMetaschemaType):
     description = 'A 1D array with or without units.'
     properties = ['length']
     metadata_properties = ['length']
-    python_types = ScalarMetaschemaProperties._all_python_arrays
+    python_types = units.ALL_PYTHON_ARRAYS_WITH_UNITS
 
     @classmethod
     def validate(cls, obj, raise_errors=False):
@@ -43,7 +42,7 @@ class NDArrayMetaschemaType(ScalarMetaschemaType):
     description = 'An ND array with or without units.'
     properties = ['shape']
     metadata_properties = ['shape']
-    python_types = ScalarMetaschemaProperties._all_python_arrays
+    python_types = units.ALL_PYTHON_ARRAYS_WITH_UNITS
 
     @classmethod
     def validate(cls, obj, raise_errors=False):
diff --git a/yggdrasil/metaschema/datatypes/InstanceMetaschemaType.py b/yggdrasil/metaschema/datatypes/InstanceMetaschemaType.py
index 928b28075..f798ff0f9 100644
--- a/yggdrasil/metaschema/datatypes/InstanceMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/InstanceMetaschemaType.py
@@ -1,4 +1,4 @@
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError
+from yggdrasil.metaschema import MetaschemaTypeError
 from yggdrasil.metaschema.datatypes.MetaschemaType import MetaschemaType
 from yggdrasil.metaschema.datatypes.JSONArrayMetaschemaType import (
     JSONArrayMetaschemaType)
diff --git a/yggdrasil/metaschema/datatypes/JSONMetaschemaType.py b/yggdrasil/metaschema/datatypes/JSONMetaschemaType.py
index c44b998f5..ed1fb8533 100644
--- a/yggdrasil/metaschema/datatypes/JSONMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/JSONMetaschemaType.py
@@ -1,5 +1,4 @@
 import numbers
-from jsonschema.compat import str_types, int_types
 from yggdrasil.metaschema.datatypes.MetaschemaType import MetaschemaType
 
 
@@ -74,7 +73,7 @@ class JSONIntegerMetaschemaType(JSONMetaschemaTypeBase):
 
     name = 'integer'
     description = 'JSON integer type.'
-    python_types = int_types
+    python_types = (int,)
     # TODO: Find a better way to signify this for creating the table
     cross_language_support = False
     example_data = int(1)
@@ -147,7 +146,7 @@ class JSONStringMetaschemaType(JSONMetaschemaTypeBase):
 
     name = 'string'
     description = 'JSON string type.'
-    python_types = str_types
+    python_types = (str,)
     example_data = 'hello'
 
     @classmethod
diff --git a/yggdrasil/metaschema/datatypes/MetaschemaType.py b/yggdrasil/metaschema/datatypes/MetaschemaType.py
index 6b95ff061..a4f22f63b 100644
--- a/yggdrasil/metaschema/datatypes/MetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/MetaschemaType.py
@@ -3,11 +3,11 @@
 import uuid
 import importlib
 import jsonschema
-from yggdrasil import tools
+from yggdrasil import constants
 from yggdrasil.metaschema import (get_metaschema, get_validator, encoder,
-                                  validate_instance)
+                                  validate_instance, MetaschemaTypeError)
 from yggdrasil.metaschema.datatypes import (
-    MetaschemaTypeError, MetaschemaTypeMeta, compare_schema, YGG_MSG_HEAD,
+    MetaschemaTypeMeta, compare_schema,
     get_type_class, conversions, is_default_typedef)
 from yggdrasil.metaschema.properties import get_metaschema_property
 
@@ -650,7 +650,7 @@ def serialize(self, obj, no_metadata=False, dont_encode=False,
             if k in kwargs:
                 raise RuntimeError("'%s' is a reserved keyword in the metadata." % k)
         if ((isinstance(obj, bytes)
-             and ((obj == tools.YGG_MSG_EOF) or kwargs.get('raw', False)
+             and ((obj == constants.YGG_MSG_EOF) or kwargs.get('raw', False)
                   or dont_encode))):
             metadata = kwargs
             data = obj
@@ -668,7 +668,8 @@ def serialize(self, obj, no_metadata=False, dont_encode=False,
             return data
         metadata['size'] = len(data)
         metadata.setdefault('id', str(uuid.uuid4()))
-        header = YGG_MSG_HEAD + encoder.encode_json(metadata) + YGG_MSG_HEAD
+        header = (constants.YGG_MSG_HEAD + encoder.encode_json(metadata)
+                  + constants.YGG_MSG_HEAD)
         if (max_header_size > 0) and (len(header) > max_header_size):
             metadata_type = metadata
             metadata = {}
@@ -678,10 +679,13 @@ def serialize(self, obj, no_metadata=False, dont_encode=False,
                 if k in metadata_type:
                     metadata[k] = metadata_type.pop(k)
             assert(metadata)
-            data = (encoder.encode_json(metadata_type) + YGG_MSG_HEAD + data)
+            data = (encoder.encode_json(metadata_type)
+                    + constants.YGG_MSG_HEAD + data)
             metadata['size'] = len(data)
             metadata['type_in_data'] = True
-            header = YGG_MSG_HEAD + encoder.encode_json(metadata) + YGG_MSG_HEAD
+            header = (constants.YGG_MSG_HEAD
+                      + encoder.encode_json(metadata)
+                      + constants.YGG_MSG_HEAD)
             if len(header) > max_header_size:  # pragma: debug
                 raise AssertionError(("The header is larger (%d) than the "
                                       "maximum (%d): %.100s...")
@@ -717,17 +721,17 @@ def deserialize(self, msg, no_data=False, metadata=None, dont_decode=False,
         if not isinstance(msg, bytes):
             raise TypeError("Message to be deserialized is not bytes type.")
         # Check for header
-        if msg.startswith(YGG_MSG_HEAD):
+        if msg.startswith(constants.YGG_MSG_HEAD):
             if metadata is not None:
                 raise ValueError("Metadata in header and provided by keyword.")
-            _, metadata, data = msg.split(YGG_MSG_HEAD, 2)
+            _, metadata, data = msg.split(constants.YGG_MSG_HEAD, 2)
             if len(metadata) == 0:
                 metadata = dict(size=len(data))
             else:
                 metadata = encoder.decode_json(metadata)
         elif isinstance(metadata, dict) and metadata.get('type_in_data', False):
-            assert(msg.count(YGG_MSG_HEAD) == 1)
-            typedef, data = msg.split(YGG_MSG_HEAD, 1)
+            assert(msg.count(constants.YGG_MSG_HEAD) == 1)
+            typedef, data = msg.split(constants.YGG_MSG_HEAD, 1)
             if len(typedef) > 0:
                 metadata.update(encoder.decode_json(typedef))
             metadata.pop('type_in_data')
@@ -736,13 +740,13 @@ def deserialize(self, msg, no_data=False, metadata=None, dont_decode=False,
             data = msg
             if metadata is None:
                 metadata = dict(size=len(msg))
-                if (((len(msg) > 0) and (msg != tools.YGG_MSG_EOF)
+                if (((len(msg) > 0) and (msg != constants.YGG_MSG_EOF)
                      and (not is_default_typedef(self._typedef))
                      and (not dont_decode))):
                     raise ValueError("Header marker not in message.")
         # Set flags based on data
         metadata['incomplete'] = (len(data) < metadata['size'])
-        if (data == tools.YGG_MSG_EOF):
+        if (data == constants.YGG_MSG_EOF):
             metadata['raw'] = True
         # Return based on flags
         if no_data:
diff --git a/yggdrasil/metaschema/datatypes/MultiMetaschemaType.py b/yggdrasil/metaschema/datatypes/MultiMetaschemaType.py
index b52129559..60d7a4f92 100644
--- a/yggdrasil/metaschema/datatypes/MultiMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/MultiMetaschemaType.py
@@ -1,7 +1,7 @@
 import copy
 from collections import OrderedDict
-from yggdrasil.metaschema.datatypes import (
-    get_type_class, MetaschemaTypeError)
+from yggdrasil.metaschema import MetaschemaTypeError
+from yggdrasil.metaschema.datatypes import get_type_class
 from yggdrasil.metaschema.datatypes.MetaschemaType import MetaschemaType
 from yggdrasil.metaschema.properties.TypeMetaschemaProperty import (
     TypeMetaschemaProperty)
diff --git a/yggdrasil/metaschema/datatypes/ScalarMetaschemaType.py b/yggdrasil/metaschema/datatypes/ScalarMetaschemaType.py
index f48b1d39c..a985026c9 100644
--- a/yggdrasil/metaschema/datatypes/ScalarMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/ScalarMetaschemaType.py
@@ -2,12 +2,12 @@
 import copy
 import warnings
 import base64
-from yggdrasil import units
+from yggdrasil import units, constants
+from yggdrasil.metaschema import data2dtype, definition2dtype
 from yggdrasil.metaschema.datatypes.MetaschemaType import MetaschemaType
 from yggdrasil.metaschema.datatypes.FixedMetaschemaType import (
     create_fixed_type_class)
-from yggdrasil.metaschema.properties import (
-    get_metaschema_property, ScalarMetaschemaProperties)
+from yggdrasil.metaschema.properties import get_metaschema_property
 
 
 class ScalarMetaschemaType(MetaschemaType):
@@ -25,7 +25,7 @@ class ScalarMetaschemaType(MetaschemaType):
     definition_properties = ['subtype']
     metadata_properties = ['subtype', 'precision', 'units']
     extract_properties = ['subtype', 'precision', 'units']
-    python_types = ScalarMetaschemaProperties._all_python_scalars
+    python_types = units.ALL_PYTHON_SCALARS_WITH_UNITS
 
     @classmethod
     def validate(cls, obj, raise_errors=False):
@@ -44,13 +44,12 @@ def validate(cls, obj, raise_errors=False):
             obj = obj.reshape((1, ))[0]
         if super(ScalarMetaschemaType, cls).validate(units.get_data(obj),
                                                      raise_errors=raise_errors):
-            dtype = ScalarMetaschemaProperties.data2dtype(obj)
+            dtype = data2dtype(obj)
             if cls.is_fixed and ('subtype' in cls.fixed_properties):
                 type_list = [
-                    ScalarMetaschemaProperties._valid_types[
-                        cls.fixed_properties['subtype']]]
+                    constants.VALID_TYPES[cls.fixed_properties['subtype']]]
             else:
-                type_list = ScalarMetaschemaProperties._valid_numpy_types
+                type_list = constants.NUMPY_TYPES
             if dtype.name.startswith(tuple(type_list)):
                 return True
             else:
@@ -103,7 +102,7 @@ def normalize(cls, obj):
                 else:
                     obj = str(obj)
             else:
-                dtype = ScalarMetaschemaProperties._python_scalars[
+                dtype = units.PYTHON_SCALARS_WITH_UNITS[
                     cls.fixed_properties['subtype']][0]
                 try:
                     obj = dtype(obj)
@@ -183,7 +182,7 @@ def decode_data(cls, obj, typedef):
 
         """
         bytes = base64.decodebytes(obj.encode('ascii'))
-        dtype = ScalarMetaschemaProperties.definition2dtype(typedef)
+        dtype = definition2dtype(typedef)
         arr = np.frombuffer(bytes, dtype=dtype)
         # arr = np.fromstring(bytes, dtype=dtype)
         if 'shape' in typedef:
@@ -210,7 +209,7 @@ def transform_type(cls, obj, typedef=None):
         typedef0 = cls.encode_type(obj)
         typedef1 = copy.deepcopy(typedef0)
         typedef1.update(**typedef)
-        dtype = ScalarMetaschemaProperties.definition2dtype(typedef1)
+        dtype = definition2dtype(typedef1)
         arr = cls.to_array(obj).astype(dtype, casting='same_kind')
         out = cls.from_array(arr, unit_str=typedef0.get('units', None),
                              dtype=dtype, typedef=typedef)
@@ -231,7 +230,7 @@ def to_array(cls, obj):
         if isinstance(obj_nounits, np.ndarray):
             arr = obj_nounits
         else:
-            dtype = ScalarMetaschemaProperties.data2dtype(obj_nounits)
+            dtype = data2dtype(obj_nounits)
             arr = np.array([obj_nounits], dtype=dtype)
         return arr
         
@@ -255,9 +254,9 @@ def from_array(cls, arr, unit_str=None, dtype=None, typedef=None):
 
         """
         # if (typedef is None) and (dtype is not None):
-        #     typedef = ScalarMetaschemaProperties.dtype2definition(dtype)
+        #     typedef = dtype2definition(dtype)
         # elif (dtype is None) and (typedef is not None):
-        #     dtype = ScalarMetaschemaProperties.definition2dtype(typedef)
+        #     dtype = definition2dtype(typedef)
         if (cls.name not in ['1darray', 'ndarray']) and (arr.ndim > 0):
             out = arr[0]
         else:
@@ -267,7 +266,7 @@ def from_array(cls, arr, unit_str=None, dtype=None, typedef=None):
             out = cls.as_python_type(out, typedef)
         if unit_str is not None:
             if dtype is None:
-                dtype = ScalarMetaschemaProperties.data2dtype(out)
+                dtype = data2dtype(out)
             out = units.add_units(out, unit_str, dtype=dtype)
         return out
 
@@ -285,7 +284,7 @@ def get_extract_properties(cls, metadata):
         """
         out = super(ScalarMetaschemaType, cls).get_extract_properties(metadata)
         dtype = metadata.get('subtype', metadata['type'])
-        if (((dtype in ScalarMetaschemaProperties._flexible_types)
+        if (((dtype in constants.FLEXIBLE_TYPES)
              and (metadata['type'] not in ['1darray', 'ndarray'])
              and (not metadata.get('fixed_precision', False)))):
             out.remove('precision')
@@ -308,7 +307,7 @@ def as_python_type(cls, obj, typedef):
         if ((isinstance(typedef, dict)
              and (typedef.get('type', '1darray') not in ['1darray', 'ndarray']))):
             stype = typedef.get('subtype', typedef.get('type', None))
-            py_type = ScalarMetaschemaProperties._python_scalars[stype][0]
+            py_type = units.PYTHON_SCALARS_WITH_UNITS[stype][0]
             if np.dtype(py_type) == type(obj):
                 obj = py_type(obj)
         return obj
@@ -324,7 +323,7 @@ def _generate_data(cls, typedef):
             object: Python object of the specified type.
 
         """
-        dtype = ScalarMetaschemaProperties.definition2dtype(typedef)
+        dtype = definition2dtype(typedef)
         if typedef['type'] == '1darray':
             out = np.zeros(typedef.get('length', 2), dtype)
         elif typedef['type'] == 'ndarray':
@@ -336,7 +335,7 @@ def _generate_data(cls, typedef):
 
 
 # Dynamically create explicity scalar classes for shorthand
-for t in ScalarMetaschemaProperties._valid_types.keys():
+for t in constants.VALID_TYPES.keys():
     short_doc = 'A %s value with or without units.' % t
     long_doc = ('%s\n\n'
                 '    Developer Notes:\n'
@@ -344,6 +343,6 @@ def _generate_data(cls, typedef):
     kwargs = {'target_globals': globals(),
               '__doc__': long_doc,
               '__module__': ScalarMetaschemaType.__module__,
-              'python_types': ScalarMetaschemaProperties._python_scalars[t]}
+              'python_types': units.PYTHON_SCALARS_WITH_UNITS[t]}
     create_fixed_type_class(t, short_doc, ScalarMetaschemaType,
                             {'subtype': t}, **kwargs)
diff --git a/yggdrasil/metaschema/datatypes/__init__.py b/yggdrasil/metaschema/datatypes/__init__.py
index c29cefd52..3e0091b07 100644
--- a/yggdrasil/metaschema/datatypes/__init__.py
+++ b/yggdrasil/metaschema/datatypes/__init__.py
@@ -2,7 +2,7 @@
 import glob
 import jsonschema
 import copy
-import numpy as np
+from yggdrasil import constants
 from yggdrasil.components import ClassRegistry
 from yggdrasil.metaschema.encoder import decode_json
 from yggdrasil.metaschema.properties import get_metaschema_property
@@ -11,7 +11,6 @@
 _schema_dir = os.path.join(os.path.dirname(__file__), 'schemas')
 # _base_validator = jsonschema.validators.validator_for({"$schema": ""})
 _base_validator = jsonschema.validators._LATEST_VERSION
-YGG_MSG_HEAD = b'YGG_MSG_HEAD'
 _property_attributes = ['properties', 'definition_properties',
                         'metadata_properties', 'extract_properties']
 
@@ -39,11 +38,6 @@ def import_schema_types():
                             % new_names)
     
 
-class MetaschemaTypeError(TypeError):
-    r"""Error that should be raised when a class encounters a type it cannot handle."""
-    pass
-
-
 _default_typedef = {'type': 'bytes'}
 _type_registry = ClassRegistry(import_function=import_schema_types)
 
@@ -302,8 +296,8 @@ def guess_type_from_msg(msg):
 
     """
     try:
-        if YGG_MSG_HEAD in msg:
-            _, metadata, data = msg.split(YGG_MSG_HEAD, 2)
+        if constants.YGG_MSG_HEAD in msg:
+            _, metadata, data = msg.split(constants.YGG_MSG_HEAD, 2)
             metadata = decode_json(metadata)
             cls = _type_registry[metadata['datatype']['type']]
         else:
@@ -456,7 +450,7 @@ def decode(msg):
 
     """
     cls = guess_type_from_msg(msg)
-    metadata = decode_json(msg.split(YGG_MSG_HEAD, 2)[1])
+    metadata = decode_json(msg.split(constants.YGG_MSG_HEAD, 2)[1])
     typedef = cls.extract_typedef(metadata.get('datatype', {}))
     cls_inst = cls(**typedef)
     obj = cls_inst.deserialize(msg)[0]
@@ -581,37 +575,3 @@ def generate_data(typedef):
     """
     type_cls = get_type_class(typedef['type'])
     return type_cls.generate_data(typedef)
-
-
-def type2numpy(typedef):
-    r"""Convert a type definition into a numpy dtype.
-
-    Args:
-        typedef (dict): Type definition.
-
-    Returns:
-        np.dtype: Numpy data type.
-
-    """
-    from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-        definition2dtype)
-    out = None
-    if ((isinstance(typedef, dict) and ('type' in typedef)
-         and (typedef['type'] == 'array') and ('items' in typedef))):
-        if isinstance(typedef['items'], dict):
-            as_array = (typedef['items']['type'] in ['1darray', 'ndarray'])
-            if as_array:
-                out = definition2dtype(typedef['items'])
-        elif isinstance(typedef['items'], (list, tuple)):
-            as_array = True
-            dtype_list = []
-            field_names = []
-            for i, x in enumerate(typedef['items']):
-                if x['type'] not in ['1darray', 'ndarray']:
-                    as_array = False
-                    break
-                dtype_list.append(definition2dtype(x))
-                field_names.append(x.get('title', 'f%d' % i))
-            if as_array:
-                out = np.dtype(dict(names=field_names, formats=dtype_list))
-    return out
diff --git a/yggdrasil/metaschema/encoder.py b/yggdrasil/metaschema/encoder.py
index f1d01b30b..2c59b5bbd 100644
--- a/yggdrasil/metaschema/encoder.py
+++ b/yggdrasil/metaschema/encoder.py
@@ -36,11 +36,11 @@ def string2import(s):
 
     """
     pkg_mod = s.split(u':')
-    if len(pkg_mod) == 2:
+    if (len(pkg_mod) == 2) and (not s.startswith('http')) and (' ' not in s):
         try:
             mod = importlib.import_module(pkg_mod[0])
             s = getattr(mod, pkg_mod[1])
-        except (ImportError, AttributeError):
+        except (ImportError, AttributeError):  # pragma: debug
             pass
     return s
 
@@ -50,8 +50,8 @@ class JSONReadableEncoder(stdjson.JSONEncoder):
 
     def default(self, o):  # pragma: no cover
         r"""Encoder that allows for expansion types."""
-        from yggdrasil.metaschema.datatypes import (
-            encode_data_readable, MetaschemaTypeError)
+        from yggdrasil.metaschema import MetaschemaTypeError
+        from yggdrasil.metaschema.datatypes import encode_data_readable
         try:
             return encode_data_readable(o)
         except MetaschemaTypeError:
@@ -63,8 +63,8 @@ class JSONEncoder(_json_encoder):
 
     def default(self, o):
         r"""Encoder that allows for expansion types."""
-        from yggdrasil.metaschema.datatypes import (
-            encode_data, MetaschemaTypeError)
+        from yggdrasil.metaschema import MetaschemaTypeError
+        from yggdrasil.metaschema.datatypes import encode_data
         try:
             return encode_data(o)
         except MetaschemaTypeError:
diff --git a/yggdrasil/metaschema/properties/ArgsMetaschemaProperty.py b/yggdrasil/metaschema/properties/ArgsMetaschemaProperty.py
index 64e0084bd..c7e49fa24 100644
--- a/yggdrasil/metaschema/properties/ArgsMetaschemaProperty.py
+++ b/yggdrasil/metaschema/properties/ArgsMetaschemaProperty.py
@@ -1,5 +1,5 @@
 import weakref
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError
+from yggdrasil.metaschema import MetaschemaTypeError
 from yggdrasil.metaschema.properties.MetaschemaProperty import (
     MetaschemaProperty)
 from yggdrasil.metaschema.properties.JSONArrayMetaschemaProperties import (
diff --git a/yggdrasil/metaschema/properties/JSONObjectMetaschemaProperties.py b/yggdrasil/metaschema/properties/JSONObjectMetaschemaProperties.py
index 99c797bc8..3e0033fbc 100644
--- a/yggdrasil/metaschema/properties/JSONObjectMetaschemaProperties.py
+++ b/yggdrasil/metaschema/properties/JSONObjectMetaschemaProperties.py
@@ -1,4 +1,3 @@
-from jsonschema.compat import iteritems
 from yggdrasil.metaschema import normalizer as normalizer_mod
 from yggdrasil.metaschema.datatypes import encode_type, compare_schema
 from yggdrasil.metaschema.properties.MetaschemaProperty import MetaschemaProperty
@@ -34,7 +33,7 @@ def normalize(cls, validator, value, instance, schema):
         r"""Normalization method for 'properties' container property."""
         if not isinstance(instance, dict):
             return instance
-        for property, subschema in iteritems(value):
+        for property, subschema in value.items():
             if property not in instance:
                 instance[property] = normalizer_mod.UndefinedProperty()
         return instance
diff --git a/yggdrasil/metaschema/properties/ScalarMetaschemaProperties.py b/yggdrasil/metaschema/properties/ScalarMetaschemaProperties.py
index b3cda3ac9..f13eb834d 100644
--- a/yggdrasil/metaschema/properties/ScalarMetaschemaProperties.py
+++ b/yggdrasil/metaschema/properties/ScalarMetaschemaProperties.py
@@ -1,96 +1,7 @@
-import numpy as np
-from yggdrasil import units, platform
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError
-from yggdrasil.metaschema.properties.MetaschemaProperty import MetaschemaProperty
-from collections import OrderedDict
-
-
-_valid_numpy_types = ['int', 'uint', 'float', 'complex']
-_valid_types = OrderedDict([(k, k) for k in _valid_numpy_types])
-_flexible_types = ['string', 'bytes', 'unicode']
-_python_scalars = OrderedDict([('float', [float]),
-                               ('int', [int]), ('uint', []),
-                               ('complex', [complex])])
-_valid_numpy_types += ['bytes', 'unicode', 'str']
-_valid_types['bytes'] = 'bytes'
-_valid_types['unicode'] = 'str'
-_python_scalars.update([('bytes', [bytes]), ('unicode', [str])])
-for t, t_np in _valid_types.items():
-    prec_list = []
-    if t in ['float']:
-        prec_list = [16, 32, 64]
-        if not platform._is_win:
-            prec_list.append(128)  # Not available on windows
-    if t in ['int', 'uint']:
-        prec_list = [8, 16, 32, 64]
-    elif t in ['complex']:
-        prec_list = [64, 128]
-        if not platform._is_win:
-            prec_list.append(256)  # Not available on windows
-    _python_scalars[t].append(np.dtype(t_np).type)
-    for p in prec_list:
-        _python_scalars[t].append(np.dtype(t_np + str(p)).type)
-# For some reason windows fails to check types on ints in some cases
-_python_scalars['int'].append(np.signedinteger)
-_python_scalars['uint'].append(np.unsignedinteger)
-_all_python_scalars = [units._unit_quantity]
-for k in _python_scalars.keys():
-    _python_scalars[k].append(units._unit_quantity)
-    _all_python_scalars += list(_python_scalars[k])
-    _python_scalars[k] = tuple(_python_scalars[k])
-_all_python_arrays = tuple(set([np.ndarray, units._unit_array]))
-_all_python_scalars = tuple(set(_all_python_scalars))
-
-
-def data2dtype(data):
-    r"""Get numpy data type for an object.
-
-    Args:
-        data (object): Python object.
-
-    Returns:
-        np.dtype: Numpy data type.
-
-    """
-    data_nounits = units.get_data(data)
-    if isinstance(data_nounits, np.ndarray):
-        dtype = data_nounits.dtype
-    elif isinstance(data_nounits, (list, dict, tuple)):
-        raise MetaschemaTypeError
-    elif isinstance(data_nounits, np.dtype(_valid_types['bytes']).type):
-        dtype = np.array(data_nounits).dtype
-    else:
-        dtype = np.array([data_nounits]).dtype
-    return dtype
-
-
-def definition2dtype(props):
-    r"""Get numpy data type for a type definition.
-
-    Args:
-        props (dict): Type definition properties.
-        
-    Returns:
-        np.dtype: Numpy data type.
-
-    """
-    typename = props.get('subtype', None)
-    if typename is None:
-        typename = props.get('type', None)
-        if typename is None:
-            raise KeyError('Could not find type in dictionary')
-    if ('precision' not in props):
-        if typename in _flexible_types:
-            out = np.dtype((_valid_types[typename]))
-        else:
-            raise RuntimeError("Precision required for type: '%s'" % typename)
-    elif typename == 'unicode':
-        out = np.dtype((_valid_types[typename], int(props['precision'] // 32)))
-    elif typename in _flexible_types:
-        out = np.dtype((_valid_types[typename], int(props['precision'] // 8)))
-    else:
-        out = np.dtype('%s%d' % (_valid_types[typename], int(props['precision'])))
-    return out
+from yggdrasil import units, constants
+from yggdrasil.metaschema import data2dtype, MetaschemaTypeError
+from yggdrasil.metaschema.properties.MetaschemaProperty import (
+    MetaschemaProperty)
 
 
 class SubtypeMetaschemaProperty(MetaschemaProperty):
@@ -99,14 +10,14 @@ class SubtypeMetaschemaProperty(MetaschemaProperty):
     name = 'subtype'
     schema = {'description': 'The base type for each item.',
               'type': 'string',
-              'enum': [k for k in sorted(_valid_types.keys())]}
+              'enum': [k for k in sorted(constants.VALID_TYPES.keys())]}
 
     @classmethod
     def encode(cls, instance, typedef=None):
         r"""Encoder for the 'subtype' scalar property."""
         dtype = data2dtype(instance)
         out = None
-        for k, v in _valid_types.items():
+        for k, v in constants.VALID_TYPES.items():
             if dtype.name.startswith(v):
                 out = k
                 break
diff --git a/yggdrasil/metaschema/properties/TypeMetaschemaProperty.py b/yggdrasil/metaschema/properties/TypeMetaschemaProperty.py
index e27336132..0e595bd0c 100644
--- a/yggdrasil/metaschema/properties/TypeMetaschemaProperty.py
+++ b/yggdrasil/metaschema/properties/TypeMetaschemaProperty.py
@@ -1,5 +1,6 @@
+from yggdrasil.metaschema import MetaschemaTypeError
 from yggdrasil.metaschema.datatypes import (
-    get_registered_types, get_type_class, MetaschemaTypeError)
+    get_registered_types, get_type_class)
 from yggdrasil.metaschema.properties.MetaschemaProperty import MetaschemaProperty
 
 
diff --git a/yggdrasil/runner.py b/yggdrasil/runner.py
index a9a76c96c..c00a3f9e2 100755
--- a/yggdrasil/runner.py
+++ b/yggdrasil/runner.py
@@ -4,7 +4,6 @@
 import time
 import copy
 import signal
-import traceback
 import atexit
 from pprint import pformat
 from itertools import chain
@@ -24,6 +23,11 @@
 COLOR_NORMAL = '\033[0m'
 
 
+class IntegrationError(BaseException):
+    r"""Error raised when there is an error in an integration."""
+    pass
+
+
 class YggFunction(YggClass):
     r"""This class wraps function-like behavior around a model.
 
@@ -238,12 +242,12 @@ class YggRunner(YggClass):
         namespace (str, optional): Name that should be used to uniquely
             identify any RMQ exchange. Defaults to the value in the config
             file.
-        host (str, optional): Name of the host that the models will be launched
-            from. Defaults to None.
+        host (str, optional): Name of the host that the models will be
+            launched from. Defaults to None.
         rank (int, optional): Rank of this set of models if run in parallel.
             Defaults to 0.
-        ygg_debug_level (str, optional): Level for Ygg debug messages. Defaults
-            to environment variable 'YGG_DEBUG'.
+        ygg_debug_level (str, optional): Level for Ygg debug messages.
+            Defaults to environment variable 'YGG_DEBUG'.
         rmq_debug_level (str, optional): Level for RabbitMQ debug messages.
             Defaults to environment variable 'RMQ_DEBUG'.
         ygg_debug_prefix (str, optional): Prefix for Ygg debug messages.
@@ -257,10 +261,16 @@ class YggRunner(YggClass):
         partial_commtype (dict, optional): Communicator kwargs that should be
             be used for the connections to the unpaired channels when
             complete_partial is True. Defaults to None and will be ignored.
+        yaml_param (dict, optional): Parameters that should be used in
+            mustache formatting of YAML files. Defaults to None and is
+            ignored.
+        validate (bool, optional): If True, the validation scripts for each
+            modle (if present), will be run after the integration finishes
+            running. Defaults to False.
 
     Attributes:
-        namespace (str): Name that should be used to uniquely identify any RMQ
-            exchange.
+        namespace (str): Name that should be used to uniquely identify any
+            RMQ exchange.
         host (str): Name of the host that the models will be launched from.
         rank (int): Rank of this set of models if run in parallel.
         modeldrivers (dict): Model drivers associated with this run.
@@ -276,7 +286,7 @@ def __init__(self, modelYmls, namespace=None, host=None, rank=0,
                  ygg_debug_prefix=None, connection_task_method='thread',
                  as_service=False, complete_partial=False,
                  partial_commtype=None, production_run=False,
-                 mpi_tag_start=None):
+                 mpi_tag_start=None, yaml_param=None, validate=False):
         self.mpi_comm = None
         name = 'runner'
         if MPI is not None:
@@ -306,6 +316,7 @@ def __init__(self, modelYmls, namespace=None, host=None, rank=0,
         self.error_flag = False
         self.complete_partial = complete_partial
         self.partial_commtype = partial_commtype
+        self.validate = validate
         self.debug("Running in %s with path %s namespace %s rank %d",
                    os.getcwd(), sys.path, namespace, rank)
         # Update environment based on config
@@ -317,7 +328,7 @@ def __init__(self, modelYmls, namespace=None, host=None, rank=0,
         else:
             self.drivers = yamlfile.parse_yaml(
                 modelYmls, complete_partial=complete_partial,
-                partial_commtype=partial_commtype)
+                partial_commtype=partial_commtype, yaml_param=yaml_param)
             self.connectiondrivers = self.drivers['connection']
             self.modeldrivers = self.drivers['model']
             for x in self.modeldrivers.values():
@@ -436,6 +447,11 @@ def run(self, signal_handler=None, timer=None, t0=None):
                 tprev = t
             self.info(40 * '=')
             self.info('%20s\t%f', "Total", tprev - t0)
+        if self.error_flag:
+            raise IntegrationError("Error running the integration.")
+        if self.validate:
+            for v in self.modeldrivers.values():
+                v['instance'].run_validation()
         return times
 
     @property
@@ -1029,10 +1045,5 @@ def get_runner(models, **kwargs):
 
 def run(*args, **kwargs):
     yggRunner = get_runner(*args, **kwargs)
-    try:
-        yggRunner.run()
-        yggRunner.debug("runner returns, exiting")
-    except Exception as ex:  # pragma: debug
-        yggRunner.pprint("yggrun exception: %s" % type(ex))
-        print(traceback.format_exc())
-    print('')
+    yggRunner.run()
+    yggRunner.debug("runner returns, exiting")
diff --git a/yggdrasil/schema.py b/yggdrasil/schema.py
index eac223122..97fa18034 100644
--- a/yggdrasil/schema.py
+++ b/yggdrasil/schema.py
@@ -3,6 +3,7 @@
 import pprint
 import yaml
 import json
+import importlib
 from collections import OrderedDict
 from jsonschema.exceptions import ValidationError
 from yggdrasil import metaschema
@@ -11,6 +12,11 @@
 _schema_fname = os.path.abspath(os.path.join(
     os.path.dirname(__file__), '.ygg_schema.yml'))
 _schema = None
+_constants_separator = (
+    "\n# ======================================================\n"
+    "# Do not edit this file past this point as the following\n"
+    "# is generated by yggdrasil.schema.update_constants\n"
+    "# ======================================================\n")
 
 
 class SchemaDict(OrderedDict):
@@ -70,16 +76,10 @@ def init_schema(fname=None):
 
 def create_schema():
     r"""Create a new schema from the registry."""
-    from yggdrasil.components import init_registry
-    try:
-        old_env = os.environ.get('YGG_RUNNING_YGGSCHEMA', None)
-        os.environ['YGG_RUNNING_YGGSCHEMA'] = '1'
-        x = SchemaRegistry(init_registry())
-    finally:
-        if old_env is None:
-            del os.environ['YGG_RUNNING_YGGSCHEMA']
-        else:  # pragma: no cover
-            os.environ['YGG_RUNNING_YGGSCHEMA'] = old_env
+    from yggdrasil.components import init_registry, registering
+    with registering():
+        x = SchemaRegistry(init_registry(recurse=True))
+        update_constants(x)
     return x
 
 
@@ -141,7 +141,9 @@ def convert_extended2base(s):
                 'function': 'string', 'class': 'string',
                 'instance': 'string', '1darray': 'array',
                 'ndarray': 'array', 'obj': 'object',
-                'ply': 'object'}
+                'ply': 'object',
+                'any': ["number", "string", "boolean", "object", "array",
+                        "null"]}
     if isinstance(s, (list, tuple)):
         s = [convert_extended2base(x) for x in s]
     elif isinstance(s, (dict, OrderedDict)):
@@ -220,11 +222,13 @@ def get_model_form_schema(fname_dst=None, **kwargs):
     return out
 
 
-def update_constants():
+def update_constants(schema=None):
     r"""Update constants.py with info from the schema."""
     from yggdrasil.components import import_component
     from yggdrasil.drivers.CompiledModelDriver import (
         get_compilation_tool_registry)
+    if schema is None:
+        schema = get_schema()
 
     def as_lines(x, newline='\n', key_order=None):
         out = ""
@@ -245,14 +249,19 @@ def as_lines(x, newline='\n', key_order=None):
             out += repr(x)
         return out
 
-    separator = (
-        "\n# ======================================================\n"
-        "# Do not edit this file past this point as the following\n"
-        "# is generated by yggdrasil.schema.update_constants\n"
-        "# ======================================================\n")
     filename = os.path.join(os.path.dirname(__file__), 'constants.py')
-    s = get_schema()
-    drivers = {k: import_component('model', k) for k in s['model'].subtypes}
+    # Component information
+    component_registry = {}
+    for k, v in schema.items():
+        component_registry[k] = {
+            'module': v.module,
+            'default': v.default_subtype,
+            'base': v.base_subtype_class_name,
+            'key': v.subtype_key,
+            'subtypes': v.subtype2class}
+    # Language driver information
+    drivers = {k: import_component('model', v)
+               for k, v in component_registry['model']['subtypes'].items()}
     language_cat = ['compiled', 'interpreted', 'build', 'dsl', 'other']
     typemap = {'compiler': 'compiled', 'interpreter': 'interpreted'}
     lang2ext = {'yaml': '.yml', 'executable': '.exe'}
@@ -277,6 +286,9 @@ def as_lines(x, newline='\n', key_order=None):
         languages_with_aliases.setdefault(drv_type, [])
         languages_with_aliases[drv_type].append(drv.language)
         languages_with_aliases[drv_type] += drv.language_aliases
+    languages = {k: sorted(v) for k, v in languages.items()}
+    languages_with_aliases = {k: sorted(v) for k, v in
+                              languages_with_aliases.items()}
     for x in ['compiler', 'linker', 'archiver']:
         reg = get_compilation_tool_registry(x).get('by_language', {})
         for lang, tools in reg.items():
@@ -291,8 +303,13 @@ def as_lines(x, newline='\n', key_order=None):
                     compiler_env_vars[lang] = compilation_tool_vars[k].copy()
     language_cat = list(languages.keys())
     with open(filename, 'r') as fd:
-        lines = [fd.read().split(separator)[0], separator[1:]]
+        lines = [fd.read().split(_constants_separator)[0],
+                 _constants_separator[1:]]
+    lines += [
+        "", "# Component registry",
+        f"COMPONENT_REGISTRY = {as_lines(component_registry)}"]
     lines += [
+        "", "# Language driver constants",
         "LANG2EXT = %s" % as_lines(lang2ext),
         "EXT2LANG = {v: k for k, v in LANG2EXT.items()}",
         "LANGUAGES = %s" % as_lines(languages, key_order=language_cat)]
@@ -340,19 +357,18 @@ class ComponentSchema(object):
             associated values of the subtype_key property for this component.
 
     """
-    # _subtype_keys = {'model': 'language', 'comm': 'commtype', 'file': 'filetype',
-    #                  'connection': 'connection_type'}
 
-    def __init__(self, schema_type, subtype_key,
-                 schema_registry=None, schema_subtypes=None):
+    def __init__(self, schema_type, subtype_key, schema_registry=None,
+                 module=None, schema_subtypes=None):
         self._storage = SchemaDict()
         self._base_schema = None
-        self.schema_registry = schema_registry
         self.schema_type = schema_type
         self.subtype_key = subtype_key
+        self.schema_registry = schema_registry
         if schema_subtypes is None:
             schema_subtypes = {}
         self.schema_subtypes = schema_subtypes
+        self.module = module
         super(ComponentSchema, self).__init__()
 
     def identify_subtype(self, doc):
@@ -430,8 +446,9 @@ def get_subtype_schema(self, subtype, unique=False, relaxed=False,
             if unique:
                 out['additionalProperties'] = True
                 if 'required' in out:
-                    out['required'] = list(set(out['required'])
-                                           - set(self._base_schema.get('required', [])))
+                    out['required'] = sorted(
+                        list(set(out['required'])
+                             - set(self._base_schema.get('required', []))))
                     if not out['required']:
                         del out['required']
                 for k in self._base_schema['properties'].keys():
@@ -447,8 +464,7 @@ def get_subtype_schema(self, subtype, unique=False, relaxed=False,
             else:
                 from yggdrasil.components import import_component
                 comp_cls = import_component(
-                    self.schema_type, subtype=subtype,
-                    without_schema=True)
+                    self.schema_type, subtype=subtype)
             out = {'oneOf': [out, {'type': 'instance',
                                    'class': comp_cls}]}
         return out
@@ -480,7 +496,7 @@ def get_schema(self, relaxed=False, allow_instance=False, for_form=False):
         else:
             out['allOf'] = [self.get_subtype_schema('base', relaxed=relaxed),
                             {'anyOf': [self.get_subtype_schema(x, unique=True)
-                                       for x in self._storage.keys()]}]
+                                       for x in sorted(self._storage.keys())]}]
         if allow_instance:
             out['oneOf'] = [{'allOf': out.pop('allOf')},
                             {'type': 'instance',
@@ -503,7 +519,7 @@ def full_schema(self):
                'title': self.schema_type,
                'allOf': [self.get_subtype_schema('base', unique=True),
                          {'anyOf': [self.get_subtype_schema(x)
-                                    for x in self._storage.keys()]}]}
+                                    for x in sorted(self._storage.keys())]}]}
         return out
 
     @classmethod
@@ -533,9 +549,15 @@ def from_schema(cls, schema, schema_registry=None):
         out = cls(schema_type, subtype_key, schema_registry=schema_registry)
         out._base_schema = schema['allOf'][0]
         for v in subt_schema:
-            out._storage[v['title']] = v
+            v_class_name = v['title'].split('.')[-1]
+            out._storage[v_class_name] = v
             subtypes = v['properties'][out.subtype_key]['enum']
-            out.schema_subtypes[v['title']] = subtypes
+            out.schema_subtypes[v_class_name] = subtypes
+            v_module = '.'.join(v['title'].split('.')[:-2])
+            if out.module is None:
+                out.module = v_module
+            else:
+                assert(v_module == out.module)
         # Remove subtype specific properties
         for k in subt_props:
             if k != out.subtype_key:
@@ -550,12 +572,12 @@ def from_schema(cls, schema, schema_registry=None):
         return out
 
     @classmethod
-    def from_registry(cls, schema_type, schema_classes, **kwargs):
+    def from_registry(cls, schema_type, registry, **kwargs):
         r"""Construct a ComponentSchema from a registry entry.
 
         Args:
             schema_type (str): Name of component type to build.
-            schema_classes (list): List of classes for the component type.
+            registry (dict): Registry information for the component.
             **kwargs: Additional keyword arguments are passed to the class
                 __init__ method.
 
@@ -563,10 +585,15 @@ def from_registry(cls, schema_type, schema_classes, **kwargs):
             ComponentSchema: Schema with information from classes.
 
         """
-        out = None
-        for x in schema_classes.values():
-            if out is None:
-                out = cls(schema_type, x._schema_subtype_key, **kwargs)
+        schema_subtypes = {}
+        for k, v in registry['subtypes'].items():
+            if v not in schema_subtypes:
+                schema_subtypes[v] = []
+            schema_subtypes[v].append(k)
+        kwargs.update(module=registry['module'],
+                      schema_subtypes=schema_subtypes)
+        out = cls(schema_type, registry['key'], **kwargs)
+        for x in registry['classes'].values():
             out.append(x, verify=True)
         return out
 
@@ -601,14 +628,32 @@ def subtype2class(self):
                 out[iv] = k
         return out
 
+    @property
+    def base_subtype_class_name(self):
+        r"""str: Name of base class for the subtype."""
+        if not getattr(self, '_base_subtype_class_name', None):
+            self.base_subtype_class
+        return self._base_subtype_class_name
+            
     @property
     def base_subtype_class(self):
         r"""ComponentClass: Base class for the subtype."""
         if not getattr(self, '_base_subtype_class', None):
-            from yggdrasil.components import get_component_base_class
-            keys = list(self.subtype2class.values())
-            self._base_subtype_class = get_component_base_class(
-                self.schema_type, subtype=keys[0], without_schema=True)
+            default_class = list(self.schema_subtypes.keys())[0]
+            cls = getattr(
+                importlib.import_module(f"{self.module}.{default_class}"),
+                default_class)
+            base_class = cls
+            for i, x in enumerate(cls.__mro__):
+                if x._schema_type != cls._schema_type:
+                    break
+                base_class = x
+            else:  # pragma: debug
+                raise RuntimeError(
+                    f"Could not determine a base class for "
+                    f"{self.schema_type} (using class {cls})")
+            self._base_subtype_class = base_class
+            self._base_subtype_class_name = base_class.__name__
         return self._base_subtype_class
 
     @property
@@ -642,15 +687,17 @@ def append(self, comp_cls, verify=False):
         assert(comp_cls._schema_type == self.schema_type)
         assert(comp_cls._schema_subtype_key == self.subtype_key)
         name = comp_cls.__name__
-        # name = '%s.%s' % (comp_cls.__module__, comp_cls.__name__)
+        fullname = f'{comp_cls.__module__}.{comp_cls.__name__}'
+        subtype_module = '.'.join(comp_cls.__module__.split('.')[:-1])
         # Append subtype
         subtype_list = getattr(comp_cls, '_%s' % self.subtype_key, None)
         if not isinstance(subtype_list, list):
             subtype_list = [subtype_list]
         subtype_list += getattr(comp_cls, '_%s_aliases' % self.subtype_key, [])
         self.schema_subtypes[name] = subtype_list
+        assert(subtype_module == self.module)
         # Create new schema for subtype
-        new_schema = {'title': name,
+        new_schema = {'title': fullname,
                       'description': ('Schema for %s component %s subtype.'
                                       % (self.schema_type, subtype_list)),
                       'type': 'object',
@@ -694,9 +741,9 @@ def append(self, comp_cls, verify=False):
         # Update base schema, checking for compatiblity
         if not is_base:
             if 'required' in self._base_schema:
-                self._base_schema['required'] = list(
+                self._base_schema['required'] = sorted(list(
                     set(self._base_schema['required'])
-                    & set(new_schema.get('required', [])))
+                    & set(new_schema.get('required', []))))
                 if not self._base_schema['required']:  # pragma: no cover
                     del self._base_schema['required']
             prop_overlap = list(
@@ -896,15 +943,21 @@ def model_form_schema_props(self):
                 'comm': {
                     'transform': {
                         "type": "array",
-                        "items": {"$ref": "#/definitions/transform"}}}},
+                        "items": {"$ref": "#/definitions/transform"}},
+                    'default_file': {
+                        '$ref': '#/definitions/file'}},
+                'file': {
+                    'serializer': {
+                        '$ref': '#/definitions/serializer'}}},
             'required': {
                 'model': ['args', 'inputs', 'outputs', 'description',
-                          'repository_url']},
+                          'repository_url', 'repository_commit']},
             'remove': {
                 'comm': ['is_default', 'length_map', 'serializer',
                          'address', 'dont_copy', 'for_service',
                          'send_converter', 'recv_converter', 'client_id',
-                         'cookies', 'host', 'params', 'port'],
+                         'cookies', 'host', 'params', 'port', 'commtype'],
+                'ocomm': ['default_value'],
                 'file': ['is_default', 'length_map',
                          'wait_for_creation', 'working_dir',
                          'read_meth', 'in_temp',
@@ -920,19 +973,37 @@ def model_form_schema_props(self):
                           'aggregation', 'interpolation', 'synonyms',
                           'driver']},
             'order': {
-                'model': ['name', 'repository_url', 'contact_email',
-                          'language', 'description', 'args', 'inputs',
-                          'outputs']},
+                'model': ['name', 'repository_url', 'repository_commit',
+                          'contact_email', 'language', 'description',
+                          'args', 'inputs', 'outputs'],
+                'comm': ['name', 'datatype']},
+            'update': {
+                'model': {
+                    'inputs': {
+                        'description': ('Zero or more channels carrying '
+                                        'input to the model'),
+                        'items': {'$ref': '#/definitions/icomm'}},
+                    'outputs': {
+                        'description': ('Zero or more channels carrying '
+                                        'output from the model'),
+                        'items': {'$ref': '#/definitions/ocomm'}},
+                    'repository_commit': {
+                        'description': ('Commit that should be checked out '
+                                        'from the model repository.')},
+                    'args': {'minItems': 1}},
+                'file': {
+                    'name': {
+                        'description': ('Path to a file in the model '
+                                        'repository')}}},
         }
         return prop
         
     @property
     def model_form_schema(self):
         r"""dict: Schema for generating a model YAML form."""
-        from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-            _valid_types)
+        from yggdrasil import constants
         out = self.get_schema(for_form=True)
-        scalar_types = list(_valid_types.keys())
+        scalar_types = list(constants.VALID_TYPES.keys())
         meta = copy.deepcopy(metaschema._metaschema)
         meta_prop = {
             'subtype': ['1darray', 'ndarray'],
@@ -954,6 +1025,8 @@ def model_form_schema(self):
             if types:
                 out['definitions']['schema']['properties'][k]['options'] = {
                     'dependencies': {'type': types}}
+        for k in ['comm', 'file', 'model']:
+            out['definitions'][k].pop('description', '')
         for k in out['definitions'].keys():
             if k in ['schema', 'simpleTypes']:
                 continue
@@ -964,53 +1037,60 @@ def model_form_schema(self):
             for p, v in list(out['definitions'][k]['properties'].items()):
                 if v.get('description', '').startswith('[DEPRECATED]'):
                     out['definitions'][k]['properties'].pop(p)
-        for x in ['comm', 'file']:
-            for k in ['send_converter', 'recv_converter']:
-                out['definitions'][x]['properties'][k].pop('oneOf', None)
-                out['definitions'][x]['properties'][k].update(
-                    type='array', items={"$ref": "#/definitions/transform"})
-        for x in ['file']:
-            for k in ['serializer']:
-                out['definitions'][x]['properties'][k].pop('oneOf', None)
-                out['definitions'][x]['properties'][k].update(
-                    {"$ref": "#/definitions/serializer"})
+        # Process based on model_form_schema_props
         prop = self.model_form_schema_props
-        for k in ['inputs', 'outputs']:
-            out['definitions']['model']['properties'][k].pop('default', None)
-            desc = out['definitions']['model']['properties'][k]['description'].split(
-                ' A full description')[0]
-            out['definitions']['model']['properties'][k]['description'] = desc
-        out['definitions']['model']['properties']['args']['minItems'] = 1
-        for k, rlist in prop['remove'].items():
-            for p in rlist:
+
+        def adjust_definitions(k):
+            # Remove
+            for p in prop['remove'].get(k, []):
                 out['definitions'][k]['properties'].pop(p, None)
-        for k, rdict in prop['replace'].items():
-            for r in rdict.keys():
-                if 'description' in out['definitions'][k]['properties'][r]:
-                    rdict[r]['description'] = (
+                if p in out['definitions'][k].get('required', []):
+                    out['definitions'][k]['required'].remove(p)
+            # Replace
+            for r, v in prop['replace'].get(k, {}).items():
+                if 'description' in out['definitions'][k]['properties'].get(r, {}):
+                    v['description'] = (
                         out['definitions'][k]['properties'][r]['description'])
-            out['definitions'][k]['properties'].update(rdict)
-        for k, rlist in prop['required'].items():
+                out['definitions'][k]['properties'][r] = v
+            # Required
             out['definitions'][k].setdefault('required', [])
-            for p in rlist:
+            for p in prop['required'].get(k, []):
                 if p not in out['definitions'][k]['required']:
                     out['definitions'][k]['required'].append(p)
-        # for k, adict in prop['add'].items():
-        #     out['definitions'][k]['properties'].update(adict)
-        for k, rlist in prop['order'].items():
-            for i, p in enumerate(rlist):
+            # Update
+            for p, new in prop['update'].get(k, {}).items():
+                out['definitions'][k]['properties'][p].update(new)
+            # Add
+            # out['definitions'][k]['properties'].update(
+            #     prop['add'].get(k, {}))
+            # Order
+            for i, p in enumerate(prop['order'].get(k, [])):
                 out['definitions'][k]['properties'][p]['propertyOrder'] = i
-        out.update(out['definitions'].pop('model'))
-        out['definitions'].pop('connection')
+
+        # Update definitions
+        for k in ['model', 'comm', 'file']:
+            adjust_definitions(k)
+        for k in ['icomm', 'ocomm']:
+            out['definitions'][k] = copy.deepcopy(out['definitions']['comm'])
+            adjust_definitions(k)
+        out['definitions']['icomm']['oneOf'] = [
+            {'title': 'default file',
+             'required': ['default_file'],
+             'not': {'required': ['default_value']}},
+            {'title': 'default value',
+             'required': ['default_value'],
+             'not': {'required': ['default_file']}}]
+        # Adjust formating
         for x in [out] + list(out['definitions'].values()):
             for p, v in x.get('properties', {}).items():
                 if v.get("type", None) == "boolean":
                     v.setdefault("format", "checkbox")
+        # Isolate model
+        out.update(out['definitions'].pop('model'))
+        out['definitions'].pop('connection')
         out.update(
             title='Model YAML Schema',
             description='Schema for yggdrasil model YAML input files.')
-        out['definitions']['comm']['properties']['default_file'] = {
-            '$ref': '#/definitions/file'}
         out = convert_extended2base(out)
         return out
 
@@ -1031,6 +1111,9 @@ def __getitem__(self, k):
     def keys(self):
         return self._storage.keys()
 
+    def items(self):
+        return self._storage.items()
+
     def __eq__(self, other):
         if not hasattr(other, 'schema'):
             return False
@@ -1097,12 +1180,12 @@ def validate_model_submission(self, obj, **kwargs):
 
         Args:
             obj (object): Object to validate.
-            **kwargs: Additional keyword arguments are passed to
-                yggdrasil.metaschema.validate_instance.
+            **kwargs: Additional keyword arguments are ignored.
 
         """
-        return metaschema.validate_instance(obj, self.model_form_schema,
-                                            **kwargs)
+        import jsonschema
+        jsonschema.validate(obj, self.model_form_schema)
+        return obj
 
     def validate_component(self, comp_name, obj, **kwargs):
         r"""Validate an object against a specific component.
diff --git a/yggdrasil/serialize/AsciiMapSerialize.py b/yggdrasil/serialize/AsciiMapSerialize.py
index 581d3b6fb..79482eb27 100644
--- a/yggdrasil/serialize/AsciiMapSerialize.py
+++ b/yggdrasil/serialize/AsciiMapSerialize.py
@@ -1,6 +1,5 @@
 import json
-from yggdrasil import tools
-from yggdrasil.serialize import _default_delimiter_str
+from yggdrasil import tools, constants
 from yggdrasil.serialize.SerializeBase import SerializeBase
 from yggdrasil.metaschema.encoder import JSONReadableEncoder
 
@@ -21,7 +20,7 @@ class AsciiMapSerialize(SerializeBase):
                                    'values.')
     _schema_properties = {
         'delimiter': {'type': 'string',
-                      'default': _default_delimiter_str}}
+                      'default': constants.DEFAULT_DELIMITER_STR}}
     _attr_conv = SerializeBase._attr_conv  # + ['delimiter']
     default_datatype = {'type': 'object'}
     concats_as_str = False
diff --git a/yggdrasil/serialize/AsciiTableSerialize.py b/yggdrasil/serialize/AsciiTableSerialize.py
index 0ce723dc9..9070eecfd 100644
--- a/yggdrasil/serialize/AsciiTableSerialize.py
+++ b/yggdrasil/serialize/AsciiTableSerialize.py
@@ -1,8 +1,6 @@
-from yggdrasil import units, serialize, tools
-from yggdrasil.serialize import _default_delimiter_str
+from yggdrasil import units, serialize, tools, constants
 from yggdrasil.serialize.DefaultSerialize import DefaultSerialize
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    definition2dtype, data2dtype)
+from yggdrasil.metaschema import definition2dtype, data2dtype
 
 
 class AsciiTableSerialize(DefaultSerialize):
@@ -50,7 +48,8 @@ class AsciiTableSerialize(DefaultSerialize):
         'field_names': {'type': 'array', 'items': {'type': 'string'}},
         'field_units': {'type': 'array', 'items': {'type': 'string'}},
         'as_array': {'type': 'boolean', 'default': False},
-        'delimiter': {'type': 'string', 'default': _default_delimiter_str},
+        'delimiter': {'type': 'string',
+                      'default': constants.DEFAULT_DELIMITER_STR},
         'use_astropy': {'type': 'boolean', 'default': False}}
     _attr_conv = DefaultSerialize._attr_conv + ['format_str', 'delimiter']
     has_header = True
diff --git a/yggdrasil/serialize/PlySerialize.py b/yggdrasil/serialize/PlySerialize.py
index 24a315e2a..4b3dcc623 100644
--- a/yggdrasil/serialize/PlySerialize.py
+++ b/yggdrasil/serialize/PlySerialize.py
@@ -1,4 +1,4 @@
-from yggdrasil.serialize import _default_newline_str
+from yggdrasil import constants
 from yggdrasil.serialize.SerializeBase import SerializeBase
 from yggdrasil.metaschema.datatypes.PlyMetaschemaType import PlyDict
 
@@ -25,7 +25,7 @@ class PlySerialize(SerializeBase):
     _schema_subtype_description = ('Serialize 3D structures using Ply format.')
     _schema_properties = {
         'newline': {'type': 'string',
-                    'default': _default_newline_str}}
+                    'default': constants.DEFAULT_NEWLINE_STR}}
     default_datatype = {'type': 'ply'}
     concats_as_str = False
 
diff --git a/yggdrasil/serialize/SerializeBase.py b/yggdrasil/serialize/SerializeBase.py
index 27bb76346..0387d9cfe 100644
--- a/yggdrasil/serialize/SerializeBase.py
+++ b/yggdrasil/serialize/SerializeBase.py
@@ -3,15 +3,11 @@
 import pprint
 import numpy as np
 import warnings
-from yggdrasil import tools, units, serialize
+from yggdrasil import tools, units, serialize, constants
 from yggdrasil.metaschema.datatypes import (
-    guess_type_from_obj, get_type_from_def, get_type_class, compare_schema,
-    type2numpy)
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    _flexible_types)
+    guess_type_from_obj, get_type_from_def, get_type_class, compare_schema)
+from yggdrasil.metaschema import type2numpy
 from yggdrasil.metaschema.datatypes.MetaschemaType import MetaschemaType
-from yggdrasil.metaschema.datatypes.ArrayMetaschemaType import (
-    OneDArrayMetaschemaType)
 
 
 class SerializeBase(tools.YggClass):
@@ -59,9 +55,9 @@ class SerializeBase(tools.YggClass):
                      'default': 'default',
                      'description': ('Serializer type.')},
         'newline': {'type': 'string',
-                    'default': serialize._default_newline_str},
+                    'default': constants.DEFAULT_NEWLINE_STR},
         'comment': {'type': 'string',
-                    'default': serialize._default_comment_str},
+                    'default': constants.DEFAULT_COMMENT_STR},
         'datatype': {'type': 'schema'}}
     _oldstyle_kws = ['format_str', 'field_names', 'field_units', 'as_array']
     _attr_conv = ['newline', 'comment']
@@ -570,6 +566,8 @@ def update_typedef_from_oldstyle(self, typedef):
                 continue
             # Key specific changes to type
             if k == 'format_str':
+                from yggdrasil.metaschema.datatypes.ArrayMetaschemaType import (
+                    OneDArrayMetaschemaType)
                 v = tools.bytes2str(v)
                 fmts = serialize.extract_formats(v)
                 if 'type' in typedef:
@@ -593,7 +591,7 @@ def update_typedef_from_oldstyle(self, typedef):
                         itype['type'] = '1darray'
                     else:
                         itype['type'] = itype.pop('subtype')
-                        if (((itype['type'] in _flexible_types)
+                        if (((itype['type'] in constants.FLEXIBLE_TYPES)
                              and ('precision' in itype))):
                             del itype['precision']
                     typedef['items'].append(itype)
@@ -689,7 +687,7 @@ def serialize(self, args, header_kwargs=None, add_serializer_info=False,
         """
         if header_kwargs is None:
             header_kwargs = {}
-        if isinstance(args, bytes) and (args == tools.YGG_MSG_EOF):
+        if isinstance(args, bytes) and (args == constants.YGG_MSG_EOF):
             header_kwargs['raw'] = True
         self.initialize_from_message(args, **header_kwargs)
         metadata = {'no_metadata': no_metadata,
diff --git a/yggdrasil/serialize/__init__.py b/yggdrasil/serialize/__init__.py
index 54aecd109..8b5a22670 100644
--- a/yggdrasil/serialize/__init__.py
+++ b/yggdrasil/serialize/__init__.py
@@ -3,7 +3,7 @@
 import numpy as np
 import pandas
 import io as sio
-from yggdrasil import platform, units, scanf, tools
+from yggdrasil import platform, units, scanf, tools, constants
 try:
     from astropy.io import ascii as apy_ascii
     from astropy.table import Table as apy_Table
@@ -15,16 +15,6 @@
     _use_astropy = False
 
 
-_fmt_char = b'%'
-_default_comment = b'# '
-_default_delimiter = b'\t'
-_default_newline = b'\n'
-_fmt_char_str = _fmt_char.decode("utf-8")
-_default_comment_str = _default_comment.decode("utf-8")
-_default_delimiter_str = _default_delimiter.decode("utf-8")
-_default_newline_str = _default_newline.decode("utf-8")
-
-
 def extract_formats(fmt_str):
     r"""Locate format codes within a format string.
 
@@ -601,7 +591,7 @@ def format2table(fmt_str):
         out['newline'] = fmt_rem
     seps = set(seps)
     if len(seps) == 0:
-        out['delimiter'] = _default_delimiter
+        out['delimiter'] = constants.DEFAULT_DELIMITER
     elif len(seps) == 1:
         out['delimiter'] = list(seps)[0]
     elif len(seps) > 1:
@@ -619,22 +609,22 @@ def table2format(fmts=[], delimiter=None, newline=None, comment=None):
         fmts (list, optional): List of format codes for each column. Defaults to
             [].
         delimiter (bytes, optional): String used to separate columns. Defaults
-            to _default_delimiter.
+            to constants.DEFAULT_DELIMITER.
         newline (bytes, optional): String used to indicate the end of a table
-            line. Defaults to _default_newline.
+            line. Defaults to constants.DEFAULT_NEWLINE.
         comment (bytes, optional): String that should be prepended to the format
-            string to indicate a comment. Defaults to _default_comment.
+            string to indicate a comment. Defaults to constants.DEFAULT_COMMENT.
 
     Returns:
         str, bytes: Table format string.
 
     """
     if delimiter is None:
-        delimiter = _default_delimiter
+        delimiter = constants.DEFAULT_DELIMITER
     if newline is None:
-        newline = _default_newline
+        newline = constants.DEFAULT_NEWLINE
     if comment is None:
-        comment = _default_comment
+        comment = constants.DEFAULT_COMMENT
     if isinstance(fmts, np.dtype):
         fmts = nptype2cformat(fmts)
     bytes_fmts = tools.str2bytes(fmts, recurse=True)
@@ -891,13 +881,13 @@ def format_header(format_str=None, dtype=None,
             specified, the formats will not be part of the header.
         comment (bytes, optional): String that should be used to comment the
             header lines. If not provided and not in format_str, defaults to
-            _default_comment.
+            constants.DEFAULT_COMMENT.
         delimiter (bytes, optional): String that should be used to separate
             columns. If not provided and not in format_str, defaults to
-            _default_delimiter.
+            constants.DEFAULT_DELIMITER.
         newline (bytes, optional): String that should be used to end lines in
             the table. If not provided and not in format_str, defaults to
-            _default_newline.
+            constants.DEFAULT_NEWLINE.
         field_names (list, optional): List of field names that should be
             included in the header. If not provided and dtype is None, names
             will not be included in the header.
@@ -930,11 +920,11 @@ def format_header(format_str=None, dtype=None,
         if field_names is None:
             field_names = [n.encode("utf-8") for n in dtype.names]
     if delimiter is None:
-        delimiter = _default_delimiter
+        delimiter = constants.DEFAULT_DELIMITER
     if comment is None:
-        comment = _default_comment
+        comment = constants.DEFAULT_COMMENT
     if newline is None:
-        newline = _default_newline
+        newline = constants.DEFAULT_NEWLINE
     # Get count of fields
     if fmts is not None:
         nfld = len(fmts)
@@ -955,8 +945,8 @@ def format_header(format_str=None, dtype=None,
     return out
 
 
-def discover_header(fd, serializer, newline=_default_newline,
-                    comment=_default_comment, delimiter=None,
+def discover_header(fd, serializer, newline=constants.DEFAULT_NEWLINE,
+                    comment=constants.DEFAULT_COMMENT, delimiter=None,
                     lineno_format=None, lineno_names=None, lineno_units=None,
                     use_astropy=False):
     r"""Discover ASCII table header info from a file.
@@ -966,13 +956,13 @@ def discover_header(fd, serializer, newline=_default_newline,
         serializer (DefaultSerialize): Serializer that should be updated with
             header information.
         newline (str, optional): Newline character that should be used to split
-            header if it is not already a list. Defaults to _default_newline.
+            header if it is not already a list. Defaults to constants.DEFAULT_NEWLINE.
         comment (bytes, optional): String that should be used to mark the
             header lines. If not provided and not in format_str, defaults to
-            _default_comment.
+            constants.DEFAULT_COMMENT.
         delimiter (bytes, optional): String that should be used to separate
             columns. If not provided and not in format_str, defaults to
-            _default_delimiter.
+            constants.DEFAULT_DELIMITER.
         lineno_format (int, optional): Line number where formats are located.
             If not provided, an attempt will be made to locate one.
         lineno_names (int, optional): Line number where field names are located.
@@ -1039,14 +1029,14 @@ def discover_header(fd, serializer, newline=_default_newline,
     fd.seek(prev_pos + header_size)
 
 
-def parse_header(header, newline=_default_newline, lineno_format=None,
+def parse_header(header, newline=constants.DEFAULT_NEWLINE, lineno_format=None,
                  lineno_names=None, lineno_units=None):
     r"""Parse an ASCII table header to get information about the table.
 
     Args:
         header (list, str): Header lines that should be parsed.
         newline (str, optional): Newline character that should be used to split
-            header if it is not already a list. Defaults to _default_newline.
+            header if it is not already a list. Defaults to constants.DEFAULT_NEWLINE.
         lineno_format (int, optional): Line number where formats are located.
             If not provided, an attempt will be made to locate one.
         lineno_names (int, optional): Line number where field names are located.
@@ -1077,7 +1067,8 @@ def parse_header(header, newline=_default_newline, lineno_format=None,
         out['format_str'] = header[lineno_format].split(info['comment'])[-1]
     else:
         ncol = 0
-        out.update(delimiter=_default_delimiter, comment=_default_comment,
+        out.update(delimiter=constants.DEFAULT_DELIMITER,
+                   comment=constants.DEFAULT_COMMENT,
                    fmts=[])
     out.setdefault('newline', newline)
     # Use explicit lines for names & units
diff --git a/yggdrasil/services.py b/yggdrasil/services.py
index 85e5e2ceb..3c6cf2982 100644
--- a/yggdrasil/services.py
+++ b/yggdrasil/services.py
@@ -5,6 +5,7 @@
 import json
 import traceback
 import yaml
+import glob
 import pprint
 import functools
 import threading
@@ -17,6 +18,7 @@
 
 
 _service_host_env = 'YGGDRASIL_SERVICE_HOST_URL'
+_service_repo_dir = 'YGGDRASIL_SERVICE_REPO_DIR'
 _default_service_type = ygg_cfg.get('services', 'default_type', 'flask')
 _default_commtype = ygg_cfg.get('services', 'default_comm', None)
 _default_address = ygg_cfg.get('services', 'address', None)
@@ -127,7 +129,7 @@ def set_log_level(self, log_level):
         logging.basicConfig(level=log_level)
 
     def start_server(self, remote_url=None, with_coverage=False,
-                     log_level=None):
+                     log_level=None, model_repository=None):
         r"""Start the server.
 
         Args:
@@ -140,12 +142,18 @@ def start_server(self, remote_url=None, with_coverage=False,
                 with coverage. Defaults to False.
             log_level (int, optional): Level of log messages that should be
                 printed. Defaults to None and is ignored.
+            model_repository (str, optional): URL of directory in a Git
+                repository containing YAMLs that should be added to the model
+                registry. Defaults to None and is ignored.
 
         """
         if remote_url is None:
             remote_url = os.environ.get(_service_host_env, None)
         if remote_url is None:
             remote_url = self.address
+        if model_repository is not None:
+            repo_dir = self.registry.add_from_repository(model_repository)
+            os.environ.setdefault(_service_repo_dir, repo_dir)
         os.environ.setdefault(_service_host_env, remote_url)
         if log_level is not None:
             self.set_log_level(log_level)
@@ -1078,6 +1086,38 @@ def remove(self, name):
             registry.pop(k)
         self.save(registry)
 
+    def add_from_repository(self, model_repository, directory=None):
+        r"""Add integration services to the registry from a repository of
+        model YAMLs.
+
+        Args:
+            model_repository (str): URL of directory in a Git repository
+                containing YAMLs that should be added to the model registry.
+            directory (str, optional): Directory where services from the
+                model_repository should be cloned. Defaults to
+                '~/.yggdrasil_service'.
+
+        Returns:
+            str: The directory where the repositories were cloned.
+
+        """
+        from yggdrasil.yamlfile import clone_github_repo, prep_yaml
+        if directory is None:
+            directory = os.path.expanduser(
+                os.path.join('~', '.yggdrasil_services'))
+        yaml_dir = clone_github_repo(model_repository,
+                                     local_directory=directory)
+        yaml_files = (glob.glob(os.path.join(yaml_dir, '*.yaml'))
+                      + glob.glob(os.path.join(yaml_dir, '*.yml')))
+        for x in yaml_files:
+            # Calling prep_yaml allows the model repositories to be cloned
+            # in advance to circumvent th hold place on git cloning on the
+            # service manager (these models are assumed to be vetted so
+            # they do not pose a security risk).
+            prep_yaml(x, directory_for_clones=directory)
+            self.add(os.path.splitext(os.path.basename(x))[0], x)
+        return directory
+
     def add(self, name, yamls=None, **kwargs):
         r"""Add an integration service to the registry.
 
@@ -1102,7 +1142,7 @@ def add(self, name, yamls=None, **kwargs):
             assert(yamls)
             collection = {name: dict(kwargs, name=name, yamls=yamls)}
         for k, v in collection.items():
-            if k in registry:
+            if (k in registry) and (registry[k] != v):
                 old = pprint.pformat(registry[k])
                 new = pprint.pformat(v)
                 raise ValueError(f"There is an registry integration "
@@ -1119,11 +1159,21 @@ def validate_model_submission(fname):
     the yggdrasil model repository.
 
     Args:
-        fname (str): YAML file to validate.
+        fname (str): YAML file to validate or directory in which to check
+            each of the YAML files.
 
     """
-    import glob
     from yggdrasil import yamlfile, runner
+    if isinstance(fname, list):
+        for x in fname:
+            validate_model_submission(x)
+        return
+    elif os.path.isdir(fname):
+        files = sorted(glob.glob(os.path.join(fname, '*.yml'))
+                       + glob.glob(os.path.join(fname, '*.yaml')))
+        for x in files:
+            validate_model_submission(x)
+        return
     # 1-2. YAML syntax and schema
     yml = yamlfile.parse_yaml(fname, model_submission=True)
     # 3a. LICENSE
@@ -1135,5 +1185,5 @@ def validate_model_submission(fname):
             break
     else:
         raise RuntimeError("Model repository does not contain a LICENSE file.")
-    # 4. Run
-    runner.run(fname)
+    # 4. Run & validate
+    runner.run(fname, validate=True)
diff --git a/yggdrasil/tools.py b/yggdrasil/tools.py
index eb4ff4f01..49d8a66c0 100644
--- a/yggdrasil/tools.py
+++ b/yggdrasil/tools.py
@@ -6,6 +6,7 @@
 import os
 import re
 import sys
+import glob
 import sysconfig
 try:
     from distutils import sysconfig as distutils_sysconfig
@@ -27,8 +28,6 @@
 
 
 logger = logging.getLogger(__name__)
-YGG_MSG_EOF = b'EOF!!!'
-YGG_MSG_BUF = 1024 * 2
 
 
 _stack_in_log = False
@@ -1253,6 +1252,53 @@ def print_encoded(msg, *args, **kwargs):
             print(str2bytes(msg), *args, **kwargs)
 
 
+def import_all_modules(base=None, exclude=None, do_first=None):
+    r"""Import all yggdrasil modules.
+
+    Args:
+        base (str, optional): Base module to start from. Defaults to
+            'yggdrasil'.
+        exclude (list, optional): Modules that should not be imported.
+            Defaults to empty list.
+        do_first (list, optional): Modules that should be import first.
+            Defaults to empty list.
+
+    """
+    if base is None:
+        base = 'yggdrasil'
+    if exclude is None:
+        exclude = []
+    if do_first is None:
+        do_first = []
+    assert(base.startswith('yggdrasil'))
+    for x in do_first:
+        import_all_modules(x, exclude=exclude)
+    exclude = exclude + do_first
+    directory = os.path.dirname(__file__)
+    parts = base.split('.')[1:]
+    if parts:
+        directory = os.path.join(directory, *parts)
+    if not os.path.isfile(os.path.join(directory, '__init__.py')):
+        return
+    if (base in exclude) or base.endswith('tests'):
+        return
+    importlib.import_module(base)
+    for x in sorted(glob.glob(os.path.join(directory, '*.py'))):
+        x_base = os.path.basename(x)
+        if x_base.startswith('__') and x_base.endswith('__.py'):
+            continue
+        x_mod = f"{base}.{os.path.splitext(os.path.basename(x))[0]}"
+        if x_mod in exclude:
+            continue
+        importlib.import_module(x_mod)
+    for x in sorted(glob.glob(os.path.join(directory, '*', ''))):
+        if x.startswith('__') and x.endswith('__'):  # pragma: debug
+            continue
+        next_module = os.path.basename(os.path.dirname(x))
+        import_all_modules(f"{base}.{next_module}",
+                           exclude=exclude + do_first)
+
+
 class TimeOut(object):
     r"""Class for checking if a period of time has been elapsed.
 
diff --git a/yggdrasil/units.py b/yggdrasil/units.py
index 2a67b5c32..b69a7bc5a 100644
--- a/yggdrasil/units.py
+++ b/yggdrasil/units.py
@@ -2,12 +2,22 @@
 import numpy as np
 import pandas as pd
 import unyt
-from yggdrasil import tools
+from collections import OrderedDict
+from yggdrasil import tools, constants
 _unit_quantity = unyt.array.unyt_quantity
 _unit_array = unyt.array.unyt_array
 _ureg_unyt = None
 
 
+PYTHON_SCALARS_WITH_UNITS = OrderedDict([
+    (k, tuple(list(v) + [_unit_quantity]))
+    for k, v in constants.PYTHON_SCALARS.items()])
+ALL_PYTHON_ARRAYS_WITH_UNITS = tuple(
+    list(constants.ALL_PYTHON_ARRAYS) + [_unit_array])
+ALL_PYTHON_SCALARS_WITH_UNITS = tuple(
+    list(constants.ALL_PYTHON_SCALARS) + [_unit_quantity])
+
+
 def get_ureg():
     r"""Get the unit registry."""
     global _ureg_unyt
diff --git a/yggdrasil/yamlfile.py b/yggdrasil/yamlfile.py
index 565616369..c6b3b6048 100644
--- a/yggdrasil/yamlfile.py
+++ b/yggdrasil/yamlfile.py
@@ -36,22 +36,27 @@ def no_duplicates_constructor(loader, node, deep=False):
                         no_duplicates_constructor)
 
 
-def clone_github_repo(fname):
+def clone_github_repo(fname, commit=None, local_directory=None):
     r"""Clone a GitHub repository, returning the path to the local copy of the
     file pointed to by the URL if there is one.
 
     Args:
         fname (str): URL to a GitHub repository or a file in a GitHub
             repository that should be cloned.
-
+        commit (str, optional): Commit that should be checked out. Defaults
+            to None and the HEAD of the default branch is used.
+        local_directory (str, optional): Local directory that the file should
+            be cloned into. Defaults to None and the current working directory
+            will be used.
 
     Returns:
         str: Path to the local copy of the repository or file in the
             repository.
 
     """
-    
-    from yggdrasil.services import _service_host_env
+    from yggdrasil.services import _service_host_env, _service_repo_dir
+    if local_directory is None:
+        local_directory = os.environ.get(_service_repo_dir, os.getcwd())
     # make sure we start with a full url
     if 'http' not in fname:
         url = 'http://github.com/' + fname
@@ -67,7 +72,7 @@ def clone_github_repo(fname):
     reponame = splitpath[2]
     # the full path is the file name and location
     # turn the file path into an os based format
-    fname = os.path.join(*splitpath)
+    fname = os.path.join(local_directory, *splitpath)
     # check to see if the file already exists, and clone if it does not
     if not os.path.exists(fname):
         if os.environ.get(_service_host_env, False):
@@ -78,13 +83,16 @@ def clone_github_repo(fname):
         cloneurl = parsed.scheme + '://' + parsed.netloc + '/' + owner + '/' +\
             reponame
         # clone the repo into the appropriate directory
-        repo = git.Repo.clone_from(cloneurl, os.path.join(owner, reponame))
+        repo = git.Repo.clone_from(cloneurl, os.path.join(local_directory,
+                                                          owner, reponame))
+        if commit is not None:
+            repo.git.checkout(commit)
         repo.close()
         # now that it is cloned, just pass the yaml file (and path) onwards
     return os.path.realpath(fname)
 
 
-def load_yaml(fname):
+def load_yaml(fname, yaml_param=None, directory_for_clones=None):
     r"""Parse a yaml file defining a run.
 
     Args:
@@ -96,6 +104,12 @@ def load_yaml(fname):
             YAML file (the server is assumed to be github.com if not given)
             (foo/bar/yam/interesting.yaml will be interpreted as
             http://github.com/foo/bar/yam/interesting.yml).
+        yaml_param (dict, optional): Parameters that should be used in
+            mustache formatting of YAML files. Defaults to None and is
+            ignored.
+        directory_for_clones (str, optional): Directory that git repositories
+            should be cloned into. Defaults to None and the current working
+            directory will be used.
 
     Returns:
         dict: Contents of yaml file.
@@ -109,7 +123,8 @@ def load_yaml(fname):
     elif isinstance(fname, str):
         # pull foreign file
         if fname.startswith('git:'):
-            fname = clone_github_repo(fname[4:])
+            fname = clone_github_repo(fname[4:],
+                                      local_directory=directory_for_clones)
         fname = os.path.realpath(fname)
         if not os.path.isfile(fname):
             raise IOError("Unable locate yaml file %s" % fname)
@@ -122,9 +137,11 @@ def load_yaml(fname):
         else:
             fname = os.path.join(os.getcwd(), 'stream')
     # Mustache replace vars
+    if yaml_param is None:
+        yaml_param = {}
     yamlparsed = fd.read()
     yamlparsed = chevron.render(
-        sio.StringIO(yamlparsed).getvalue(), dict(os.environ))
+        sio.StringIO(yamlparsed).getvalue(), dict(os.environ, **yaml_param))
     if fname.endswith('.json'):
         yamlparsed = json.loads(yamlparsed)
     else:
@@ -137,7 +154,7 @@ def load_yaml(fname):
     return yamlparsed
 
 
-def prep_yaml(files):
+def prep_yaml(files, yaml_param=None, directory_for_clones=None):
     r"""Prepare yaml to be parsed by jsonschema including covering backwards
     compatible options.
 
@@ -145,6 +162,12 @@ def prep_yaml(files):
         files (str, list): Either the path to a single yaml file or a list of
             yaml files. Entries can also be opened file descriptors for files
             containing YAML documents or pre-loaded YAML documents.
+        yaml_param (dict, optional): Parameters that should be used in
+            mustache formatting of YAML files. Defaults to None and is
+            ignored.
+        directory_for_clones (str, optional): Directory that git repositories
+            should be cloned into. Defaults to None and the current working
+            directory will be used.
 
     Returns:
         dict: YAML ready to be parsed using schema.
@@ -154,7 +177,9 @@ def prep_yaml(files):
     # Load each file
     if not isinstance(files, list):
         files = [files]
-    yamls = [load_yaml(f) for f in files]
+    yamls = [load_yaml(f, yaml_param=yaml_param,
+                       directory_for_clones=directory_for_clones)
+             for f in files]
     # Load files pointed to
     for y in yamls:
         if 'include' in y:
@@ -176,7 +201,7 @@ def prep_yaml(files):
                 y['models'].append(y.pop('model'))
         for x in services:
             request = {'action': 'start'}
-            for k in ['name', 'yamls']:
+            for k in ['name', 'yamls', 'yaml_param']:
                 if k in x:
                     request[k] = x.pop(k)
             if 'type' in x:
@@ -195,7 +220,10 @@ def prep_yaml(files):
             for x in yml[k]:
                 if isinstance(x, dict):
                     if (k == 'models') and ('repository_url' in x):
-                        repo_dir = clone_github_repo(x['repository_url'])
+                        repo_dir = clone_github_repo(
+                            x['repository_url'],
+                            commit=x.get('repository_commit', None),
+                            local_directory=directory_for_clones)
                         x.setdefault('working_dir', repo_dir)
                     else:
                         x.setdefault('working_dir', yml['working_dir'])
@@ -209,7 +237,8 @@ def prep_yaml(files):
 
 
 def parse_yaml(files, complete_partial=False, partial_commtype=None,
-               model_only=False, model_submission=False):
+               model_only=False, model_submission=False, yaml_param=None,
+               directory_for_clones=None):
     r"""Parse list of yaml files.
 
     Args:
@@ -227,6 +256,12 @@ def parse_yaml(files, complete_partial=False, partial_commtype=None,
         model_submission (bool, optional): If True, the YAML will be evaluated
             as a submission to the yggdrasil model repository and model_only
             will be set to True. Defaults to False.
+        yaml_param (dict, optional): Parameters that should be used in
+            mustache formatting of YAML files. Defaults to None and is
+            ignored.
+        directory_for_clones (str, optional): Directory that git repositories
+            should be cloned into. Defaults to None and the current working
+            directory will be used.
 
     Raises:
         ValueError: If the yml dictionary is missing a required keyword or
@@ -240,16 +275,15 @@ def parse_yaml(files, complete_partial=False, partial_commtype=None,
     """
     s = get_schema()
     # Parse files using schema
-    yml_prep = prep_yaml(files)
+    yml_prep = prep_yaml(files, yaml_param=yaml_param,
+                         directory_for_clones=directory_for_clones)
     # print('prepped')
     # pprint.pprint(yml_prep)
     if model_submission:
         models = []
         for yml in yml_prep['models']:
             wd = yml.pop('working_dir', None)
-            x = s.validate_model_submission(yml, normalize=True,
-                                            no_defaults=True,
-                                            required_defaults=True)
+            x = s.validate_model_submission(yml)
             if wd:
                 x['working_dir'] = wd
             models.append(x)


Test patch:
diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml
index f369e1603..61172d8bb 100644
--- a/.github/workflows/test-install.yml
+++ b/.github/workflows/test-install.yml
@@ -44,7 +44,7 @@ jobs:
         - macos-latest
         - windows-latest
         python-version:
-        - 3.6
+        - 3.7
         install-method:
         - pip
         test-flags1:
@@ -61,7 +61,7 @@ jobs:
         - true
         include:
         - os: windows-latest
-          python-version: 3.6
+          python-version: 3.7
           install-method: pip
           test-flags1: --long-running --languages cpp
           test-flags2: --long-running --skip-languages c++
@@ -351,7 +351,7 @@ jobs:
         - macos-latest
         - windows-latest
         python-version:
-        - 3.6
+        - 3.7
         install-method:
         - conda
         test-flags1:
@@ -364,7 +364,7 @@ jobs:
         - true
         include:
         - os: ubuntu-latest
-          python-version: 3.7
+          python-version: 3.6
           install-method: conda
           test-flags1: --long-running
           test-flags2: ''
@@ -679,7 +679,7 @@ jobs:
         os:
         - ubuntu-latest
         python-version:
-        - 3.6
+        - 3.7
         install-method:
         - pip
         test-flags1:
@@ -981,7 +981,7 @@ jobs:
         os:
         - ubuntu-latest
         python-version:
-        - 3.6
+        - 3.7
         install-method:
         - conda
         test-flags1:
@@ -1295,7 +1295,7 @@ jobs:
         - macos-latest
         - windows-latest
         python-version:
-        - 3.6
+        - 3.7
         install-method:
         - pip
         test-flags1:
@@ -1672,3 +1672,32 @@ jobs:
       run: python utils/build_docker.py --push
     - name: Build and push executable docker images for the new release
       run: python utils/build_docker.py --push executable
+  schema:
+    name: Update the schema used by the model submission form
+    needs:
+    - test_pip
+    - test_conda
+    - test_rmq_pip
+    - test_rmq_conda
+    - test_stripped
+    - docs
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - name: Set up Python
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - name: Install dependencies
+      run: 'python -m pip install PyGithub
+
+        python utils/setup_test_env.py install pip
+
+        '
+    - name: Verify installation
+      run: python utils/setup_test_env.py verify
+    - name: Update the form
+      run: python utils/update_form_schema.py
diff --git a/utils/setup_test_env.py b/utils/setup_test_env.py
index c16d3e2f2..60d6089e7 100644
--- a/utils/setup_test_env.py
+++ b/utils/setup_test_env.py
@@ -329,7 +329,7 @@ def create_env(method, python, name=None, packages=None, init=_on_ci):
 
     """
     cmds = ["echo Creating test environment using %s..." % method]
-    major, minor = [int(x) for x in python.split('.')]
+    major, minor = [int(x) for x in python.split('.')][:2]
     if name is None:
         name = '%s%s' % (method, python.replace('.', ''))
     if packages is None:
@@ -782,7 +782,7 @@ def install_deps(method, return_commands=False, verbose=False,
         ]
     if fallback_to_conda:
         cmds.append("%s update --all" % CONDA_CMD)
-    if install_opts['R'] and (not fallback_to_conda):
+    if install_opts['R'] and (not fallback_to_conda) and (not only_python):
         # TODO: Test split installation where r-base is installed from
         # conda and the R dependencies are installed from CRAN?
         if _is_linux:
diff --git a/utils/test-install-base.yml b/utils/test-install-base.yml
index 8ee644c21..f25eeb2eb 100644
--- a/utils/test-install-base.yml
+++ b/utils/test-install-base.yml
@@ -38,7 +38,7 @@ jobs:
       max-parallel: 20
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [3.6]
+        python-version: [3.7]
         install-method: [pip]
         test-flags1: [--long-running --test-suite=timing --test-suite=demos --test-suite=top, --test-suite=examples --separate-test='--test-suite=mpi --write-script=run_mpi.sh --with-mpi=3', --test-suite=types --skip-languages c c++, --test-suite=types --languages c cpp]
         test-flags2: [""]
@@ -46,7 +46,7 @@ jobs:
         install-mpi: [true]
         include:
           - os: windows-latest
-            python-version: 3.6
+            python-version: 3.7
             install-method: pip
             test-flags1: --long-running --languages cpp
             test-flags2: --long-running --skip-languages c++
@@ -348,7 +348,7 @@ jobs:
       max-parallel: 20
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [3.6]
+        python-version: [3.7]
         install-method: [conda]
         test-flags1: [--long-running --separate-test='--test-suite=mpi --write-script=run_mpi.sh']
         test-flags2: [--with-examples examples/tests/test_backwards.py]
@@ -356,7 +356,7 @@ jobs:
         install-mpi: [true]
         include:
           - os: ubuntu-latest
-            python-version: 3.7
+            python-version: 3.6
             install-method: conda
             test-flags1: --long-running
             test-flags2: ""
@@ -400,7 +400,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        python-version: [3.6]
+        python-version: [3.7]
         install-method: [pip]
         test-flags1: [--long-running --languages python R matlab]
         test-flags2: [""]
@@ -425,7 +425,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        python-version: [3.6]
+        python-version: [3.7]
         install-method: [conda]
         test-flags1: [--long-running --languages python R matlab]
         test-flags2: [tests/test_services.py --nocapture]
@@ -455,7 +455,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [3.6]
+        python-version: [3.7]
         install-method: [pip]
         test-flags1: [--long-running]
         test-flags2: [""]
@@ -543,3 +543,25 @@ jobs:
       run: python utils/build_docker.py --push
     - name: Build and push executable docker images for the new release
       run: python utils/build_docker.py --push executable
+
+  schema:
+    name: Update the schema used by the model submission form
+    needs: [test_pip, test_conda, test_rmq_pip, test_rmq_conda, test_stripped, docs]
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - name: Set up Python
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - name: Install dependencies
+      run: |
+        python -m pip install PyGithub
+        python utils/setup_test_env.py install pip
+    - name: Verify installation
+      run: python utils/setup_test_env.py verify
+    - name: Update the form
+      run: python utils/update_form_schema.py
diff --git a/yggdrasil/communication/tests/test_ValueComm.py b/yggdrasil/communication/tests/test_ValueComm.py
index cf6a51933..6e5db52cc 100644
--- a/yggdrasil/communication/tests/test_ValueComm.py
+++ b/yggdrasil/communication/tests/test_ValueComm.py
@@ -43,6 +43,9 @@ def test_send_recv(self):
         n_recv = self.testing_options['kwargs']['count']
         msg_recv = self.test_msg
         if self.use_async:
+            from yggdrasil.multitasking import wait_on_function
+            wait_on_function(lambda: self.recv_instance.n_msg_recv > 0,
+                             timeout=self.timeout)
             assert(self.recv_instance.n_msg_recv > 0)
         else:
             self.assert_equal(self.recv_instance.n_msg_recv, n_recv)
diff --git a/yggdrasil/drivers/tests/test_FileOutputDriver.py b/yggdrasil/drivers/tests/test_FileOutputDriver.py
index e5ad80051..1c40e9cf4 100644
--- a/yggdrasil/drivers/tests/test_FileOutputDriver.py
+++ b/yggdrasil/drivers/tests/test_FileOutputDriver.py
@@ -93,9 +93,9 @@ def setup(self):
         super(TestFileOutputDriver, self).setup()
         self.send_file_contents()
         
-    # def run_before_stop(self):
-    #     r"""Commands to run while the instance is running."""
-    #     self.send_file_contents()
+    def run_before_stop(self):
+        r"""Commands to run while the instance is running."""
+        self.instance.wait(1.0)
 
     @property
     def contents_to_read(self):
diff --git a/yggdrasil/drivers/tests/test_ModelDriver.py b/yggdrasil/drivers/tests/test_ModelDriver.py
index 7a8437398..e0c4bb44b 100644
--- a/yggdrasil/drivers/tests/test_ModelDriver.py
+++ b/yggdrasil/drivers/tests/test_ModelDriver.py
@@ -1,3 +1,4 @@
+import pytest
 import os
 import copy
 import unittest
@@ -470,7 +471,30 @@ def test_split_line(self, vals=None):
         for line, kwargs, splits in vals:
             self.assert_equal(
                 self.import_cls.split_line(line, **kwargs), splits)
-    
+
+    def test_install_model_dependencies(self, deps=None):
+        r"""Test install_model_dependencies."""
+        if deps is None:
+            if self.import_cls.language == 'c':
+                deps = [
+                    "cmake",
+                    {"package_manager": "pip", "package": "pyyaml",
+                     "arguments": "-v"},
+                    {"package": "cmake", "arguments": "-v"}]
+                if platform._is_win:  # pragma: windows
+                    from yggdrasil import tools
+                    if not tools.get_conda_prefix():
+                        deps.append({"package_manager": "vcpkg",
+                                     "package": "czmq"})
+                else:
+                    deps.append('doxygen')
+            else:
+                deps = []
+        self.import_cls.install_model_dependencies(deps, always_yes=True)
+        with pytest.raises(NotImplementedError):
+            self.import_cls.install_dependency(
+                'invalid', package_manager='invalid')
+
 
 class TestModelDriverNoStart(TestModelParam, parent.TestDriverNoStart):
     r"""Test runner for basic ModelDriver class."""
diff --git a/yggdrasil/drivers/tests/test_PythonModelDriver.py b/yggdrasil/drivers/tests/test_PythonModelDriver.py
index 13b3ba054..cb567974f 100644
--- a/yggdrasil/drivers/tests/test_PythonModelDriver.py
+++ b/yggdrasil/drivers/tests/test_PythonModelDriver.py
@@ -10,7 +10,14 @@ class TestPythonModelParam(parent.TestInterpretedModelParam):
 class TestPythonModelDriverNoInit(TestPythonModelParam,
                                   parent.TestInterpretedModelDriverNoInit):
     r"""Test runner for PythonModelDriver without init."""
-    pass
+
+    def test_install_model_dependencies(self, deps=None):
+        r"""Test install_model_dependencies."""
+        if deps is None:
+            deps = [{'package': 'numpy', 'arguments': '-v'},
+                    'requests', 'pyyaml']
+        super(TestPythonModelDriverNoInit, self).test_install_model_dependencies(
+            deps=deps)
         
 
 class TestPythonModelDriverNoStart(TestPythonModelParam,
diff --git a/yggdrasil/drivers/tests/test_RModelDriver.py b/yggdrasil/drivers/tests/test_RModelDriver.py
index dca7e5b81..2521a73e9 100644
--- a/yggdrasil/drivers/tests/test_RModelDriver.py
+++ b/yggdrasil/drivers/tests/test_RModelDriver.py
@@ -42,6 +42,14 @@ def test_python2language(self):
         for a, b in test_vars:
             self.assert_equal(self.import_cls.python2language(a), b)
 
+    def test_install_model_dependencies(self, deps=None):
+        r"""Test install_model_dependencies."""
+        if deps is None:
+            deps = ['units', 'zeallot',
+                    {'package': 'units', 'arguments': '-v'}]
+        super(TestRModelDriverNoInit, self).test_install_model_dependencies(
+            deps=deps)
+
 
 class TestRModelDriverNoStart(TestRModelParam,
                               parent.TestInterpretedModelDriverNoStart):
diff --git a/yggdrasil/examples/tests/__init__.py b/yggdrasil/examples/tests/__init__.py
index aa4d6da6c..3b82cba5f 100644
--- a/yggdrasil/examples/tests/__init__.py
+++ b/yggdrasil/examples/tests/__init__.py
@@ -380,7 +380,10 @@ def run_example(self):
             self.runner = runner.get_runner(self.yaml, namespace=self.namespace,
                                             production_run=True,
                                             mpi_tag_start=mpi_tag_start)
-            self.runner.run()
+            try:
+                self.runner.run()
+            except runner.IntegrationError:
+                pass
             self.runner.printStatus()
             self.runner.printStatus(return_str=True)
             if self.mpi_rank != 0:
diff --git a/yggdrasil/examples/tests/test_transforms.py b/yggdrasil/examples/tests/test_transforms.py
index 7f6d00748..a2fb019b9 100644
--- a/yggdrasil/examples/tests/test_transforms.py
+++ b/yggdrasil/examples/tests/test_transforms.py
@@ -3,7 +3,8 @@
 import numpy as np
 from yggdrasil import tools, units
 from yggdrasil.tests import assert_equal
-from yggdrasil.components import import_component, create_component
+from yggdrasil.components import (
+    ComponentError, import_component, create_component)
 from yggdrasil.languages import get_language_ext
 from yggdrasil.examples import _example_dir
 from yggdrasil.examples.tests import ExampleTstBase
@@ -78,7 +79,7 @@ def check_received_data(cls, transform, x_recv):
         """
         try:
             t = create_component('transform', subtype=transform)
-        except ValueError:
+        except ComponentError:
             def t(x):
                 return x
         x_sent = t(cls.get_test_data(transform))
diff --git a/yggdrasil/languages/Python/test_YggInterface.py b/yggdrasil/languages/Python/test_YggInterface.py
index f0faea996..5e5668e01 100644
--- a/yggdrasil/languages/Python/test_YggInterface.py
+++ b/yggdrasil/languages/Python/test_YggInterface.py
@@ -3,8 +3,8 @@
 import flaky
 from yggdrasil.communication import get_comm
 from yggdrasil.interface import YggInterface
-from yggdrasil.tools import (
-    YGG_MSG_EOF, get_YGG_MSG_MAX, YGG_MSG_BUF, is_lang_installed)
+from yggdrasil import constants
+from yggdrasil.tools import get_YGG_MSG_MAX, is_lang_installed
 from yggdrasil.components import import_component
 from yggdrasil.drivers import ConnectionDriver
 from yggdrasil.tests import YggTestClassInfo, assert_equal, assert_raises
@@ -43,12 +43,12 @@ def test_maxMsgSize():
 
 def test_eof_msg():
     r"""Test eof message signal."""
-    assert_equal(YggInterface.eof_msg(), YGG_MSG_EOF)
+    assert_equal(YggInterface.eof_msg(), constants.YGG_MSG_EOF)
 
 
 def test_bufMsgSize():
     r"""Test buf message size."""
-    assert_equal(YggInterface.bufMsgSize(), YGG_MSG_BUF)
+    assert_equal(YggInterface.bufMsgSize(), constants.YGG_MSG_BUF)
 
 
 def test_init():
@@ -100,7 +100,7 @@ def do_send_recv(language='python', fmt='%f\\n%d', msg=[float(1.0), np.int32(2)]
             o.close(linger=True)
             # Input
             assert_equal(i.recv(), (True, converter(msg)))
-            assert_equal(i.recv(), (False, converter(YGG_MSG_EOF)))
+            assert_equal(i.recv(), (False, converter(constants.YGG_MSG_EOF)))
     finally:
         iodrv.terminate()
 
@@ -122,7 +122,7 @@ def test_YggInit_backwards():
 def test_YggInit_variables():
     r"""Test Matlab interface for variables."""
     assert_equal(YggInterface.YggInit('YGG_MSG_MAX'), YGG_MSG_MAX)
-    assert_equal(YggInterface.YggInit('YGG_MSG_EOF'), YGG_MSG_EOF)
+    assert_equal(YggInterface.YggInit('YGG_MSG_EOF'), constants.YGG_MSG_EOF)
     assert_equal(YggInterface.YggInit('YGG_MSG_EOF'),
                  YggInterface.YggInit('CIS_MSG_EOF'))
     assert_equal(YggInterface.YggInit('YGG_MSG_EOF'),
diff --git a/yggdrasil/metaschema/datatypes/tests/__init__.py b/yggdrasil/metaschema/datatypes/tests/__init__.py
index cfc336602..5101318f0 100644
--- a/yggdrasil/metaschema/datatypes/tests/__init__.py
+++ b/yggdrasil/metaschema/datatypes/tests/__init__.py
@@ -1,5 +1,5 @@
 from yggdrasil.tests import assert_raises, assert_equal
-from yggdrasil.metaschema import datatypes
+from yggdrasil.metaschema import datatypes, MetaschemaTypeError
 from yggdrasil.metaschema.tests import _valid_objects
 from yggdrasil.metaschema.datatypes.ScalarMetaschemaType import (
     ScalarMetaschemaType)
@@ -53,7 +53,7 @@ def test_guess_type_from_obj():
     for t, x in _valid_objects.items():
         assert_equal(datatypes.guess_type_from_obj(x).name, t)
     for x in invalid_objects:
-        assert_raises(datatypes.MetaschemaTypeError,
+        assert_raises(MetaschemaTypeError,
                       datatypes.guess_type_from_obj, x)
 
 
diff --git a/yggdrasil/metaschema/datatypes/tests/test_MetaschemaType.py b/yggdrasil/metaschema/datatypes/tests/test_MetaschemaType.py
index 9ecbc15ac..5cd2a0ff4 100644
--- a/yggdrasil/metaschema/datatypes/tests/test_MetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/tests/test_MetaschemaType.py
@@ -3,7 +3,8 @@
 import copy
 import pprint
 import jsonschema
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError, YGG_MSG_HEAD
+from yggdrasil import constants
+from yggdrasil.metaschema import MetaschemaTypeError
 from yggdrasil.tests import YggTestClassInfo, assert_equal
 
 
@@ -301,7 +302,8 @@ def test_deserialize_empty(self):
         self.assert_result_equal(out[0], self.instance._empty_msg)
         self.assert_equal(out[1], dict(size=0, incomplete=False))
         # Empty metadata and message
-        out = self.instance.deserialize((2 * YGG_MSG_HEAD) + self._empty_msg)
+        out = self.instance.deserialize((2 * constants.YGG_MSG_HEAD)
+                                        + self._empty_msg)
         self.assert_result_equal(out[0], self.instance._empty_msg)
         self.assert_equal(out[1], dict(size=0, incomplete=False))
 
diff --git a/yggdrasil/metaschema/datatypes/tests/test_MultiMetaschemaType.py b/yggdrasil/metaschema/datatypes/tests/test_MultiMetaschemaType.py
index 583e8f1c5..10bc7454c 100644
--- a/yggdrasil/metaschema/datatypes/tests/test_MultiMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/tests/test_MultiMetaschemaType.py
@@ -1,4 +1,4 @@
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError
+from yggdrasil.metaschema import MetaschemaTypeError
 from yggdrasil.metaschema.datatypes.tests import test_MetaschemaType as parent
 from yggdrasil.metaschema.datatypes.MultiMetaschemaType import (
     create_multitype_class)
diff --git a/yggdrasil/metaschema/datatypes/tests/test_ScalarMetaschemaType.py b/yggdrasil/metaschema/datatypes/tests/test_ScalarMetaschemaType.py
index 48314f79d..b997fbfcf 100644
--- a/yggdrasil/metaschema/datatypes/tests/test_ScalarMetaschemaType.py
+++ b/yggdrasil/metaschema/datatypes/tests/test_ScalarMetaschemaType.py
@@ -1,9 +1,7 @@
 import copy
 import numpy as np
-from yggdrasil import units, platform
+from yggdrasil import units, platform, constants
 from yggdrasil.metaschema.datatypes.tests import test_MetaschemaType as parent
-from yggdrasil.metaschema.properties.ScalarMetaschemaProperties import (
-    _valid_types)
 
 
 class TestScalarMetaschemaType(parent.TestMetaschemaType):
@@ -109,7 +107,7 @@ def test_from_array(self):
 
 
 # Dynamically create tests for dynamic and explicitly typed scalars
-for t in _valid_types.keys():
+for t in constants.VALID_TYPES.keys():
     iattr_imp = {'_type': t}
     if t == 'complex':
         iattr_imp['_prec'] = 64
diff --git a/yggdrasil/metaschema/properties/tests/test_MetaschemaProperty.py b/yggdrasil/metaschema/properties/tests/test_MetaschemaProperty.py
index 0be3e5e25..fe6a762dd 100644
--- a/yggdrasil/metaschema/properties/tests/test_MetaschemaProperty.py
+++ b/yggdrasil/metaschema/properties/tests/test_MetaschemaProperty.py
@@ -1,6 +1,6 @@
 from yggdrasil.tests import YggTestClassInfo, assert_equal
-from yggdrasil.metaschema import get_validator, get_metaschema
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError
+from yggdrasil.metaschema import (
+    get_validator, get_metaschema, MetaschemaTypeError)
 from yggdrasil.metaschema.properties.MetaschemaProperty import (
     create_property)
 
diff --git a/yggdrasil/metaschema/properties/tests/test_ScalarMetaschemaProperties.py b/yggdrasil/metaschema/properties/tests/test_ScalarMetaschemaProperties.py
index 58d9ee25e..8a746224a 100644
--- a/yggdrasil/metaschema/properties/tests/test_ScalarMetaschemaProperties.py
+++ b/yggdrasil/metaschema/properties/tests/test_ScalarMetaschemaProperties.py
@@ -1,24 +1,24 @@
 import numpy as np
-from yggdrasil import units
+from yggdrasil import units, constants
 from yggdrasil.tests import assert_raises, assert_equal
-from yggdrasil.metaschema.properties import ScalarMetaschemaProperties
 from yggdrasil.metaschema.properties.tests import (
     test_MetaschemaProperty as parent)
-from yggdrasil.metaschema.datatypes import MetaschemaTypeError
+from yggdrasil.metaschema import (data2dtype, definition2dtype,
+                                  MetaschemaTypeError)
 
 
 def test_data2dtype_errors():
     r"""Check that error is raised for list, dict, & tuple objects."""
-    assert_raises(MetaschemaTypeError, ScalarMetaschemaProperties.data2dtype, [])
+    assert_raises(MetaschemaTypeError, data2dtype, [])
 
 
 def test_definition2dtype_errors():
     r"""Check that error raised if type not specified."""
-    assert_raises(KeyError, ScalarMetaschemaProperties.definition2dtype, {})
-    assert_raises(RuntimeError, ScalarMetaschemaProperties.definition2dtype,
+    assert_raises(KeyError, definition2dtype, {})
+    assert_raises(RuntimeError, definition2dtype,
                   {'type': 'float'})
-    assert_equal(ScalarMetaschemaProperties.definition2dtype({'type': 'bytes'}),
-                 np.dtype((ScalarMetaschemaProperties._valid_types['bytes'])))
+    assert_equal(definition2dtype({'type': 'bytes'}),
+                 np.dtype((constants.VALID_TYPES['bytes'])))
 
 
 class TestSubtypeMetaschemaProperty(parent.TestMetaschemaProperty):
diff --git a/yggdrasil/serialize/tests/test_SerializeBase.py b/yggdrasil/serialize/tests/test_SerializeBase.py
index 000d59e15..58f8f9c75 100644
--- a/yggdrasil/serialize/tests/test_SerializeBase.py
+++ b/yggdrasil/serialize/tests/test_SerializeBase.py
@@ -1,6 +1,6 @@
 import copy
 from yggdrasil.tests import YggTestClassInfo, assert_equal
-from yggdrasil import tools
+from yggdrasil import constants
 from yggdrasil.components import import_component
 from yggdrasil.serialize import SerializeBase
 
@@ -39,7 +39,7 @@ def inst_kwargs(self):
     def empty_head(self, msg):
         r"""dict: Empty header for message only contains the size."""
         out = dict(size=len(msg), incomplete=False)
-        if msg == tools.YGG_MSG_EOF:  # pragma: debug
+        if msg == constants.YGG_MSG_EOF:  # pragma: debug
             out['eof'] = True
         return out
 
@@ -143,7 +143,7 @@ def test_serialize_eof(self):
         r"""Test serialize/deserialize EOF."""
         if (self._cls == 'SerializeBase'):
             return
-        iobj = tools.YGG_MSG_EOF
+        iobj = constants.YGG_MSG_EOF
         msg = self.instance.serialize(iobj)
         iout, ihead = self.instance.deserialize(msg)
         self.assert_equal(iout, iobj)
@@ -153,7 +153,7 @@ def test_serialize_eof_header(self):
         r"""Test serialize/deserialize EOF with header."""
         if (self._cls == 'SerializeBase'):
             return
-        iobj = tools.YGG_MSG_EOF
+        iobj = constants.YGG_MSG_EOF
         msg = self.instance.serialize(iobj, header_kwargs=self._header_info)
         iout, ihead = self.instance.deserialize(msg)
         self.assert_equal(iout, iobj)
diff --git a/yggdrasil/serialize/tests/test_serialize.py b/yggdrasil/serialize/tests/test_serialize.py
index 4e6a70fff..95cbcf1e6 100644
--- a/yggdrasil/serialize/tests/test_serialize.py
+++ b/yggdrasil/serialize/tests/test_serialize.py
@@ -1,5 +1,5 @@
 import numpy as np
-from yggdrasil import serialize, platform
+from yggdrasil import serialize, platform, constants
 from yggdrasil.tests import assert_raises, assert_equal
 
 
@@ -107,10 +107,10 @@ def test_cformat2nptype():
         if isinstance(a, str):
             a = [a]
         for _ia in a:
-            if _ia.startswith(serialize._fmt_char_str):
+            if _ia.startswith(constants.FMT_CHAR_STR):
                 ia = _ia.encode("utf-8")
             else:
-                ia = serialize._fmt_char + _ia.encode("utf-8")
+                ia = constants.FMT_CHAR + _ia.encode("utf-8")
             assert_equal(serialize.cformat2nptype(ia), np.dtype(b))  # .str)
             # assert_equal(serialize.cformat2nptype(ia), np.dtype(b).str)
     assert_raises(TypeError, serialize.cformat2nptype, 0)
diff --git a/yggdrasil/tests/test_components.py b/yggdrasil/tests/test_components.py
index 477186d56..bc4358302 100644
--- a/yggdrasil/tests/test_components.py
+++ b/yggdrasil/tests/test_components.py
@@ -1,4 +1,4 @@
-from yggdrasil.tests import assert_raises
+import pytest
 from yggdrasil import components
 
 
@@ -16,7 +16,8 @@ def test_import_component():
     components.import_component('serializer', 'PandasSerialize')
     # Test access to file through comm (including error)
     components.import_component('comm', 'pickle')
-    assert_raises(ValueError, components.import_component, 'comm', 'invalid')
+    with pytest.raises(components.ComponentError):
+        components.import_component('comm', 'invalid')
     # Tests with registry suspended
     out = components.suspend_registry()
     components.import_component('serializer')
diff --git a/yggdrasil/tests/test_runner.py b/yggdrasil/tests/test_runner.py
index bf7996d9e..3b024a2be 100644
--- a/yggdrasil/tests/test_runner.py
+++ b/yggdrasil/tests/test_runner.py
@@ -1,3 +1,4 @@
+import pytest
 import os
 import unittest
 import signal
@@ -12,7 +13,7 @@ def test_get_runner():
     r"""Use get_runner to start a run."""
     namespace = "test_get_runner_%s" % str(uuid.uuid4)
     cr = runner.get_runner([ex_yamls['hello']['python']],
-                           namespace=namespace)
+                           namespace=namespace, validate=True)
     cr.run()
     cr.sleep()
 
@@ -22,8 +23,9 @@ def test_get_run():
     namespace = "test_run_%s" % str(uuid.uuid4)
     runner.run([ex_yamls['hello']['python']],
                namespace=namespace)
-    runner.run([ex_yamls['model_error']['python']],
-               namespace=namespace)
+    with pytest.raises(runner.IntegrationError):
+        runner.run([ex_yamls['model_error']['python']],
+                   namespace=namespace)
 
 
 def test_run_process_connections():
diff --git a/yggdrasil/tests/test_schema.py b/yggdrasil/tests/test_schema.py
index 32d8eff6b..498b62160 100644
--- a/yggdrasil/tests/test_schema.py
+++ b/yggdrasil/tests/test_schema.py
@@ -1,6 +1,7 @@
 import os
 import pprint
 import tempfile
+import subprocess
 from jsonschema import ValidationError
 from yggdrasil import schema, components
 from yggdrasil.tests import assert_raises, assert_equal
@@ -102,12 +103,33 @@ def test_default_schema():
 
 
 def test_create_schema():
-    r"""Test creating new schema."""
+    r"""Test re-creating the schema."""
+    f_schema = schema._schema_fname
+    f_consts = os.path.join(os.path.dirname(schema.__file__), 'constants.py')
+    old_schema = open(f_schema, 'r').read()
+    old_consts = open(f_consts, 'r').read()
+    try:
+        os.remove(f_schema)
+        open(f_consts, 'w').write(
+            old_consts.split(schema._constants_separator)[0]
+            + schema._constants_separator)
+        subprocess.check_call(['yggschema'])
+        new_schema = open(f_schema, 'r').read()
+        new_consts = open(f_consts, 'r').read()
+        assert(new_consts == old_consts)
+        assert(new_schema == old_schema)
+    finally:
+        open(f_schema, 'w').write(old_schema)
+        open(f_consts, 'w').write(old_consts)
+
+
+def test_save_load_schema():
+    r"""Test saving & loading schema."""
     fname = 'test_schema.yml'
     if os.path.isfile(fname):  # pragma: debug
         os.remove(fname)
     # Test saving/loading schema
-    s0 = schema.create_schema()
+    s0 = schema.load_schema()
     s0.save(fname)
     assert(s0 is not None)
     assert(os.path.isfile(fname))
@@ -118,7 +140,8 @@ def test_create_schema():
     # Test getting schema
     s2 = schema.load_schema(fname)
     assert(os.path.isfile(fname))
-    assert_equal(s2, s0)
+    assert(s2.schema == s0.schema)
+    assert(s2 == s0)
     os.remove(fname)
 
 
diff --git a/yggdrasil/tests/test_services.py b/yggdrasil/tests/test_services.py
index ca447061f..905c52e36 100644
--- a/yggdrasil/tests/test_services.py
+++ b/yggdrasil/tests/test_services.py
@@ -72,11 +72,14 @@ def check_settings(service_type, partial_commtype=None):
 def running_service(service_type, partial_commtype=None, with_coverage=False):
     r"""Context manager to run and clean-up an integration service."""
     check_settings(service_type, partial_commtype)
+    model_repo = "https://github.com/cropsinsilico/yggdrasil_models_test/models"
     log_level = logging.ERROR
     args = [sys.executable, "-m", "yggdrasil", "integration-service-manager",
             f"--service-type={service_type}"]
     if partial_commtype is not None:
         args.append(f"--commtype={partial_commtype}")
+    args += ["start", f"--model-repository={model_repo}",
+             f"--log-level={log_level}"]
     package_dir = None
     process_kws = {}
     if with_coverage:
@@ -97,7 +100,8 @@ def running_service(service_type, partial_commtype=None, with_coverage=False):
         lines[-1] += ')'
         lines += ['assert(not srv.is_running)',
                   f'srv.start_server(with_coverage={with_coverage},',
-                  f'                 log_level={log_level})']
+                  f'                 log_level={log_level},'
+                  f'                 model_repository=\'{model_repo}\')']
         with open(script_path, 'w') as fd:
             fd.write('\n'.join(lines))
         args = [sys.executable, script_path]
@@ -186,7 +190,7 @@ def running_service(self, request):
             self.cli = None
 
     def call_integration_service(self, cli, yamls, test_yml, copy_yml=None,
-                                 name='test'):
+                                 name='test', yaml_param=None):
         r"""Call an integration that includes a service."""
         remote_yml = '_remote'.join(os.path.splitext(test_yml))
         yamls = copy.copy(yamls)
@@ -202,12 +206,17 @@ def call_integration_service(self, cli, yamls, test_yml, copy_yml=None,
                 remote_code = 'a'
             else:
                 remote_code = 'w'
+            lines = ['service:',
+                     f'    name: {name}',
+                     f'    yamls: [{test_yml}]',
+                     f'    type: {service_type}',
+                     f'    address: {address}']
+            if yaml_param:
+                lines.append('    yaml_param:')
+                for k, v in yaml_param.items():
+                    lines.append(f'        {k}: "{v}"')
             with open(remote_yml, remote_code) as fd:
-                fd.write('\n'.join(['service:',
-                                    f'    name: {name}',
-                                    f'    yamls: [{test_yml}]',
-                                    f'    type: {service_type}',
-                                    f'    address: {address}']))
+                fd.write('\n'.join(lines))
             r = runner.get_runner(yamls)
             r.run()
             assert(not r.error_flag)
@@ -219,9 +228,9 @@ def test_git_fails(self, running_service):
         r"""Test that sending a request for a git YAML fails."""
         cli = running_service
         test_yml = ("git:https://github.com/cropsinsilico/example-fakemodel/"
-                    "fakemodel.yml")
+                    "fakemodel3.yml")
         assert(not os.path.isfile(
-            "cropsinsilico/example-fakemodel/fakemodel.yml"))
+            "cropsinsilico/example-fakemodel/fakemodel3.yml"))
         assert_raises(ServerError, cli.send_request, yamls=test_yml,
                       action='start')
 
@@ -255,6 +264,7 @@ def test_registered_service(self, running_service):
         test_yml = ex_yamls['fakeplant']['python']
         assert_raises(KeyError, cli.registry.remove, 'test')
         assert_raises(ServerError, cli.send_request, 'test')
+        print(cli.send_request('FakePlant'))
         cli.registry.add('test', test_yml, namespace='remote')
         print(cli.send_request('test'))
         assert_raises(ValueError, cli.registry.add, 'test', [test_yml])
@@ -272,7 +282,7 @@ def test_registered_service(self, running_service):
             cli.registry.add(reg_coll)
             print(cli.send_request('photosynthesis', namespace='phot'))
             assert_raises(ValueError, cli.registry.add,
-                          'photosynthesis', [test_yml])
+                          'photosynthesis', [test_yml], invalid=1)
             cli.send_request('photosynthesis', action='stop')
             cli.registry.remove(reg_coll)
             assert_raises(KeyError, cli.registry.remove, 'photosynthesis')
@@ -297,8 +307,9 @@ def test_calling_server_as_service(self, running_service):
         if (((running_service.commtype != 'rest')
              or (running_service.service_type != 'flask'))):
             pytest.skip("redundent test")  # pragma: testing
-        os.environ.update(FIB_ITERATIONS='3',
+        yaml_param = dict(FIB_ITERATIONS='3',
                           FIB_SERVER_SLEEP_SECONDS='0.01')
+        os.environ.update(yaml_param)
         yamls = ex_yamls['rpcFib']['all_nomatlab']
         service = None
         for x in yamls:
@@ -306,7 +317,8 @@ def test_calling_server_as_service(self, running_service):
                 service = x
                 break
         self.call_integration_service(running_service, yamls, service,
-                                      name='rpcFibSrv')
+                                      name='rpcFibSrv',
+                                      yaml_param=yaml_param)
 
     def test_calling_service_as_function(self, running_service):
         r"""Test calling an integrations as a service in an integration."""
@@ -340,10 +352,30 @@ def test_validate_model_submission():
     try:
         fname = os.path.join(os.path.dirname(__file__), 'yamls',
                              'FakePlant.yaml')
-        validate_model_submission(fname)
+        validate_model_submission([fname])
         os.remove(os.path.join('cropsinsilico', 'example-fakemodel',
                                'LICENSE'))
         assert_raises(RuntimeError, validate_model_submission, fname)
     finally:
         if os.path.isfile('cropsinsilico/example-fakemodel/fakemodel.yml'):
             git.rmtree("cropsinsilico")
+
+
+def test_validate_model_repo():
+    r"""Test validation of YAMLs in the model repository."""
+    import git
+    import tempfile
+    dest = os.path.join(tempfile.gettempdir(), "model_repo")
+    url = "https://github.com/cropsinsilico/yggdrasil_models"
+    for x in [url, url + "_test"]:
+        try:
+            repo = git.Repo.clone_from(x, dest)
+            model_dir = os.path.join(dest, "models")
+            if os.path.isdir(model_dir):
+                # This condition can be removed once there are models in the
+                # non-dev repository
+                validate_model_submission(model_dir)
+            repo.close()
+        finally:
+            if os.path.isdir(dest):
+                git.rmtree(dest)
diff --git a/yggdrasil/tests/test_yamlfile.py b/yggdrasil/tests/test_yamlfile.py
index da04369fe..01808ba28 100644
--- a/yggdrasil/tests/test_yamlfile.py
+++ b/yggdrasil/tests/test_yamlfile.py
@@ -655,6 +655,8 @@ class TestYamlModelSubmission(YamlTestBase):
                   '  - name: FakeModel',
                   '    repository_url: https://github.com/cropsinsilico/'
                   'example-fakemodel',
+                  '    repository_commit: e4bc7932c3c0c68fb3852cfb864777ca64cba448',
+                  '    description: Example model submission',
                   '    language: python',
                   '    args:',
                   '      - ./src/fakemodel.py',
diff --git a/yggdrasil/tests/yamls/FakePlant.yaml b/yggdrasil/tests/yamls/FakePlant.yaml
index 7ee6ea41b..29c719866 100644
--- a/yggdrasil/tests/yamls/FakePlant.yaml
+++ b/yggdrasil/tests/yamls/FakePlant.yaml
@@ -6,7 +6,6 @@
   language: python
   inputs:
   - name: photosynthesis_rate
-    commtype: default
     datatype:
       type: bytes
     default_file:
@@ -14,11 +13,16 @@
       filetype: table
   outputs:
   - name: growth_rate
-    commtype: default
     datatype:
       type: bytes
     default_file:
       name: ./Output/output.txt
       filetype: table
   repository_url: https://github.com/cropsinsilico/example-fakemodel
-  description: Example model submission
\ No newline at end of file
+  repository_commit: 498d04b7c98078fbd8d608f860882dc571476fd8
+  description: Example model submission
+  validation_command: validate.py
+  dependencies:
+  - numpy
+  additional_dependencies:
+    c++: [cmake]