Representing data#
Data Types#
Motivations#
The interoperability problem that Metasmith seeks to solve can be summarized as determining if the output of one computational step can be immediately used as the input of another. The base case to be considered consists of three components: an upstream step, which produces an intermediate data product, that is then consumed by a downstream step. In bioinformatics, the conventional approach is to forego types and link the steps using the literal files that are produced. We will call this the “topology-centric” approach because interoperability is encoded by the connections between tools and intermediate files. The alternative proposed by Metasmith is to attach types to the inputs and outputs of each step. Outputs from the upstream step can be used as the inputs of the downstream step if the types match. We will call this the “contract-centric” approach because interoperability in each step’s contract, which describe the required inputs and promised outputs.
There are three key features that motivate the use of a contract-centric approach over a topology-centric approach.
The contract of each step can be defined in isolation of other steps. While not sufficient on its own, this sets up the foundation for modular systems.
Tools are means to an end. Obtaining correct results are often more important than the specific methods used. In this way, the contract-centric approach favours the user over the machine as it describes the data explicitly and leaves Metasmith to infer the topology. By contrast, the topology-centric approach describes the methods explicitly but leaves the no information about what is produced. For example, “a file produced by flye, megahit, spades, and hifiasm” is harder to comprehend than “a nucleotide sequence encoded in FASTA format”.
Definitions#
Metasmith describes data types using set of properties, which enables comparions between data to leverage set operations. For example, if we have two types: A = {1, 2} and B = {1, 2, 3}, then B may replace A since B can provide all properties that A can provide. Formally, if A is a subset of B (A ⊆ B), then A is substitutable by B. Practically, if the properties of an output A is a subset of the properties of required input B, then A can be used as the input B.
Data type objects within the Metasmith API are called Endpoints to avoid conflicts with python types and because they exist at either end of transforms.
1from metasmith.python_api import Endpoint
The following example shows how to create and compare endpoints.
1ball = Endpoint({"ball"})
2red_ball = Endpoint({"red", "ball"})
3red_ball.IsA(ball) # True, a red ball is a ball
4ball.IsA(red_ball) # False, a ball is not necessarily red
Data Type Library#
Endpoints can be gathered into a DataTypeLibrary and given a name for convenience.
1from metasmith.python_api import DataTypeLibrary
A DataTypeLibrary shares the same basic syntax as a python dict
1dtypes = DataTypeLibrary()
2dtypes["red_ball"] = Endpoint({"red", "ball"})
YAML#
Data type libraries can be persisted to disk and it may be more convenient to edit them as yaml files.
1dtypes.Save("dtypes.yml")
2dtypes = DataTypeLibrary.Load("dtypes.yml")
Note
To help with standardization across data type libraries, metadata can be included to describe the ontology of the data types.
In YAML form, properties of endpoints can be key-value pairs…
1from metasmith import examples
2dtypes = examples.DataTypeLibraries("template_keyval")
1# ...
2types:
3 property_type_demo:
4 properties:
5 str: abc
6 int: 1
7 float: 0.3
8 bool: True
9 none: null
10 list:
11 - item 1
12 - item 2
13 contigs:
14 properties:
15 data: DNA sequence
16 format: FASTA
17 oci_image:
18 properties:
19 data: software container
20 format: OCI
21 provides:
22 - python==3.12
23 - some other tool
… or simple lists
1dtypes = examples.DataTypeLibraries("template_list")
1# ...
2types:
3 property_type_demo:
4 properties:
5 - abc
6 - 1
7 - 0.3
8 - True
9 - null
10 - list item 1
11 - list item 2
12 contigs:
13 properties:
14 - data=DNA sequence
15 - format=FASTA
16 oci_image:
17 properties:
18 - data=software container
19 - format=OCI
20 - provides=python 3.12
21 - provides=some other tool
Below is a more realistic example themed after genomics.
1dtypes = examples.DataTypeLibraries("minimal_genomics")
1ontology:
2 doi: https://doi.org/10.1093/bioinformatics/btt113
3 name: EDAM
4 strict: false
5 version: 1.25
6schema: '1.0'
7types:
8 aa_sequences:
9 properties:
10 data: Amino acid sequence
11 format: FASTA
12 contigs:
13 properties:
14 data: DNA sequence
15 format: FASTA
16 oci_image_blast:
17 properties:
18 data: software container
19 format: OCI
20 provides:
21 - blast
22 oci_image_prodigal:
23 properties:
24 data: software container
25 format: OCI
26 provides:
27 - prodigal
28 orf_annotations:
29 properties:
30 data: Protein features
31 format: CSV
32 protein_reference_fasta:
33 properties:
34 data: database reference
35 format: .faa
Data Instances#
A DataInstance refers to the piece of data that is described by an endpoint.
Data instances are created when a file or folder is registered to a DataInstanceLibrary.
On the filesystem, an XGDB is just a folder.
1from metasmith.python_api import DataInstanceLibrary
2xgdb = DataInstanceLibrary("./example.xgdb")
Note
For historical reasons, data instance libraries are shortened to “XGDB” (extended genome database) named after “PGDBs” (pathway genome database) from Pathway Tools and Metapathways.
Data type libraries must be added as namespaces to an XGDB before data instances can be added. The namespace
"genomics" is added below.
1from metasmith import examples
2dtypes = examples.DataTypeLibraries("minimal_genomics")
3xgdb.AddTypeLibrary("genomics", dtypes)
The following information is required when adding new data instances:
The path to the original file or folder
The type of the data instance in the form
"namespace::type"
1xgdb.AddItem("/path/to/original/contigs.fna", "genomics::contigs")
2xgdb.AddItem("/path/to/original/orfs.faa", "genomics::aa_sequences")
3xgdb.Save()
Once added, softlinks can by automatically generated for each input file within the XGDB. A prefix is added to ensure that file names are unique.
This creates the following directory structure:
example.xgdb/
├── _metasmith/
│ ├── index.yml
│ └── types/
│ └── genomics.yml
├── 1_contigs.fna (symlink)
└── 2_orfs.faa (symlink)
_metasmith/index.yml:
1# ...
2manifest:
3 /path/to/original/contigs.fna: genomics::contigs
4 /path/to/original/orfs.faa: genomics::aa_sequences
5# ...
_metasmith/types/genomics.yml:
1# ...
2types:
3 contigs:
4 properties:
5 data: DNA sequence
6 format: FASTA
7 aa_sequences:
8 properties:
9 data: Amino acid sequence
10 format: FASTA
11# ...