Apps and manifests#

A PlayMolecule “app” is a container plus a JSON manifest. The container holds the science (the actual proteinprepare Python code, for example). The manifest declares everything PlayMolecule needs to expose that container as a typed Python function: parameters, defaults, expected outputs, resource requirements, and built-in tests.

This page explains what’s in a manifest and how PlayMolecule turns it into the Python surface you import.

Anatomy of a manifest#

A manifest is a JSON document with these top-level keys. Annotated with for omitted parts (not real JSON):

{
  "container_config": { "name": "ProteinPrepare", "version": "1", "condaenvs": [...] },
  "meta_keywords": ["preparation", "protein", ...],
  "citations": [...],
  "files": { "tests/3ptb.pdb": "/app/files/tests/3ptb.pdb", ... },
  "functions": [
    {
      "function": "proteinprepare.apps.proteinprepare.app.main",
      "env": "base",
      "resources": { "ncpu": 1, "ngpu": 0 },
      "outputs": { "output.pdb": "...", "details.csv": "...", "pka_plot.png": "..." },
      "params": [
        { "name": "outdir", "type": "Path", "mandatory": true, ... },
        { "name": "pdbid", "type": "str", "mandatory": false, ... },
        { "name": "pH", "type": "float", "value": 7.2, ... },
        ...
      ],
      "tests": {
        "simple": {
          "description": "Prepare 3PTB structure from RCSB",
          "arguments": { "pdbid": "3ptb" },
          "expected_outputs": ["output.pdb", "details.csv", "pka_plot.png"]
        },
        ...
      },
      "examples": ["proteinprepare(outdir='./test', pdbid='3ptb').run()"],
      "description": "ProteinPrepare prepares proteins (and nucleic acids)"
    }
  ]
}

Each entry in functions becomes one callable on the app module.

How a manifest becomes a Python function#

When a manifest is loaded, PlayMolecule assembles a Python callable for each entry in functions, plus a set of module-level attributes on the app’s version submodule:

On the dynamic function itself:

  1. Signatureparams is converted into an inspect.Signature. Each param’s type, nargs, mandatory, and default value map onto inspect.Parameter attributes. The function then binds incoming kwargs against that signature on every call, which is how PlayMolecule raises a typed TypeError when you mistype pdbi for pdbid.

  2. Docstringdescription, params, outputs, and examples are formatted into a NumPy-style docstring so help(app) and app? work without any extra wiring.

  3. Tests — each entry under tests becomes a callable test attribute accessible as app.tests.<name>.run().

  4. Manifest metadata — the per-function manifest entry is attached as app.__manifest__.

On the version submodule (shared by every function in that version):

  1. Artifacts / files — entries under artifacts (or the older synonym datasets) and the files block become attributes on the submodule itself: someapp.v1.artifacts.<NAME>, someapp.v1.files. The full app manifest is also attached at the submodule level as someapp.v1.__manifest__.

The end result: at the version submodule playmolecule.apps.proteinprepare.v1 you get a callable proteinprepare (the function), plus artifacts, datasets, files, and __manifest__ — everything derived from one JSON file.

Versions#

A given app can ship multiple manifests, one per version. They appear as parallel submodules:

playmolecule.apps.proteinprepare           # alias for latest
playmolecule.apps.proteinprepare.v1        # explicit
playmolecule.apps.proteinprepare.v2        # explicit

The unqualified symbol is set at import time by natural-sort over the version strings (v10 sorts after v9).

Multi-function apps#

functions is a list. A single manifest can expose several entry points — for example, an app that does both “prepare” and “validate”. Each one shows up as its own attribute:

from playmolecule.apps import someapp

someapp.prepare(outdir="out", ...)
someapp.validate(outdir="out", ...)

A function literally named main is exposed under the app name instead, so you can write someapp(...) rather than someapp.main(...). That’s why proteinprepare(...) works — the manifest’s actual function is named main.

Where to put a manifest#

  • Docker registry — embedded as an image label.

  • HTTP backend — served by the backend’s /apps/manifests endpoint.

  • Local registry (developer use)<root>/apps/<appname>/<version>/<manifest>.json (where <root> is the path after local: in PM_REGISTRIES), alongside a run.sh and any payload files. Useful when you’re authoring your own app and want to iterate on the manifest without publishing.

The three discovery paths produce the same app_versions data shape (see Architecture), so the resulting Python surface is identical regardless of source.

See also#