app-development.md
375 lines · skills/dnanexus-integration/references/app-development.md at 5f2bc03
Most marks come from matching words in the file, so treat them as pointers.
App and Applet Development
Raw lines 1 to 2Quick Navigation
- Model and source layout
- Python and Bash entry points
- Execution environment
- Local testing
- Build and platform tests
- Subjobs, reuse, and errors
- Publish checklist
Model
- Applet: immutable executable data object in one project; best for development, testing, and project-local tools.
- App: versioned executable that can be authorized, published, and run across projects/regions; best for maintained reusable products.
- Job: execution of an app or applet.
- Entry point: named function within an executable.
mainis used for a normal app/applet run; other entry points can be launched as subjobs.
Develop as an applet, test in a non-production project, then create and publish an app only after reviewing code, permissions, dependencies, and regions.
Raw lines 13 to 25Source Layout
my-app/
├── dxapp.json
├── src/
│ └── my_app.py
├── resources/
│ └── requirements.txt
└── test/
├── input.json
└── expected.jsonresources/ is bundled into the executable. Never place tokens, private keys, registry passwords, or patient data in it.
Create a Skeleton
dx-app-wizardThe wizard supports templates such as:
basicparallelizedscatter-process-gather
The generated code is a starting point, not a production security boundary. Review all access and dependency fields.
Raw lines 43 to 57Python Entry Point
The following adapter is illustrative: quality-filter is a placeholder for your tested scientific tool, not a bundled executable. It has not been run on a DNAnexus worker during this documentation review.
from __future__ import annotations
import subprocess
from pathlib import Path
from typing import Any
import dxpy
@dxpy.entry_point("main")
def main(reads: dict[str, Any], min_quality: int = 20) -> dict[str, Any]:
if not 0 <= min_quality <= 93:
raise dxpy.AppError("min_quality must be between 0 and 93")
input_path = Path("reads.fastq.gz")
output_path = Path("filtered.fastq.gz")
reads_file = dxpy.get_handler(reads)
if not isinstance(reads_file, dxpy.DXFile):
raise dxpy.AppError("reads must reference a DNAnexus file")
dxpy.download_dxfile(reads_file, str(input_path))
# Fixed executable + argv list; no shell interpretation of user input.
subprocess.run(
[
"quality-filter",
"--input",
str(input_path),
"--output",
str(output_path),
"--min-quality",
str(min_quality),
],
check=True,
)
report = dxpy.upload_local_file(
str(output_path),
wait_on_close=True,
)
return {"filtered_reads": dxpy.dxlink(report.get_id())}
dxpy.run()Key points:
dxpy.get_handler()accepts an ID or DNAnexus link.- Pass subprocess arguments as a list. Do not use
shell=Truewith inputs. - Return a mapping whose keys exactly match
outputSpec. - Return file/record outputs as DNAnexus links.
- Use
AppErrorfor an expected, actionable user/input error. - Do not catch every exception and convert it to success.
The Python execution template reads job_input.json, calls the selected entry point with keyword arguments, and writes the returned mapping to job_output.json.
Bash Entry Point
#!/usr/bin/env bash
main() {
dx download "$reads" --output "reads.fastq.gz"
quality-filter \
--input "reads.fastq.gz" \
--output "filtered.fastq.gz" \
--min-quality "$min_quality"
filtered_reads="$(dx upload "filtered.fastq.gz" --brief)"
dx-jobutil-add-output \
"filtered_reads" "$filtered_reads" --class=file
}The platform invokes Bash with error-exit behavior. Still quote variables, validate scalar inputs, and avoid building command strings. Input values are untrusted even when provided by a trusted platform user.
Raw lines 125 to 147Execution Environment
Current AEEs:
- Ubuntu 24.04, version
0 - Ubuntu 20.04, version
0
Jobs run on ephemeral workers. The platform:
- Provisions the worker and app container.
- Installs
execDepends. - Configures API, networking, and logs.
- Unpacks bundled dependencies and assets.
- Runs the selected interpreter/entry point.
- Captures
stdoutandstderr. - Processes
job_output.jsonorjob_error.json. - Destroys the workspace unless a supported debugging hold is requested.
Useful system-provided values include:
DX_JOB_IDDX_WORKSPACE_IDDX_PROJECT_CONTEXT_IDDX_RESOURCES_IDDX_SECURITY_CONTEXT
Use dxpy rather than parsing these directly where possible. Never print the security context.
Network access is restricted unless requested in app metadata. Prefer a domain allowlist; do not request ["*"] solely to install dependencies at runtime.
Separate Pure Logic from Platform I/O
Do not use python src/my_app.py as the only local test. dxpy.run() expects the platform execution contract.
Instead:
- Put scientific logic in ordinary functions/modules.
- Unit-test those functions with local files.
- Keep the entry point as a thin download → validate → compute → upload adapter.
- Test the packaged applet on DNAnexus with small non-sensitive fixtures.
Example:
def build_command(
input_path: str,
output_path: str,
min_quality: int,
) -> list[str]:
if not 0 <= min_quality <= 93:
raise ValueError("min_quality must be between 0 and 93")
return [
"quality-filter",
"--input",
input_path,
"--output",
output_path,
"--min-quality",
str(min_quality),
]Test build_command() locally without credentials.
Build
Validate offline from this skill's root (or resolve scripts/ relative to the loaded skill directory):
uv run python "scripts/validate_dxapp.py" \
"/path/to/my-app/dxapp.json" --kind applet --strictBuild an applet:
dx build "my-app"Build a versioned app:
dx build "my-app" --create-appUse dx build --help from the installed toolkit for destination, overwrite, regional, and advanced flags; these evolve with dx-toolkit.
After build:
dx describe "applet-xxxx"
dx run "applet-xxxx" -hVerify:
- Input/output fields and defaults
- AEE release
- Regions and instance requirements
- Access and network requirements
- Dependency records/files
- Timeout and restart policy
Test on Platform
Use a dedicated test project and explicit destination:
dx run "applet-xxxx" \
--input-json-file "test/input.json" \
--destination "project-test:/test-runs/run-001"Keep interactive confirmation. Set a small cost limit for automation:
dx run "applet-xxxx" \
--input-json-file "test/input.json" \
--destination "project-test:/test-runs/run-002" \
--cost-limit 5Monitor:
dx watch "job-xxxx"
dx describe "job-xxxx"Test:
- Required and optional inputs
- Malformed and incompatible inputs
- Empty and boundary-size data
- Output classes, names, and closed states
- Retry behavior and idempotency
- Network-denied behavior
- Resource exhaustion behavior
- Duplicate execution and reuse
Subjobs and Parallelism
Only use subjobs for independent work large enough to justify worker startup.
import dxpy
@dxpy.entry_point("main")
def main(items):
jobs = [
dxpy.new_dxjob(
fn_input={"item": item},
fn_name="process_item",
)
for item in items
]
return {
"results": [
job.get_output_ref("result")
for job in jobs
]
}
@dxpy.entry_point("process_item")
def process_item(item):
result = process_one(item)
return {"result": result}
dxpy.run()Do not wait synchronously for child jobs when output references can express the dependency. Ensure every restartable entry point is idempotent before setting restartableEntryPoints to all.
Reuse and Idempotency
DNAnexus can reuse identical completed jobs. Preserve reuse for deterministic workloads to save time and cost.
Disable reuse only when:
- The executable intentionally depends on untracked external state.
- A debugging run must execute again.
- The user explicitly requires recomputation.
If an app writes outside its output folder, uses the current time, downloads floating resources, or mutates a shared record, document that behavior and reconsider whether it is suitable for reuse/restarts.
Raw lines 332 to 346Errors
Use failure categories deliberately:
AppError: expected user/data problem with an actionable messageAppInternalError: unexpected application defect or nonzero process exitAppInsufficientResourceError: insufficient memory/storage conditionInputError/OutputError: platform contract mismatchDXExecDependencyError: dependency installation failure
Do not mark partial scientific output as success. If safe partial results are valuable, publish them under explicitly named diagnostic outputs and still return the appropriate failure.
Raw lines 347 to 360Publish Checklist
- Source and dependency licenses reviewed
- App version incremented
- Inputs/outputs backward compatible or breaking change documented
- Reproducible assets/images pinned
- No embedded credentials or sensitive fixtures
- Network and project access minimized
- Supported regions tested
- Instance types currently available
- Timeouts, retries, and cost behavior tested
- Output metadata and scientific provenance included
- App source visibility (
openSource) chosen intentionally - Authorized users/orgs reviewed
- Applet test evidence retained