From 80eafc507b5f397d32b577e3bafa6a35b95c7989 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 2 Jan 2024 16:30:44 -0800 Subject: [PATCH 01/11] Adding method to scan directory of sifter files and list their outputs --- cmd/root.go | 2 ++ cmd/scan/main.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 cmd/scan/main.go diff --git a/cmd/root.go b/cmd/root.go index d23161c..86df762 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "github.com/bmeg/sifter/cmd/inspect" "github.com/bmeg/sifter/cmd/run" + "github.com/bmeg/sifter/cmd/scan" "github.com/spf13/cobra" ) @@ -18,6 +19,7 @@ var RootCmd = &cobra.Command{ func init() { RootCmd.AddCommand(run.Cmd) RootCmd.AddCommand(inspect.Cmd) + RootCmd.AddCommand(scan.Cmd) } var genBashCompletionCmd = &cobra.Command{ diff --git a/cmd/scan/main.go b/cmd/scan/main.go new file mode 100644 index 0000000..6991de0 --- /dev/null +++ b/cmd/scan/main.go @@ -0,0 +1,68 @@ +package scan + +import ( + "fmt" + "io/fs" + "path/filepath" + "strings" + + "github.com/bmeg/sifter/playbook" + "github.com/spf13/cobra" +) + +var jsonOut = false + +// Cmd is the declaration of the command line +var Cmd = &cobra.Command{ + Use: "scan ", + Short: "Scan for outputs", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + baseDir := args[0] + + ScanSifter(baseDir, func(pb *playbook.Playbook) { + + for pname, p := range pb.Pipelines { + emitName := "" + for _, s := range p { + if s.Emit != nil { + emitName = s.Emit.Name + } + } + if emitName != "" { + for _, s := range p { + if s.ObjectValidate != nil { + outdir := pb.GetDefaultOutDir() + outname := fmt.Sprintf("%s.%s.%s.json.gz", pb.Name, pname, emitName) + outpath := filepath.Join(outdir, outname) + //outpath, _ = filepath.Rel(baseDir, outpath) + fmt.Printf("%s\t%s\n", s.ObjectValidate.Title, outpath) + } + } + } + } + + }) + + return nil + }, +} + +func init() { + flags := Cmd.Flags() + flags.BoolVarP(&jsonOut, "json", "j", jsonOut, "Output JSON") +} + +func ScanSifter(baseDir string, userFunc func(*playbook.Playbook)) { + filepath.Walk(baseDir, + func(path string, info fs.FileInfo, err error) error { + if strings.HasSuffix(path, ".yaml") { + pb := playbook.Playbook{} + if parseErr := playbook.ParseFile(path, &pb); parseErr == nil { + userFunc(&pb) + } + } + return nil + }) +} From 51a570fb203a2f7e00ca790e7a7e1b4073d3f662 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 3 Jan 2024 15:51:04 -0800 Subject: [PATCH 02/11] Adding command for scanning for sifter scripts --- cmd/scan/main.go | 168 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 157 insertions(+), 11 deletions(-) diff --git a/cmd/scan/main.go b/cmd/scan/main.go index 6991de0..2dee020 100644 --- a/cmd/scan/main.go +++ b/cmd/scan/main.go @@ -1,28 +1,39 @@ package scan import ( + "encoding/json" "fmt" "io/fs" + "os" "path/filepath" "strings" "github.com/bmeg/sifter/playbook" + "github.com/bmeg/sifter/task" "github.com/spf13/cobra" ) var jsonOut = false +var objectsOnly = false +var baseDir = "" -// Cmd is the declaration of the command line -var Cmd = &cobra.Command{ - Use: "scan ", +type ScanEntry struct { + ObjectType string `json:"objectType"` + SifterFile string `json:"sifterFile"` + Outfile string `json:"outFile"` +} + +var ObjectCommand = &cobra.Command{ + Use: "objects", Short: "Scan for outputs", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - baseDir := args[0] + scanDir := args[0] - ScanSifter(baseDir, func(pb *playbook.Playbook) { + outputs := []ScanEntry{} + ScanSifter(scanDir, func(pb *playbook.Playbook) { for pname, p := range pb.Pipelines { emitName := "" for _, s := range p { @@ -32,26 +43,161 @@ var Cmd = &cobra.Command{ } if emitName != "" { for _, s := range p { + outdir := pb.GetDefaultOutDir() + outname := fmt.Sprintf("%s.%s.%s.json.gz", pb.Name, pname, emitName) + outpath := filepath.Join(outdir, outname) + o := ScanEntry{SifterFile: pb.GetPath(), Outfile: outpath} if s.ObjectValidate != nil { - outdir := pb.GetDefaultOutDir() - outname := fmt.Sprintf("%s.%s.%s.json.gz", pb.Name, pname, emitName) - outpath := filepath.Join(outdir, outname) //outpath, _ = filepath.Rel(baseDir, outpath) - fmt.Printf("%s\t%s\n", s.ObjectValidate.Title, outpath) + //fmt.Printf("%s\t%s\n", s.ObjectValidate.Title, outpath) + o.ObjectType = s.ObjectValidate.Title + } + if objectsOnly { + if o.ObjectType != "" { + outputs = append(outputs, o) + } + } else { + outputs = append(outputs, o) } } } } + }) + + if jsonOut { + j := json.NewEncoder(os.Stdout) + j.SetIndent("", " ") + j.Encode(outputs) + } else { + for _, i := range outputs { + fmt.Printf("%s\t%s\n", i.ObjectType, i.Outfile) + } + } + + return nil + + }, +} + +type ScriptEntry struct { + Path string `json:"path"` + Inputs []string `json:"inputs"` + Outputs []string `json:"outputs"` +} + +func removeDuplicates(s []string) []string { + t := map[string]bool{} + + for _, i := range s { + t[i] = true + } + out := []string{} + for k := range t { + out = append(out, k) + } + return out +} + +func relPathArray(basedir string, paths []string) []string { + out := []string{} + for _, i := range paths { + if o, err := filepath.Rel(baseDir, i); err == nil { + out = append(out, o) + } + } + return out +} + +var ScriptCommand = &cobra.Command{ + Use: "scripts", + Short: "Scan for scripts", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + scanDir := args[0] + + scripts := []ScriptEntry{} + if baseDir == "" { + baseDir, _ = os.Getwd() + } + baseDir, _ = filepath.Abs(baseDir) + //fmt.Printf("basedir: %s\n", baseDir) + + userInputs := map[string]string{} + + ScanSifter(scanDir, func(pb *playbook.Playbook) { + path := pb.GetPath() + scriptDir := filepath.Dir(path) + + config, _ := pb.PrepConfig(userInputs, baseDir) + + task := task.NewTask(pb.Name, scriptDir, baseDir, pb.GetDefaultOutDir(), config) + sourcePath, _ := filepath.Abs(path) + + cmdPath, _ := filepath.Rel(baseDir, sourcePath) + + inputs := []string{} + outputs := []string{} + for _, p := range pb.GetConfigFields() { + if p.IsDir() || p.IsFile() { + inputs = append(inputs, config[p.Name]) + } + } + //inputs = append(inputs, sourcePath) + + sinks, _ := pb.GetOutputs(task) + for _, v := range sinks { + outputs = append(outputs, v...) + } + + emitters, _ := pb.GetEmitters(task) + for _, v := range emitters { + outputs = append(outputs, v) + } + + //for _, e := range pb.Inputs { + //} + + s := ScriptEntry{ + Path: cmdPath, + Outputs: relPathArray(baseDir, removeDuplicates(outputs)), + Inputs: relPathArray(baseDir, removeDuplicates(inputs)), + } + scripts = append(scripts, s) }) + if jsonOut { + e := json.NewEncoder(os.Stdout) + e.SetIndent("", " ") + e.Encode(scripts) + } else { + for _, i := range scripts { + fmt.Printf("%s\n", i) + } + } + return nil }, } +// Cmd is the declaration of the command line +var Cmd = &cobra.Command{ + Use: "scan", +} + func init() { - flags := Cmd.Flags() - flags.BoolVarP(&jsonOut, "json", "j", jsonOut, "Output JSON") + Cmd.AddCommand(ObjectCommand) + Cmd.AddCommand(ScriptCommand) + + objFlags := ObjectCommand.Flags() + objFlags.BoolVarP(&objectsOnly, "objects", "s", objectsOnly, "Objects Only") + objFlags.BoolVarP(&jsonOut, "json", "j", jsonOut, "Output JSON") + + scriptFlags := ScriptCommand.Flags() + scriptFlags.StringVarP(&baseDir, "base", "b", baseDir, "Base Dir") + scriptFlags.BoolVarP(&jsonOut, "json", "j", jsonOut, "Output JSON") + } func ScanSifter(baseDir string, userFunc func(*playbook.Playbook)) { From 0b7a778e12f2c65945f676692efed60ea734a134 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 10 Jan 2024 09:25:54 -0800 Subject: [PATCH 03/11] Expanding introduction text --- website/content/docs.md | 110 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/website/content/docs.md b/website/content/docs.md index 0a5c479..d047861 100644 --- a/website/content/docs.md +++ b/website/content/docs.md @@ -11,12 +11,12 @@ menu: Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine -connects togeather arrays of transform steps into direct acylic graph that is processed +connects togeather arrays of transform steps into directed acylic graph that is processed in parallel. Example Message: -``` +```json { "firstName" : "bob", "age" : "25" @@ -37,3 +37,109 @@ be done in a transform pipeline these include: - Table based field translation - Outputing the message as a JSON Schema checked object + +# Script structure + +## Header +Each sifter file starts with a set of field to let the software know this is a sifter script, and not some random YAML file. There is also a `name` field for the script. This name will be used for output file creation and logging. Finally, there is an `outdir` that defines the directory where all output files will be placed. All paths are relative to the script file, so the `outdir` set to `my-results` will create the directory `my-results` in the same directory as the script file, regardless of where the sifter command is invoked. +```yaml +class : sifter +name: +outdir: +``` + +# Config and templating +The `config` section is a set of defined keys that are used throughout the rest of the script. + +Example config: +``` +config: + sqlite: ../../source/chembl/chembl_33/chembl_33_sqlite/chembl_33.db + uniprot2ensembl: ../../tables/uniprot2ensembl.tsv + schema: ../../schema/ +``` + +Various fields in the script file will be be parsed using a [Mustache](https://mustache.github.io/) template engine. For example, to access the various values within the config block, the template `{{config.sqlite}}`. + + +# Inputs +The input block defines the various data extractors that will be used to open resources and create streams of JSON messages for processing. The possible input engines include: + - AVRO + - JSON + - XML + - SQL-dump + - SQLite + - TSV/CSV + - GLOB + +For any other file types, there is also a plugin option to allow the user to call their own code for opening files. + +# Pipeline +The `pipelines` defined a set of named processing pipelines that can be used to transform data. Each pipeline starts with a `from` statement that defines where data comes from. It then defines a linear set of transforms that are chained togeather to do processing. Pipelines may used `emit` steps to output messages to disk. The possible data transform steps include: +- Accumulate +- Clean +- Distinct +- DropNull +- Field Parse +- Field Process +- Field Type +- Filter +- FlatMap +- GraphBuild +- Hash +- JSON Parse +- Lookup +- Value Mapping +- Object Validation +- Project +- Reduce +- Regex +- Split +- UUID Generation + +Additionally, users are able to define their one transform step types using the `plugin` step. + +# Example script +```yaml +class: sifter + +name: go +outdir: ../../output/go/ + +config: + oboFile: ../../source/go/go.obo + schema: ../../schema + +inputs: + oboData: + plugin: + commandLine: ../../util/obo_reader.py {{config.oboFile}} + +pipelines: + transform: + - from: oboData + - project: + mapping: + submitter_id: "{{row.id[0]}}" + case_id: "{{row.id[0]}}" + id: "{{row.id[0]}}" + go_id: "{{row.id[0]}}" + project_id: "gene_onotology" + namespace: "{{row.namespace[0]}}" + name: "{{row.name[0]}}" + - map: + method: fix + gpython: | + def fix(row): + row['definition'] = row['def'][0].strip('"') + if 'xref' not in row: + row['xref'] = [] + if 'synonym' not in row: + row['synonym'] = [] + return row + - objectValidate: + title: GeneOntologyTerm + schema: "{{config.schema}}" + - emit: + name: term +``` \ No newline at end of file From e97269013c88f53963f6aa4a1751ae3f7610a701 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 10 Jan 2024 10:11:14 -0800 Subject: [PATCH 04/11] Updating lists of inputs and transforms in docs --- docs/categories/index.xml | 9 +- docs/index.xml | 337 +----------------- docs/sitemap.xml | 75 +--- docs/tags/index.xml | 9 +- website/content/docs/inputs/plugin.md | 74 ++++ .../gripperLoad.md => transforms/flatmap.md} | 4 +- website/content/docs/transforms/plugin.md | 7 + website/content/docs/transforms/tableWrite.md | 7 + website/content/docs/transforms/uuid.md | 7 + 9 files changed, 115 insertions(+), 414 deletions(-) create mode 100644 website/content/docs/inputs/plugin.md rename website/content/docs/{inputs/gripperLoad.md => transforms/flatmap.md} (50%) create mode 100644 website/content/docs/transforms/plugin.md create mode 100644 website/content/docs/transforms/tableWrite.md create mode 100644 website/content/docs/transforms/uuid.md diff --git a/docs/categories/index.xml b/docs/categories/index.xml index d754d1a..02b45be 100644 --- a/docs/categories/index.xml +++ b/docs/categories/index.xml @@ -1,10 +1,11 @@ - Categories on Sifter - https://bmeg.github.io/sifter/categories/ - Recent content in Categories on Sifter + Categories on + /categories/ + Recent content in Categories on Hugo -- gohugo.io - en-us + en + diff --git a/docs/index.xml b/docs/index.xml index 2b517ba..86fd22d 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -1,338 +1,11 @@ - Sifter - https://bmeg.github.io/sifter/ - Recent content on Sifter + + / + Recent content on Hugo -- gohugo.io - en-us - - accumulate - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - accumulate Gather sequential rows into a single record, based on matching a field -Parameters name Type Description field string (field path) Field used to match rows dest string field to store accumulated records Example - accumulate: field: model_id dest: rows - - - - avroLoad - https://bmeg.github.io/sifter/docs/inputs/avroload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/avroload/ - avroLoad Load an AvroFile -Parameters name Description input Path to input file - - - - clean - https://bmeg.github.io/sifter/docs/transforms/clean/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/clean/ - clean Remove fields that don’t appear in the desingated list. -Parameters name Type Description fields [] string Fields to keep removeEmpty bool Fields with empty values will also be removed storeExtra string Field name to store removed fields Example - clean: fields: - id - synonyms - - - - debug - https://bmeg.github.io/sifter/docs/transforms/debug/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/debug/ - debug Print out copy of stream to logging -Parameters name Type Description label string Label for log output format bool Use multiline spaced output Example - debug: {} - - - - distinct - https://bmeg.github.io/sifter/docs/transforms/distinct/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/distinct/ - distinct Using templated value, allow only the first record for each distinct key -Parameters name Type Description value string Key used for distinct value Example - distinct: value: "{{row.key}}" - - - - embedded - https://bmeg.github.io/sifter/docs/inputs/embedded/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/embedded/ - embedded Load data from embedded structure -Example inputs: data: embedded: - { "name" : "Alice", "age": 28 } - { "name" : "Bob", "age": 27 } - - - - emit - https://bmeg.github.io/sifter/docs/transforms/emit/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/emit/ - emit Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz -Parameters name Type Description name string Name of emit value example - emit: name: protein_compound_association - - - - Example - https://bmeg.github.io/sifter/docs/example/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/example/ - Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. -The input file looks like: -ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it’s output directory. -name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. - - - - fieldParse - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - - - - - fieldProcess - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - fieldProcess Create stream of objects based on the contents of a field. If the selected field is an array each of the items in the array will become an independent row. -Parameters name Type Description field string Name of field to be processed mapping map[string]string Project templated values into child element itemField string If processing an array of non-dict elements, create a dict as {itemField:element} example - fieldProcess: field: portions mapping: sample: "{{row. - - - - fieldType - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - fieldType Set field to specific type, ie cast as float or integer -example - fieldType: t_depth: int t_ref_count: int t_alt_count: int n_depth: int n_ref_count: int n_alt_count: int start: int - - - - filter - https://bmeg.github.io/sifter/docs/transforms/filter/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/filter/ - filter Filter rows in stream using a number of different methods -Parameters name Type Description field string (field path) Field used to match rows value string (template string) Template string to match against match string String to match against check string How to check value, ’exists’ or ‘hasValue’ method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) Example Field based match -- filter: field: table match: source_statistics Check based match - - - - from - https://bmeg.github.io/sifter/docs/transforms/from/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/from/ - from Parmeters Name of data source -Example inputs: profileReader: tableLoad: input: "{{config.profiles}}" pipelines: profileProcess: - from: profileReader - - - - glob - https://bmeg.github.io/sifter/docs/inputs/glob/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/glob/ - glob Scan files using * based glob statement and open all files as input. -Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: "{{config.baseline}}/*.xml.gz" xmlLoad: {} - - - - graphBuild - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. -example - graphBuild: schema: "{{config.allelesSchema}}" title: Allele - - - - gripperLoad - https://bmeg.github.io/sifter/docs/inputs/gripperload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/gripperload/ - - - - - hash - https://bmeg.github.io/sifter/docs/transforms/hash/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/hash/ - hash Parameters name Type Description field string Field to store hash value value string Templated string of value to be hashed method string Hashing method: sha1/sha256/md5 example - hash: value: "{{row.contents}}" field: contents-sha1 method: sha1 - - - - Inputs - https://bmeg.github.io/sifter/docs/inputs/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/ - Every playbook consists of a series of inputs. - - - - jsonLoad - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. -Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: "{{config.casesJSON}}" - - - - lookup - https://bmeg.github.io/sifter/docs/transforms/lookup/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/lookup/ - lookup Using key from current row, get values from a reference source -Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Copy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table pipeline PipelineLookup Use output of a pipeline as a lookup table Example JSON file based lookup The JSON file defined by config. - - - - map - https://bmeg.github.io/sifter/docs/transforms/map/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/map/ - map Run function on every row -Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x["curve"].items(), key=lambda x:float(x[0])) x['dose_um'] = [] x['response'] = [] for d, r in s: try: dn = float(d) rn = float(r) x['dose_um'].append(dn) x['response'].append(rn) except ValueError: pass return x - - - - objectValidate - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - objectValidate Use JSON schema to validate row contents -parameters name Type Description title string Title of object to use for validation schema string Path to JSON schema definition example - objectValidate: title: Aliquot schema: "{{config.schema}}" - - - - Overview - https://bmeg.github.io/sifter/docs/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/ - Sifter pipelines Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine connects togeather arrays of transform steps into direct acylic graph that is processed in parallel. -Example Message: -{ "firstName" : "bob", "age" : "25" "friends" : [ "Max", "Alex"] } Once a stream of messages are produced, that can be run through a transform pipeline. - - - - Pipeline Steps - https://bmeg.github.io/sifter/docs/transforms/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/ - Transforms alter the data - - - - project - https://bmeg.github.io/sifter/docs/transforms/project/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/project/ - project Populate row with templated values -parameters name Type Description mapping map[string]any New fields to be generated from template rename map[string]string Rename field (no template engine) Example - project: mapping: type: sample id: "{{row.sample_id}}" - - - - reduce - https://bmeg.github.io/sifter/docs/transforms/reduce/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/reduce/ - reduce Using key from rows, reduce matched records into a single entry -Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { "compounds" : [] } gpython: | def merge(x,y): x["compounds"] = list(set(y["compounds"]+x["compounds"])) return x - - - - regexReplace - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - - - - - Sifter Pipeline File - https://bmeg.github.io/sifter/docs/playbook/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/playbook/ - Pipeline File An sifter pipeline file is in YAML format and describes an entire processing pipelines. If is composed of the following sections: config, inputs, pipelines, outputs. In addition, for tracking, the file will also include name and class entries. -class: sifter name: <script name> outdir: <where output files should go, relative to this file> config: <config key>: <config value> <config key>: <config value> # values that are referenced in pipeline parameters for # files will be treated like file paths and be # translated to full paths inputs: <input name>: <input driver>: <driver config> pipelines: <pipeline name>: # all pipelines must start with a from step - from: <name of input or pipeline> - <transform name>: <transform parameters> outputs: <output name>: <output driver>: <driver config> - - - - split - https://bmeg.github.io/sifter/docs/transforms/split/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/transforms/split/ - split Split a field using string sep -Parameters name Type Description field string Field to the split sep string String to use for splitting Example - split: field: methods sep: ";" - - - - sqldump - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - sqlDump Scan file produced produced from sqldump. -Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: "{{config.sql}}" tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles - - - - sqliteLoad - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - sqliteLoad Extract data from an sqlite file -Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: "{{config.sqlite}}" query: "select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO" - - - - tableLoad - https://bmeg.github.io/sifter/docs/inputs/tableload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/tableload/ - tableLoad Extract data from tabular file, includiong TSV and CSV files. -Parameters Name Type Description input string File to be transformed rowSkip int Number of header rows to skip columns []string Manually set names of columns extraColumns string Columns beyond originally declared columns will be placed in this array sep string Separator \t for TSVs or , for CSVs Example config: gafFile: ../../source/go/goa_human.gaf.gz inputs: gafLoad: tableLoad: input: "{{config.gafFile}}" columns: - db - id - symbol - qualifier - goID - reference - evidenceCode - from - aspect - name - synonym - objectType - taxon - date - assignedBy - extension - geneProduct - - - - xmlLoad - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - xmlLoad Load an XML file -Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: "{{config.xmlPath}}" - - + en + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 63f0281..cd5ab70 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,79 +2,10 @@ - https://bmeg.github.io/sifter/ - 0 + / - https://bmeg.github.io/sifter/docs/transforms/accumulate/ + /categories/ - https://bmeg.github.io/sifter/docs/inputs/avroload/ - - https://bmeg.github.io/sifter/categories/ - - https://bmeg.github.io/sifter/docs/transforms/clean/ - - https://bmeg.github.io/sifter/docs/transforms/debug/ - - https://bmeg.github.io/sifter/docs/transforms/distinct/ - - https://bmeg.github.io/sifter/docs/ - - https://bmeg.github.io/sifter/docs/inputs/embedded/ - - https://bmeg.github.io/sifter/docs/transforms/emit/ - - https://bmeg.github.io/sifter/docs/example/ - - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - - https://bmeg.github.io/sifter/docs/transforms/filter/ - - https://bmeg.github.io/sifter/docs/transforms/from/ - - https://bmeg.github.io/sifter/docs/inputs/glob/ - - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - - https://bmeg.github.io/sifter/docs/inputs/gripperload/ - - https://bmeg.github.io/sifter/docs/transforms/hash/ - - https://bmeg.github.io/sifter/docs/inputs/ - - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - - https://bmeg.github.io/sifter/docs/transforms/lookup/ - - https://bmeg.github.io/sifter/docs/transforms/map/ - - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - - https://bmeg.github.io/sifter/docs/ - - https://bmeg.github.io/sifter/docs/transforms/ - - https://bmeg.github.io/sifter/docs/transforms/project/ - - https://bmeg.github.io/sifter/docs/transforms/reduce/ - - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - - https://bmeg.github.io/sifter/docs/playbook/ - - https://bmeg.github.io/sifter/docs/transforms/split/ - - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - - https://bmeg.github.io/sifter/docs/inputs/tableload/ - - https://bmeg.github.io/sifter/tags/ - - https://bmeg.github.io/sifter/docs/inputs/xmlload/ + /tags/ diff --git a/docs/tags/index.xml b/docs/tags/index.xml index 54ec252..99ec5b4 100644 --- a/docs/tags/index.xml +++ b/docs/tags/index.xml @@ -1,10 +1,11 @@ - Tags on Sifter - https://bmeg.github.io/sifter/tags/ - Recent content in Tags on Sifter + Tags on + /tags/ + Recent content in Tags on Hugo -- gohugo.io - en-us + en + diff --git a/website/content/docs/inputs/plugin.md b/website/content/docs/inputs/plugin.md new file mode 100644 index 0000000..3b560e3 --- /dev/null +++ b/website/content/docs/inputs/plugin.md @@ -0,0 +1,74 @@ +--- +title: plugin +menu: + main: + parent: inputs + weight: 100 +--- + +# plugin +Run user program for customized data extraction. + +## Example + +```yaml +inputs: + oboData: + plugin: + commandLine: ../../util/obo_reader.py {{config.oboFile}} +``` + +The plugin program is expected to output JSON messages, one per line, to STDOUT that will then +be passed to the transform pipelines. + +## Example Plugin +The `obo_reader.py` plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the +records as single line JSON messages. +```python + #!/usr/bin/env python + +import re +import sys +import json + +re_section = re.compile(r'^\[(.*)\]') +re_field = re.compile(r'^(\w+): (.*)$') + +def obo_parse(handle): + rec = None + for line in handle: + res = re_section.search(line) + if res: + if rec is not None: + yield rec + rec = None + if res.group(1) == "Term": + rec = {"type": res.group(1)} + else: + if rec is not None: + res = re_field.search(line) + if res: + key = res.group(1) + val = res.group(2) + val = re.split(" ! | \(|\)", val) + val = ":".join(val[0:3]) + if key in rec: + rec[key].append(val) + else: + rec[key] = [val] + + if rec is not None: + yield rec + + +def unquote(s): + res = re.search(r'"(.*)"', s) + if res: + return res.group(1) + return s + + +with open(sys.argv[1]) as handle: + for rec in obo_parse(handle): + print(json.dumps(rec)) +``` \ No newline at end of file diff --git a/website/content/docs/inputs/gripperLoad.md b/website/content/docs/transforms/flatmap.md similarity index 50% rename from website/content/docs/inputs/gripperLoad.md rename to website/content/docs/transforms/flatmap.md index ac738a1..c880db2 100644 --- a/website/content/docs/inputs/gripperLoad.md +++ b/website/content/docs/transforms/flatmap.md @@ -1,7 +1,7 @@ --- -title: gripperLoad +title: flatMap menu: main: - parent: inputs + parent: transforms weight: 100 --- diff --git a/website/content/docs/transforms/plugin.md b/website/content/docs/transforms/plugin.md new file mode 100644 index 0000000..09cbf31 --- /dev/null +++ b/website/content/docs/transforms/plugin.md @@ -0,0 +1,7 @@ +--- +title: plugin +menu: + main: + parent: transforms + weight: 100 +--- diff --git a/website/content/docs/transforms/tableWrite.md b/website/content/docs/transforms/tableWrite.md new file mode 100644 index 0000000..7a39648 --- /dev/null +++ b/website/content/docs/transforms/tableWrite.md @@ -0,0 +1,7 @@ +--- +title: tableWrite +menu: + main: + parent: transforms + weight: 100 +--- diff --git a/website/content/docs/transforms/uuid.md b/website/content/docs/transforms/uuid.md new file mode 100644 index 0000000..0127b15 --- /dev/null +++ b/website/content/docs/transforms/uuid.md @@ -0,0 +1,7 @@ +--- +title: uuid +menu: + main: + parent: transforms + weight: 100 +--- From dc84e63a48f4537feeba9672474be6bb63696376 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 10 Jan 2024 13:52:32 -0800 Subject: [PATCH 05/11] Adding more to the docs --- docs/categories/index.xml | 10 +- docs/docs/example/index.html | 42 +- docs/docs/index.html | 183 +++++++- docs/docs/index.xml | 195 +++----- docs/docs/inputs/avroload/index.html | 42 +- docs/docs/inputs/embedded/index.html | 42 +- docs/docs/inputs/glob/index.html | 42 +- docs/docs/inputs/index.html | 42 +- docs/docs/inputs/jsonload/index.html | 46 +- docs/docs/inputs/plugin/index.html | 428 ++++++++++++++++++ docs/docs/inputs/sqldump/index.html | 42 +- docs/docs/inputs/sqliteload/index.html | 42 +- docs/docs/inputs/tableload/index.html | 42 +- docs/docs/inputs/xmlload/index.html | 42 +- docs/docs/transforms/accumulate/index.html | 42 +- docs/docs/transforms/clean/index.html | 42 +- docs/docs/transforms/debug/index.html | 42 +- docs/docs/transforms/distinct/index.html | 42 +- docs/docs/transforms/emit/index.html | 42 +- docs/docs/transforms/fieldparse/index.html | 42 +- docs/docs/transforms/fieldprocess/index.html | 42 +- docs/docs/transforms/fieldtype/index.html | 42 +- docs/docs/transforms/filter/index.html | 42 +- docs/docs/transforms/flatmap/index.html | 370 +++++++++++++++ docs/docs/transforms/from/index.html | 42 +- docs/docs/transforms/graphbuild/index.html | 42 +- docs/docs/transforms/hash/index.html | 42 +- docs/docs/transforms/index.html | 42 +- docs/docs/transforms/lookup/index.html | 42 +- docs/docs/transforms/map/index.html | 42 +- .../docs/transforms/objectvalidate/index.html | 42 +- docs/docs/transforms/plugin/index.html | 370 +++++++++++++++ docs/docs/transforms/project/index.html | 42 +- docs/docs/transforms/reduce/index.html | 42 +- docs/docs/transforms/regexreplace/index.html | 42 +- docs/docs/transforms/split/index.html | 42 +- docs/docs/transforms/tablewrite/index.html | 370 +++++++++++++++ docs/docs/transforms/uuid/index.html | 370 +++++++++++++++ docs/index.html | 8 +- docs/index.xml | 262 ++++++++++- docs/sifter_example.png | Bin 0 -> 124447 bytes docs/sitemap.xml | 80 +++- docs/tags/index.xml | 10 +- transform/reduce.go | 2 + website/content/_index.md | 2 + website/content/docs.md | 39 ++ website/content/docs/playbook.md | 46 -- website/layouts/index.html | 5 +- website/static/sifter_example.png | Bin 0 -> 124447 bytes 49 files changed, 3463 insertions(+), 551 deletions(-) create mode 100644 docs/docs/inputs/plugin/index.html create mode 100644 docs/docs/transforms/flatmap/index.html create mode 100644 docs/docs/transforms/plugin/index.html create mode 100644 docs/docs/transforms/tablewrite/index.html create mode 100644 docs/docs/transforms/uuid/index.html create mode 100644 docs/sifter_example.png delete mode 100644 website/content/docs/playbook.md create mode 100644 website/static/sifter_example.png diff --git a/docs/categories/index.xml b/docs/categories/index.xml index 02b45be..4b26c88 100644 --- a/docs/categories/index.xml +++ b/docs/categories/index.xml @@ -1,11 +1,11 @@ - Categories on - /categories/ - Recent content in Categories on + Categories on Sifter + https://bmeg.github.io/sifter/categories/ + Recent content in Categories on Sifter Hugo -- gohugo.io - en - + en-us + diff --git a/docs/docs/example/index.html b/docs/docs/example/index.html index a489c50..552309e 100644 --- a/docs/docs/example/index.html +++ b/docs/docs/example/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/index.html b/docs/docs/index.html index 5bc6ab4..6a0af11 100644 --- a/docs/docs/index.html +++ b/docs/docs/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + @@ -345,15 +365,15 @@

    Sifter pipelines

    Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine -connects togeather arrays of transform steps into direct acylic graph that is processed +connects togeather arrays of transform steps into directed acylic graph that is processed in parallel.

    Example Message:

    -
    {
    -  "firstName" : "bob",
    -  "age" : "25"
    -  "friends" : [ "Max", "Alex"]
    -}
    -

    Once a stream of messages are produced, that can be run through a transform +

    {
    +  "firstName" : "bob",
    +  "age" : "25"
    +  "friends" : [ "Max", "Alex"]
    +}
    +

    Once a stream of messages are produced, that can be run through a transform pipeline. A transform pipeline is an array of transform steps, each transform step can represent a different way to alter the data. The array of transforms link togeather into a pipe that makes multiple alterations to messages as they are @@ -366,7 +386,132 @@

    Sifter pipelines

  • Table based field translation
  • Outputing the message as a JSON Schema checked object
  • - +

    Script structure

    +

    Pipeline File

    +

    An sifter pipeline file is in YAML format and describes an entire processing pipelines. +If is composed of the following sections: config, inputs, pipelines, outputs. In addition, +for tracking, the file will also include name and class entries.

    +
    
    +class: sifter
    +name: <script name>
    +outdir: <where output files should go, relative to this file>
    +
    +config:
    +  <config key>: <config value>
    +  <config key>: <config value> 
    +  # values that are referenced in pipeline parameters for 
    +  # files will be treated like file paths and be 
    +  # translated to full paths
    +
    +inputs:
    +  <input name>:
    +    <input driver>:
    +      <driver config>
    +
    +pipelines:
    +  <pipeline name>:
    +    # all pipelines must start with a from step
    +    - from: <name of input or pipeline> 
    +    - <transform name>:
    +       <transform parameters>
    +
    +outputs:
    +  <output name>:
    +    <output driver>:
    +      <driver config>
    +
    +

    Each sifter file starts with a set of field to let the software know this is a sifter script, and not some random YAML file. There is also a name field for the script. This name will be used for output file creation and logging. Finally, there is an outdir that defines the directory where all output files will be placed. All paths are relative to the script file, so the outdir set to my-results will create the directory my-results in the same directory as the script file, regardless of where the sifter command is invoked.

    +
    class : sifter
    +name: <name of script>
    +outdir: <where files should be stored>
    +

    Config and templating

    +

    The config section is a set of defined keys that are used throughout the rest of the script.

    +

    Example config:

    +
    config:
    +  sqlite:  ../../source/chembl/chembl_33/chembl_33_sqlite/chembl_33.db
    +  uniprot2ensembl: ../../tables/uniprot2ensembl.tsv
    +  schema: ../../schema/
    +

    Various fields in the script file will be be parsed using a Mustache template engine. For example, to access the various values within the config block, the template {{config.sqlite}}.

    +

    Inputs

    +

    The input block defines the various data extractors that will be used to open resources and create streams of JSON messages for processing. The possible input engines include:

    +
      +
    • AVRO
    • +
    • JSON
    • +
    • XML
    • +
    • SQL-dump
    • +
    • SQLite
    • +
    • TSV/CSV
    • +
    • GLOB
    • +
    +

    For any other file types, there is also a plugin option to allow the user to call their own code for opening files.

    +

    Pipeline

    +

    The pipelines defined a set of named processing pipelines that can be used to transform data. Each pipeline starts with a from statement that defines where data comes from. It then defines a linear set of transforms that are chained togeather to do processing. Pipelines may used emit steps to output messages to disk. The possible data transform steps include:

    +
      +
    • Accumulate
    • +
    • Clean
    • +
    • Distinct
    • +
    • DropNull
    • +
    • Field Parse
    • +
    • Field Process
    • +
    • Field Type
    • +
    • Filter
    • +
    • FlatMap
    • +
    • GraphBuild
    • +
    • Hash
    • +
    • JSON Parse
    • +
    • Lookup
    • +
    • Value Mapping
    • +
    • Object Validation
    • +
    • Project
    • +
    • Reduce
    • +
    • Regex
    • +
    • Split
    • +
    • UUID Generation
    • +
    +

    Additionally, users are able to define their one transform step types using the plugin step.

    +

    Example script

    +
    class: sifter
    +
    +name: go
    +outdir: ../../output/go/
    +
    +config:
    +  oboFile: ../../source/go/go.obo
    +  schema: ../../schema
    +
    +inputs:
    +  oboData:
    +    plugin:
    +      commandLine: ../../util/obo_reader.py {{config.oboFile}}
    +
    +pipelines:
    +  transform:
    +    - from: oboData
    +    - project:
    +        mapping:
    +          submitter_id: "{{row.id[0]}}"
    +          case_id: "{{row.id[0]}}"
    +          id: "{{row.id[0]}}"
    +          go_id: "{{row.id[0]}}"
    +          project_id: "gene_onotology"
    +          namespace: "{{row.namespace[0]}}"
    +          name: "{{row.name[0]}}"
    +    - map: 
    +        method: fix
    +        gpython: | 
    +          def fix(row):
    +            row['definition'] = row['def'][0].strip('"')
    +            if 'xref' not in row:
    +              row['xref'] = []
    +            if 'synonym' not in row:
    +              row['synonym'] = []
    +            return row
    +    - objectValidate:
    +        title: GeneOntologyTerm
    +        schema: "{{config.schema}}"
    +    - emit:
    +        name: term
    +
    diff --git a/docs/docs/index.xml b/docs/docs/index.xml index 36e5687..fd78822 100644 --- a/docs/docs/index.xml +++ b/docs/docs/index.xml @@ -5,323 +5,252 @@ https://bmeg.github.io/sifter/docs/ Recent content in Docs on Sifter Hugo -- gohugo.io - en-us + en-us + accumulate https://bmeg.github.io/sifter/docs/transforms/accumulate/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/accumulate/ - accumulate Gather sequential rows into a single record, based on matching a field -Parameters name Type Description field string (field path) Field used to match rows dest string field to store accumulated records Example - accumulate: field: model_id dest: rows + accumulate Gather sequential rows into a single record, based on matching a field Parameters name Type Description field string (field path) Field used to match rows dest string field to store accumulated records Example - accumulate: field: model_id dest: rows - avroLoad https://bmeg.github.io/sifter/docs/inputs/avroload/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/avroload/ - avroLoad Load an AvroFile -Parameters name Description input Path to input file + avroLoad Load an AvroFile Parameters name Description input Path to input file - clean https://bmeg.github.io/sifter/docs/transforms/clean/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/clean/ - clean Remove fields that don&rsquo;t appear in the desingated list. -Parameters name Type Description fields [] string Fields to keep removeEmpty bool Fields with empty values will also be removed storeExtra string Field name to store removed fields Example - clean: fields: - id - synonyms + clean Remove fields that don&rsquo;t appear in the desingated list. Parameters name Type Description fields [] string Fields to keep removeEmpty bool Fields with empty values will also be removed storeExtra string Field name to store removed fields Example - clean: fields: - id - synonyms - debug https://bmeg.github.io/sifter/docs/transforms/debug/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/debug/ - debug Print out copy of stream to logging -Parameters name Type Description label string Label for log output format bool Use multiline spaced output Example - debug: {} + debug Print out copy of stream to logging Parameters name Type Description label string Label for log output format bool Use multiline spaced output Example - debug: {} - distinct https://bmeg.github.io/sifter/docs/transforms/distinct/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/distinct/ - distinct Using templated value, allow only the first record for each distinct key -Parameters name Type Description value string Key used for distinct value Example - distinct: value: &#34;{{row.key}}&#34; + distinct Using templated value, allow only the first record for each distinct key Parameters name Type Description value string Key used for distinct value Example - distinct: value: &#34;{{row.key}}&#34; - embedded https://bmeg.github.io/sifter/docs/inputs/embedded/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/embedded/ - embedded Load data from embedded structure -Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } + embedded Load data from embedded structure Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } - emit https://bmeg.github.io/sifter/docs/transforms/emit/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/emit/ - emit Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz -Parameters name Type Description name string Name of emit value example - emit: name: protein_compound_association + emit Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz Parameters name Type Description name string Name of emit value example - emit: name: protein_compound_association - Example https://bmeg.github.io/sifter/docs/example/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/example/ - Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. -The input file looks like: -ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. -name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. + Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. The input file looks like: ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. - fieldParse https://bmeg.github.io/sifter/docs/transforms/fieldparse/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldparse/ - fieldProcess https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ - fieldProcess Create stream of objects based on the contents of a field. If the selected field is an array each of the items in the array will become an independent row. -Parameters name Type Description field string Name of field to be processed mapping map[string]string Project templated values into child element itemField string If processing an array of non-dict elements, create a dict as {itemField:element} example - fieldProcess: field: portions mapping: sample: &#34;{{row. + fieldProcess Create stream of objects based on the contents of a field. If the selected field is an array each of the items in the array will become an independent row. Parameters name Type Description field string Name of field to be processed mapping map[string]string Project templated values into child element itemField string If processing an array of non-dict elements, create a dict as {itemField:element} example - fieldProcess: field: portions mapping: sample: &#34;{{row. - fieldType https://bmeg.github.io/sifter/docs/transforms/fieldtype/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/fieldtype/ - fieldType Set field to specific type, ie cast as float or integer -example - fieldType: t_depth: int t_ref_count: int t_alt_count: int n_depth: int n_ref_count: int n_alt_count: int start: int + fieldType Set field to specific type, ie cast as float or integer example - fieldType: t_depth: int t_ref_count: int t_alt_count: int n_depth: int n_ref_count: int n_alt_count: int start: int - filter https://bmeg.github.io/sifter/docs/transforms/filter/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/filter/ - filter Filter rows in stream using a number of different methods -Parameters name Type Description field string (field path) Field used to match rows value string (template string) Template string to match against match string String to match against check string How to check value, &rsquo;exists&rsquo; or &lsquo;hasValue&rsquo; method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) Example Field based match -- filter: field: table match: source_statistics Check based match + filter Filter rows in stream using a number of different methods Parameters name Type Description field string (field path) Field used to match rows value string (template string) Template string to match against match string String to match against check string How to check value, &rsquo;exists&rsquo; or &lsquo;hasValue&rsquo; method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) Example Field based match - filter: field: table match: source_statistics Check based match + + + flatMap + https://bmeg.github.io/sifter/docs/transforms/flatmap/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/flatmap/ + - from https://bmeg.github.io/sifter/docs/transforms/from/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/from/ - from Parmeters Name of data source -Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader + from Parmeters Name of data source Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader - glob https://bmeg.github.io/sifter/docs/inputs/glob/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/glob/ - glob Scan files using * based glob statement and open all files as input. -Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} + glob Scan files using * based glob statement and open all files as input. Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} - graphBuild https://bmeg.github.io/sifter/docs/transforms/graphbuild/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/graphbuild/ - graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. -example - graphBuild: schema: &#34;{{config.allelesSchema}}&#34; title: Allele + graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. example - graphBuild: schema: &#34;{{config.allelesSchema}}&#34; title: Allele - - - gripperLoad - https://bmeg.github.io/sifter/docs/inputs/gripperload/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/inputs/gripperload/ - - - hash https://bmeg.github.io/sifter/docs/transforms/hash/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/hash/ hash Parameters name Type Description field string Field to store hash value value string Templated string of value to be hashed method string Hashing method: sha1/sha256/md5 example - hash: value: &#34;{{row.contents}}&#34; field: contents-sha1 method: sha1 - Inputs https://bmeg.github.io/sifter/docs/inputs/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/ Every playbook consists of a series of inputs. - jsonLoad https://bmeg.github.io/sifter/docs/inputs/jsonload/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/jsonload/ - jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. -Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; + jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; - lookup https://bmeg.github.io/sifter/docs/transforms/lookup/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/lookup/ - lookup Using key from current row, get values from a reference source -Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Copy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table pipeline PipelineLookup Use output of a pipeline as a lookup table Example JSON file based lookup The JSON file defined by config. + lookup Using key from current row, get values from a reference source Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Copy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table pipeline PipelineLookup Use output of a pipeline as a lookup table Example JSON file based lookup The JSON file defined by config. - map https://bmeg.github.io/sifter/docs/transforms/map/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/map/ - map Run function on every row -Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x + map Run function on every row Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x - objectValidate https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ - objectValidate Use JSON schema to validate row contents -parameters name Type Description title string Title of object to use for validation schema string Path to JSON schema definition example - objectValidate: title: Aliquot schema: &#34;{{config.schema}}&#34; + objectValidate Use JSON schema to validate row contents parameters name Type Description title string Title of object to use for validation schema string Path to JSON schema definition example - objectValidate: title: Aliquot schema: &#34;{{config.schema}}&#34; - Pipeline Steps https://bmeg.github.io/sifter/docs/transforms/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/ Transforms alter the data - + + plugin + https://bmeg.github.io/sifter/docs/inputs/plugin/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/plugin/ + plugin Run user program for customized data extraction. Example inputs: oboData: plugin: commandLine: ../../util/obo_reader.py {{config.oboFile}} The plugin program is expected to output JSON messages, one per line, to STDOUT that will then be passed to the transform pipelines. Example Plugin The obo_reader.py plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the records as single line JSON messages. #!/usr/bin/env python import re import sys import json re_section = re. + + + plugin + https://bmeg.github.io/sifter/docs/transforms/plugin/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/plugin/ + + project https://bmeg.github.io/sifter/docs/transforms/project/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/project/ - project Populate row with templated values -parameters name Type Description mapping map[string]any New fields to be generated from template rename map[string]string Rename field (no template engine) Example - project: mapping: type: sample id: &#34;{{row.sample_id}}&#34; + project Populate row with templated values parameters name Type Description mapping map[string]any New fields to be generated from template rename map[string]string Rename field (no template engine) Example - project: mapping: type: sample id: &#34;{{row.sample_id}}&#34; - reduce https://bmeg.github.io/sifter/docs/transforms/reduce/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/reduce/ - reduce Using key from rows, reduce matched records into a single entry -Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x + reduce Using key from rows, reduce matched records into a single entry Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x - regexReplace https://bmeg.github.io/sifter/docs/transforms/regexreplace/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/regexreplace/ - - - Sifter Pipeline File - https://bmeg.github.io/sifter/docs/playbook/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://bmeg.github.io/sifter/docs/playbook/ - Pipeline File An sifter pipeline file is in YAML format and describes an entire processing pipelines. If is composed of the following sections: config, inputs, pipelines, outputs. In addition, for tracking, the file will also include name and class entries. -class: sifter name: &lt;script name&gt; outdir: &lt;where output files should go, relative to this file&gt; config: &lt;config key&gt;: &lt;config value&gt; &lt;config key&gt;: &lt;config value&gt; # values that are referenced in pipeline parameters for # files will be treated like file paths and be # translated to full paths inputs: &lt;input name&gt;: &lt;input driver&gt;: &lt;driver config&gt; pipelines: &lt;pipeline name&gt;: # all pipelines must start with a from step - from: &lt;name of input or pipeline&gt; - &lt;transform name&gt;: &lt;transform parameters&gt; outputs: &lt;output name&gt;: &lt;output driver&gt;: &lt;driver config&gt; - - split https://bmeg.github.io/sifter/docs/transforms/split/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/transforms/split/ - split Split a field using string sep -Parameters name Type Description field string Field to the split sep string String to use for splitting Example - split: field: methods sep: &#34;;&#34; + split Split a field using string sep Parameters name Type Description field string Field to the split sep string String to use for splitting Example - split: field: methods sep: &#34;;&#34; - sqldump https://bmeg.github.io/sifter/docs/inputs/sqldump/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/sqldump/ - sqlDump Scan file produced produced from sqldump. -Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles + sqlDump Scan file produced produced from sqldump. Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles - sqliteLoad https://bmeg.github.io/sifter/docs/inputs/sqliteload/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/sqliteload/ - sqliteLoad Extract data from an sqlite file -Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; + sqliteLoad Extract data from an sqlite file Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; - tableLoad https://bmeg.github.io/sifter/docs/inputs/tableload/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/tableload/ - tableLoad Extract data from tabular file, includiong TSV and CSV files. -Parameters Name Type Description input string File to be transformed rowSkip int Number of header rows to skip columns []string Manually set names of columns extraColumns string Columns beyond originally declared columns will be placed in this array sep string Separator \t for TSVs or , for CSVs Example config: gafFile: ../../source/go/goa_human.gaf.gz inputs: gafLoad: tableLoad: input: &#34;{{config.gafFile}}&#34; columns: - db - id - symbol - qualifier - goID - reference - evidenceCode - from - aspect - name - synonym - objectType - taxon - date - assignedBy - extension - geneProduct + tableLoad Extract data from tabular file, includiong TSV and CSV files. Parameters Name Type Description input string File to be transformed rowSkip int Number of header rows to skip columns []string Manually set names of columns extraColumns string Columns beyond originally declared columns will be placed in this array sep string Separator \t for TSVs or , for CSVs Example config: gafFile: ../../source/go/goa_human.gaf.gz inputs: gafLoad: tableLoad: input: &#34;{{config.gafFile}}&#34; columns: - db - id - symbol - qualifier - goID - reference - evidenceCode - from - aspect - name - synonym - objectType - taxon - date - assignedBy - extension - geneProduct + + + tableWrite + https://bmeg.github.io/sifter/docs/transforms/tablewrite/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/tablewrite/ + + + + uuid + https://bmeg.github.io/sifter/docs/transforms/uuid/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/uuid/ + - xmlLoad https://bmeg.github.io/sifter/docs/inputs/xmlload/ Mon, 01 Jan 0001 00:00:00 +0000 - https://bmeg.github.io/sifter/docs/inputs/xmlload/ - xmlLoad Load an XML file -Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; + xmlLoad Load an XML file Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; - diff --git a/docs/docs/inputs/avroload/index.html b/docs/docs/inputs/avroload/index.html index c6c90e0..19bba10 100644 --- a/docs/docs/inputs/avroload/index.html +++ b/docs/docs/inputs/avroload/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/embedded/index.html b/docs/docs/inputs/embedded/index.html index 6219874..63084d3 100644 --- a/docs/docs/inputs/embedded/index.html +++ b/docs/docs/inputs/embedded/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/glob/index.html b/docs/docs/inputs/glob/index.html index 566ba4c..3bc2c33 100644 --- a/docs/docs/inputs/glob/index.html +++ b/docs/docs/inputs/glob/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/index.html b/docs/docs/inputs/index.html index 98cd3d4..8290b48 100644 --- a/docs/docs/inputs/index.html +++ b/docs/docs/inputs/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/jsonload/index.html b/docs/docs/inputs/jsonload/index.html index 3120406..47ad543 100644 --- a/docs/docs/inputs/jsonload/index.html +++ b/docs/docs/inputs/jsonload/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/plugin/index.html b/docs/docs/inputs/plugin/index.html new file mode 100644 index 0000000..3bd574a --- /dev/null +++ b/docs/docs/inputs/plugin/index.html @@ -0,0 +1,428 @@ + + + + + + + + + + + plugin · Sifter + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    +

    plugin

    +

    Run user program for customized data extraction.

    +

    Example

    +
    inputs:
    +  oboData:
    +    plugin:
    +      commandLine: ../../util/obo_reader.py {{config.oboFile}}
    +

    The plugin program is expected to output JSON messages, one per line, to STDOUT that will then +be passed to the transform pipelines.

    +

    Example Plugin

    +

    The obo_reader.py plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the +records as single line JSON messages.

    +
     #!/usr/bin/env python
    +
    +import re
    +import sys
    +import json
    +
    +re_section = re.compile(r'^\[(.*)\]')
    +re_field = re.compile(r'^(\w+): (.*)$')
    +
    +def obo_parse(handle):
    +    rec = None
    +    for line in handle:
    +        res = re_section.search(line)
    +        if res:
    +            if rec is not None:
    +                yield rec
    +            rec = None
    +            if res.group(1) == "Term":
    +                rec = {"type": res.group(1)}
    +        else:
    +            if rec is not None:
    +                res = re_field.search(line)
    +                if res:
    +                    key = res.group(1)
    +                    val = res.group(2)
    +                    val = re.split(" ! | \(|\)", val)
    +                    val = ":".join(val[0:3])
    +                    if key in rec:
    +                        rec[key].append(val)
    +                    else:
    +                        rec[key] = [val]
    +
    +    if rec is not None:
    +        yield rec
    +
    +
    +def unquote(s):
    +    res = re.search(r'"(.*)"', s)
    +    if res:
    +        return res.group(1)
    +    return s
    +
    +
    +with open(sys.argv[1]) as handle:
    +    for rec in obo_parse(handle):
    +        print(json.dumps(rec))
    +
    +
    + +
    + + diff --git a/docs/docs/inputs/sqldump/index.html b/docs/docs/inputs/sqldump/index.html index 99d9ecc..bad87f6 100644 --- a/docs/docs/inputs/sqldump/index.html +++ b/docs/docs/inputs/sqldump/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/sqliteload/index.html b/docs/docs/inputs/sqliteload/index.html index c3c4c01..bd4cd3c 100644 --- a/docs/docs/inputs/sqliteload/index.html +++ b/docs/docs/inputs/sqliteload/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/tableload/index.html b/docs/docs/inputs/tableload/index.html index abcf3ab..29d4ff7 100644 --- a/docs/docs/inputs/tableload/index.html +++ b/docs/docs/inputs/tableload/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/inputs/xmlload/index.html b/docs/docs/inputs/xmlload/index.html index d95459c..b935725 100644 --- a/docs/docs/inputs/xmlload/index.html +++ b/docs/docs/inputs/xmlload/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/accumulate/index.html b/docs/docs/transforms/accumulate/index.html index cc10e90..94452f0 100644 --- a/docs/docs/transforms/accumulate/index.html +++ b/docs/docs/transforms/accumulate/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/clean/index.html b/docs/docs/transforms/clean/index.html index a1a7609..1d2b108 100644 --- a/docs/docs/transforms/clean/index.html +++ b/docs/docs/transforms/clean/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/debug/index.html b/docs/docs/transforms/debug/index.html index 84785b0..f3528d1 100644 --- a/docs/docs/transforms/debug/index.html +++ b/docs/docs/transforms/debug/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/distinct/index.html b/docs/docs/transforms/distinct/index.html index 0877ee0..dd6a9cb 100644 --- a/docs/docs/transforms/distinct/index.html +++ b/docs/docs/transforms/distinct/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/emit/index.html b/docs/docs/transforms/emit/index.html index e09e094..4547e42 100644 --- a/docs/docs/transforms/emit/index.html +++ b/docs/docs/transforms/emit/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/fieldparse/index.html b/docs/docs/transforms/fieldparse/index.html index de593a2..8653473 100644 --- a/docs/docs/transforms/fieldparse/index.html +++ b/docs/docs/transforms/fieldparse/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/fieldprocess/index.html b/docs/docs/transforms/fieldprocess/index.html index e740c6b..e2bffa4 100644 --- a/docs/docs/transforms/fieldprocess/index.html +++ b/docs/docs/transforms/fieldprocess/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/fieldtype/index.html b/docs/docs/transforms/fieldtype/index.html index 6b1d126..0f4eebc 100644 --- a/docs/docs/transforms/fieldtype/index.html +++ b/docs/docs/transforms/fieldtype/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/filter/index.html b/docs/docs/transforms/filter/index.html index b1e5156..1aa627e 100644 --- a/docs/docs/transforms/filter/index.html +++ b/docs/docs/transforms/filter/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/flatmap/index.html b/docs/docs/transforms/flatmap/index.html new file mode 100644 index 0000000..29fc1b1 --- /dev/null +++ b/docs/docs/transforms/flatmap/index.html @@ -0,0 +1,370 @@ + + + + + + + + + + + flatMap · Sifter + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + +
    + +
    + + diff --git a/docs/docs/transforms/from/index.html b/docs/docs/transforms/from/index.html index 5098871..bbdc204 100644 --- a/docs/docs/transforms/from/index.html +++ b/docs/docs/transforms/from/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/graphbuild/index.html b/docs/docs/transforms/graphbuild/index.html index 9b84821..bb491fb 100644 --- a/docs/docs/transforms/graphbuild/index.html +++ b/docs/docs/transforms/graphbuild/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/hash/index.html b/docs/docs/transforms/hash/index.html index 39d1cc9..9672d81 100644 --- a/docs/docs/transforms/hash/index.html +++ b/docs/docs/transforms/hash/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/index.html b/docs/docs/transforms/index.html index dd20f11..7fb9613 100644 --- a/docs/docs/transforms/index.html +++ b/docs/docs/transforms/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/lookup/index.html b/docs/docs/transforms/lookup/index.html index 3225c3b..eb4c6ad 100644 --- a/docs/docs/transforms/lookup/index.html +++ b/docs/docs/transforms/lookup/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/map/index.html b/docs/docs/transforms/map/index.html index 4ccfabd..14f7abb 100644 --- a/docs/docs/transforms/map/index.html +++ b/docs/docs/transforms/map/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/objectvalidate/index.html b/docs/docs/transforms/objectvalidate/index.html index 87e2592..d318599 100644 --- a/docs/docs/transforms/objectvalidate/index.html +++ b/docs/docs/transforms/objectvalidate/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/plugin/index.html b/docs/docs/transforms/plugin/index.html new file mode 100644 index 0000000..dc86b99 --- /dev/null +++ b/docs/docs/transforms/plugin/index.html @@ -0,0 +1,370 @@ + + + + + + + + + + + plugin · Sifter + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + +
    + +
    + + diff --git a/docs/docs/transforms/project/index.html b/docs/docs/transforms/project/index.html index c5aaaa2..9cb432d 100644 --- a/docs/docs/transforms/project/index.html +++ b/docs/docs/transforms/project/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/reduce/index.html b/docs/docs/transforms/reduce/index.html index 6b90649..378b3b6 100644 --- a/docs/docs/transforms/reduce/index.html +++ b/docs/docs/transforms/reduce/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/regexreplace/index.html b/docs/docs/transforms/regexreplace/index.html index dda8941..a22a736 100644 --- a/docs/docs/transforms/regexreplace/index.html +++ b/docs/docs/transforms/regexreplace/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/split/index.html b/docs/docs/transforms/split/index.html index a65587d..69b03a0 100644 --- a/docs/docs/transforms/split/index.html +++ b/docs/docs/transforms/split/index.html @@ -57,13 +57,6 @@ >Overview -
  • - - Sifter Pipeline File
  • - -
  • @@ -245,6 +238,15 @@ +
  • + +
  • +
  • +
  • + +
  • + +
  • + +
  • + diff --git a/docs/docs/transforms/tablewrite/index.html b/docs/docs/transforms/tablewrite/index.html new file mode 100644 index 0000000..e937dbc --- /dev/null +++ b/docs/docs/transforms/tablewrite/index.html @@ -0,0 +1,370 @@ + + + + + + + + + + + tableWrite · Sifter + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + +
    + +
    + + diff --git a/docs/docs/transforms/uuid/index.html b/docs/docs/transforms/uuid/index.html new file mode 100644 index 0000000..a1769da --- /dev/null +++ b/docs/docs/transforms/uuid/index.html @@ -0,0 +1,370 @@ + + + + + + + + + + + uuid · Sifter + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + +
    + +
    + + diff --git a/docs/index.html b/docs/index.html index 8b58546..120ebf1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,7 +2,7 @@ - + @@ -45,6 +45,9 @@ + + +
    @@ -57,10 +60,11 @@

    SIFTER

    files and external databases. It includes a pipeline description language to define a set of Transform steps to create object messages that can be validated using a JSON schema data.

    +

    Example of sifter code

    - +
    diff --git a/docs/index.xml b/docs/index.xml index 86fd22d..b64b623 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -1,11 +1,263 @@ - - / - Recent content on + Sifter + https://bmeg.github.io/sifter/ + Recent content on Sifter Hugo -- gohugo.io - en - + en-us + + + accumulate + https://bmeg.github.io/sifter/docs/transforms/accumulate/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/accumulate/ + accumulate Gather sequential rows into a single record, based on matching a field Parameters name Type Description field string (field path) Field used to match rows dest string field to store accumulated records Example - accumulate: field: model_id dest: rows + + + avroLoad + https://bmeg.github.io/sifter/docs/inputs/avroload/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/avroload/ + avroLoad Load an AvroFile Parameters name Description input Path to input file + + + clean + https://bmeg.github.io/sifter/docs/transforms/clean/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/clean/ + clean Remove fields that don&rsquo;t appear in the desingated list. Parameters name Type Description fields [] string Fields to keep removeEmpty bool Fields with empty values will also be removed storeExtra string Field name to store removed fields Example - clean: fields: - id - synonyms + + + debug + https://bmeg.github.io/sifter/docs/transforms/debug/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/debug/ + debug Print out copy of stream to logging Parameters name Type Description label string Label for log output format bool Use multiline spaced output Example - debug: {} + + + distinct + https://bmeg.github.io/sifter/docs/transforms/distinct/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/distinct/ + distinct Using templated value, allow only the first record for each distinct key Parameters name Type Description value string Key used for distinct value Example - distinct: value: &#34;{{row.key}}&#34; + + + embedded + https://bmeg.github.io/sifter/docs/inputs/embedded/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/embedded/ + embedded Load data from embedded structure Example inputs: data: embedded: - { &#34;name&#34; : &#34;Alice&#34;, &#34;age&#34;: 28 } - { &#34;name&#34; : &#34;Bob&#34;, &#34;age&#34;: 27 } + + + emit + https://bmeg.github.io/sifter/docs/transforms/emit/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/emit/ + emit Send data to output file. The naming of the file is outdir/script name.pipeline name.emit name.json.gz Parameters name Type Description name string Name of emit value example - emit: name: protein_compound_association + + + Example + https://bmeg.github.io/sifter/docs/example/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/example/ + Example Pipeline Our first task will be to convert a ZIP code TSV into a set of county level entries. The input file looks like: ZIP,COUNTYNAME,STATE,STCOUNTYFP,CLASSFP 36003,Autauga County,AL,01001,H1 36006,Autauga County,AL,01001,H1 36067,Autauga County,AL,01001,H1 36066,Autauga County,AL,01001,H1 36703,Autauga County,AL,01001,H1 36701,Autauga County,AL,01001,H1 36091,Autauga County,AL,01001,H1 First is the header of the pipeline. This declares the unique name of the pipeline and it&rsquo;s output directory. name: zipcode_map outdir: ./ docs: Converts zipcode TSV into graph elements Next the configuration is declared. + + + fieldParse + https://bmeg.github.io/sifter/docs/transforms/fieldparse/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/fieldparse/ + + + + fieldProcess + https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ + fieldProcess Create stream of objects based on the contents of a field. If the selected field is an array each of the items in the array will become an independent row. Parameters name Type Description field string Name of field to be processed mapping map[string]string Project templated values into child element itemField string If processing an array of non-dict elements, create a dict as {itemField:element} example - fieldProcess: field: portions mapping: sample: &#34;{{row. + + + fieldType + https://bmeg.github.io/sifter/docs/transforms/fieldtype/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/fieldtype/ + fieldType Set field to specific type, ie cast as float or integer example - fieldType: t_depth: int t_ref_count: int t_alt_count: int n_depth: int n_ref_count: int n_alt_count: int start: int + + + filter + https://bmeg.github.io/sifter/docs/transforms/filter/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/filter/ + filter Filter rows in stream using a number of different methods Parameters name Type Description field string (field path) Field used to match rows value string (template string) Template string to match against match string String to match against check string How to check value, &rsquo;exists&rsquo; or &lsquo;hasValue&rsquo; method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) Example Field based match - filter: field: table match: source_statistics Check based match + + + flatMap + https://bmeg.github.io/sifter/docs/transforms/flatmap/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/flatmap/ + + + + from + https://bmeg.github.io/sifter/docs/transforms/from/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/from/ + from Parmeters Name of data source Example inputs: profileReader: tableLoad: input: &#34;{{config.profiles}}&#34; pipelines: profileProcess: - from: profileReader + + + glob + https://bmeg.github.io/sifter/docs/inputs/glob/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/glob/ + glob Scan files using * based glob statement and open all files as input. Parameters Name Description storeFilename Store value of filename in parameter each row input Path of avro object file to transform xmlLoad xmlLoad configutation tableLoad Run transform pipeline on a TSV or CSV jsonLoad Run a transform pipeline on a multi line json file avroLoad Load data from avro file Example inputs: pubmedRead: glob: input: &#34;{{config.baseline}}/*.xml.gz&#34; xmlLoad: {} + + + graphBuild + https://bmeg.github.io/sifter/docs/transforms/graphbuild/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/graphbuild/ + graphBuild Build graph elements from JSON objects using the JSON Schema graph extensions. example - graphBuild: schema: &#34;{{config.allelesSchema}}&#34; title: Allele + + + hash + https://bmeg.github.io/sifter/docs/transforms/hash/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/hash/ + hash Parameters name Type Description field string Field to store hash value value string Templated string of value to be hashed method string Hashing method: sha1/sha256/md5 example - hash: value: &#34;{{row.contents}}&#34; field: contents-sha1 method: sha1 + + + Inputs + https://bmeg.github.io/sifter/docs/inputs/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/ + Every playbook consists of a series of inputs. + + + jsonLoad + https://bmeg.github.io/sifter/docs/inputs/jsonload/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/jsonload/ + jsonLoad Load data from a JSON file. Default behavior expects a single dictionary per line. Each line is a seperate entry. The multiline parameter reads all of the lines of the files and returns a single object. Parameters name Description input Path of JSON file to transform multiline Load file as a single multiline JSON object Example inputs: caseData: jsonLoad: input: &#34;{{config.casesJSON}}&#34; + + + lookup + https://bmeg.github.io/sifter/docs/transforms/lookup/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/lookup/ + lookup Using key from current row, get values from a reference source Parameters name Type Description replace string (field path) Field to replace lookup string (template string) Key to use for looking up data copy map[string]string Copy values from record that was found by lookup. The Key/Value record uses the Key as the destination field and copies the field from the retrieved records using the field named in Value tsv TSVTable TSV translation table file json JSONTable JSON data file table LookupTable Inline lookup table pipeline PipelineLookup Use output of a pipeline as a lookup table Example JSON file based lookup The JSON file defined by config. + + + map + https://bmeg.github.io/sifter/docs/transforms/map/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/map/ + map Run function on every row Parameters name Description method Name of function to call python Python code to be run gpython Python code to be run using GPython Example - map: method: response gpython: | def response(x): s = sorted(x[&#34;curve&#34;].items(), key=lambda x:float(x[0])) x[&#39;dose_um&#39;] = [] x[&#39;response&#39;] = [] for d, r in s: try: dn = float(d) rn = float(r) x[&#39;dose_um&#39;].append(dn) x[&#39;response&#39;].append(rn) except ValueError: pass return x + + + objectValidate + https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ + objectValidate Use JSON schema to validate row contents parameters name Type Description title string Title of object to use for validation schema string Path to JSON schema definition example - objectValidate: title: Aliquot schema: &#34;{{config.schema}}&#34; + + + Overview + https://bmeg.github.io/sifter/docs/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/ + Sifter pipelines Sifter pipelines process steams of nested JSON messages. Sifter comes with a number of file extractors that operate as inputs to these pipelines. The pipeline engine connects togeather arrays of transform steps into directed acylic graph that is processed in parallel. Example Message: { &#34;firstName&#34; : &#34;bob&#34;, &#34;age&#34; : &#34;25&#34; &#34;friends&#34; : [ &#34;Max&#34;, &#34;Alex&#34;] } Once a stream of messages are produced, that can be run through a transform pipeline. + + + Pipeline Steps + https://bmeg.github.io/sifter/docs/transforms/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/ + Transforms alter the data + + + plugin + https://bmeg.github.io/sifter/docs/inputs/plugin/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/plugin/ + plugin Run user program for customized data extraction. Example inputs: oboData: plugin: commandLine: ../../util/obo_reader.py {{config.oboFile}} The plugin program is expected to output JSON messages, one per line, to STDOUT that will then be passed to the transform pipelines. Example Plugin The obo_reader.py plugin, it reads a OBO file, such as the kind the describe the GeneOntology, and emits the records as single line JSON messages. #!/usr/bin/env python import re import sys import json re_section = re. + + + plugin + https://bmeg.github.io/sifter/docs/transforms/plugin/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/plugin/ + + + + project + https://bmeg.github.io/sifter/docs/transforms/project/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/project/ + project Populate row with templated values parameters name Type Description mapping map[string]any New fields to be generated from template rename map[string]string Rename field (no template engine) Example - project: mapping: type: sample id: &#34;{{row.sample_id}}&#34; + + + reduce + https://bmeg.github.io/sifter/docs/transforms/reduce/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/reduce/ + reduce Using key from rows, reduce matched records into a single entry Parameters name Type Description field string (field path) Field used to match rows method string Method name python string Python code string gpython string Python code string run using (https://github.com/go-python/gpython) init map[string]any Data to use for first reduce Example - reduce: field: dataset_name method: merge init: { &#34;compounds&#34; : [] } gpython: | def merge(x,y): x[&#34;compounds&#34;] = list(set(y[&#34;compounds&#34;]+x[&#34;compounds&#34;])) return x + + + regexReplace + https://bmeg.github.io/sifter/docs/transforms/regexreplace/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/regexreplace/ + + + + split + https://bmeg.github.io/sifter/docs/transforms/split/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/split/ + split Split a field using string sep Parameters name Type Description field string Field to the split sep string String to use for splitting Example - split: field: methods sep: &#34;;&#34; + + + sqldump + https://bmeg.github.io/sifter/docs/inputs/sqldump/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/sqldump/ + sqlDump Scan file produced produced from sqldump. Parameters Name Type Description input string Path to the SQL dump file tables []string Names of tables to read out Example inputs: database: sqldumpLoad: input: &#34;{{config.sql}}&#34; tables: - cells - cell_tissues - dose_responses - drugs - drug_annots - experiments - profiles + + + sqliteLoad + https://bmeg.github.io/sifter/docs/inputs/sqliteload/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/sqliteload/ + sqliteLoad Extract data from an sqlite file Parameters Name Type Description input string Path to the SQLite file query string SQL select statement based input Example inputs: sqlQuery: sqliteLoad: input: &#34;{{config.sqlite}}&#34; query: &#34;select * from drug_mechanism as a LEFT JOIN MECHANISM_REFS as b on a.MEC_ID=b.MEC_ID LEFT JOIN TARGET_COMPONENTS as c on a.TID=c.TID LEFT JOIN COMPONENT_SEQUENCES as d on c.COMPONENT_ID=d.COMPONENT_ID LEFT JOIN MOLECULE_DICTIONARY as e on a.MOLREGNO=e.MOLREGNO&#34; + + + tableLoad + https://bmeg.github.io/sifter/docs/inputs/tableload/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/tableload/ + tableLoad Extract data from tabular file, includiong TSV and CSV files. Parameters Name Type Description input string File to be transformed rowSkip int Number of header rows to skip columns []string Manually set names of columns extraColumns string Columns beyond originally declared columns will be placed in this array sep string Separator \t for TSVs or , for CSVs Example config: gafFile: ../../source/go/goa_human.gaf.gz inputs: gafLoad: tableLoad: input: &#34;{{config.gafFile}}&#34; columns: - db - id - symbol - qualifier - goID - reference - evidenceCode - from - aspect - name - synonym - objectType - taxon - date - assignedBy - extension - geneProduct + + + tableWrite + https://bmeg.github.io/sifter/docs/transforms/tablewrite/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/tablewrite/ + + + + uuid + https://bmeg.github.io/sifter/docs/transforms/uuid/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/transforms/uuid/ + + + + xmlLoad + https://bmeg.github.io/sifter/docs/inputs/xmlload/ + Mon, 01 Jan 0001 00:00:00 +0000 + https://bmeg.github.io/sifter/docs/inputs/xmlload/ + xmlLoad Load an XML file Parameters name Description input Path to input file Example inputs: loader: xmlLoad: input: &#34;{{config.xmlPath}}&#34; + diff --git a/docs/sifter_example.png b/docs/sifter_example.png new file mode 100644 index 0000000000000000000000000000000000000000..284e0ddd67070af27f377bb207c7b7dabf78e80e GIT binary patch literal 124447 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%0Ark#4x8mwj*}3#`CSD8i7)x$9^)#7#prm^ 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%0A`=rq>`Dm)jE=e)-Ys72oU%+?M>q!73dn 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 - / + https://bmeg.github.io/sifter/ - /categories/ + https://bmeg.github.io/sifter/docs/transforms/accumulate/ - /tags/ + https://bmeg.github.io/sifter/docs/inputs/avroload/ + + https://bmeg.github.io/sifter/categories/ + + https://bmeg.github.io/sifter/docs/transforms/clean/ + + https://bmeg.github.io/sifter/docs/transforms/debug/ + + https://bmeg.github.io/sifter/docs/transforms/distinct/ + + https://bmeg.github.io/sifter/docs/ + + https://bmeg.github.io/sifter/docs/inputs/embedded/ + + https://bmeg.github.io/sifter/docs/transforms/emit/ + + https://bmeg.github.io/sifter/docs/example/ + + https://bmeg.github.io/sifter/docs/transforms/fieldparse/ + + https://bmeg.github.io/sifter/docs/transforms/fieldprocess/ + + https://bmeg.github.io/sifter/docs/transforms/fieldtype/ + + https://bmeg.github.io/sifter/docs/transforms/filter/ + + https://bmeg.github.io/sifter/docs/transforms/flatmap/ + + https://bmeg.github.io/sifter/docs/transforms/from/ + + https://bmeg.github.io/sifter/docs/inputs/glob/ + + https://bmeg.github.io/sifter/docs/transforms/graphbuild/ + + https://bmeg.github.io/sifter/docs/transforms/hash/ + + https://bmeg.github.io/sifter/docs/inputs/ + + https://bmeg.github.io/sifter/docs/inputs/jsonload/ + + https://bmeg.github.io/sifter/docs/transforms/lookup/ + + https://bmeg.github.io/sifter/docs/transforms/map/ + + https://bmeg.github.io/sifter/docs/transforms/objectvalidate/ + + https://bmeg.github.io/sifter/docs/ + + https://bmeg.github.io/sifter/docs/transforms/ + + https://bmeg.github.io/sifter/docs/inputs/plugin/ + + https://bmeg.github.io/sifter/docs/transforms/plugin/ + + https://bmeg.github.io/sifter/docs/transforms/project/ + + https://bmeg.github.io/sifter/docs/transforms/reduce/ + + https://bmeg.github.io/sifter/docs/transforms/regexreplace/ + + https://bmeg.github.io/sifter/docs/transforms/split/ + + https://bmeg.github.io/sifter/docs/inputs/sqldump/ + + https://bmeg.github.io/sifter/docs/inputs/sqliteload/ + + https://bmeg.github.io/sifter/docs/inputs/tableload/ + + https://bmeg.github.io/sifter/docs/transforms/tablewrite/ + + https://bmeg.github.io/sifter/tags/ + + https://bmeg.github.io/sifter/docs/transforms/uuid/ + + https://bmeg.github.io/sifter/docs/inputs/xmlload/ diff --git a/docs/tags/index.xml b/docs/tags/index.xml index 99ec5b4..41241ef 100644 --- a/docs/tags/index.xml +++ b/docs/tags/index.xml @@ -1,11 +1,11 @@ - Tags on - /tags/ - Recent content in Tags on + Tags on Sifter + https://bmeg.github.io/sifter/tags/ + Recent content in Tags on Sifter Hugo -- gohugo.io - en - + en-us + diff --git a/transform/reduce.go b/transform/reduce.go index 8243f90..2f8a450 100644 --- a/transform/reduce.go +++ b/transform/reduce.go @@ -60,6 +60,8 @@ func (rp *reduceProcess) GetKey(i map[string]any) string { if xStr, ok := x.(string); ok { return xStr } + } else { + log.Printf("Missing field in reduce") } return "" } diff --git a/website/content/_index.md b/website/content/_index.md index 6ad7988..5c37e23 100644 --- a/website/content/_index.md +++ b/website/content/_index.md @@ -7,3 +7,5 @@ files and external databases. It includes a pipeline description language to define a set of Transform steps to create object messages that can be validated using a JSON schema data. + +![Example of sifter code](sifter_example.png) \ No newline at end of file diff --git a/website/content/docs.md b/website/content/docs.md index d047861..d45d044 100644 --- a/website/content/docs.md +++ b/website/content/docs.md @@ -40,6 +40,45 @@ be done in a transform pipeline these include: # Script structure +# Pipeline File + +An sifter pipeline file is in YAML format and describes an entire processing pipelines. +If is composed of the following sections: `config`, `inputs`, `pipelines`, `outputs`. In addition, +for tracking, the file will also include `name` and `class` entries. + +```yaml + +class: sifter +name: