Jak ustawić dane wejściowe Snakemake jako opcjonalne, ale nie puste?
Buduję skrypt SQL z danych tekstowych. (Część) skryptu składa się z CREATE TABLE
oświadczenia i opcjonalnego INSERT INTO
oświadczenia. Wartości dla INSERT INTO
instrukcji są pobierane z listy plików, każdy może istnieć lub nie; wszystkie wartości istniejących plików są scalane. Najważniejsze jest to, że INSERT INTO
wyciąg należy pominąć, gdy nie ma jednego pliku danych.
Stworzyłem skrypt w Snakemake, który to robi. Istnieją dwie niejednoznaczne reguły, które tworzą skrypt: ta, która tworzy skrypt dla pustych danych, i ta, która tworzy tabelę, ale wstawia dane (niejednoznaczność jest rozwiązywana za pomocą ruleorder
instrukcji).
Ciekawostką jest reguła, która łączy wartości z plików danych. Tworzy dane wyjściowe za każdym razem, gdy obecne jest co najmniej jedno wejście, a tej zasady nie należy traktować inaczej. Istnieją dwie trudności: uczynić każde wejście opcjonalnym i uniemożliwić Snakemake używanie tej reguły, gdy nie ma żadnych plików. Zrobiłem to podstępem:
def require_at_least_one(filelist):
existing = [file for file in filelist if os.path.isfile(file)]
return existing if len(existing) else "non_existing_file"
rule merge_values:
input: require_at_least_one(expand("path_to_data/{dataset}/values", dataset=["A", "B", "C"]))
output: ...
shell: ...
require_at_least_one
Funkcja przyjmuje listę nazw plików i odfiltrowuje te nazwy plików, które nie reprezentują pliku. Dzięki temu każde wejście może być opcjonalne. W przypadku narożnika, gdy żaden plik nie istnieje, ta funkcja zwraca specjalną wartość, która reprezentuje nieistniejący plik. Pozwala to na przycięcie tej gałęzi i preferowanie tej, która tworzy skrypt bez INSERT
instrukcji.
Mam ochotę wymyślić na nowo koło, a ponadto sztuczka „non_existing_file” wygląda na trochę brudną. Czy w Snakemake są na to lepsze i idiomatyczne sposoby?
Odpowiedzi
moim rozwiązaniem byłoby coś w rodzaju, że nie powinieneś zmuszać snakemake do używania lub nie używania reguły wewnątrz reguły, ale określ, które wyjścia potrzebujesz, a snakemake zdecyduje, czy musi użyć reguły. Więc dla twojego przykładu zrobiłbym coś takiego:
def required_files(filelist):
return [file for file in filelist if os.path.isfile(file)]
rule what_to_gen:
input:
merged = [] if required_files(expand("path_to_data/{dataset}/values", dataset=["A", "B", "C"])) else 'merged_files.txt'
rule merge_values:
input: required_files(expand("path_to_data/{dataset}/values", dataset=["A", "B", "C"]))
output: 'merged_files.txt'
shell: ...
Spowoduje to wykonanie reguły merge_values tylko wtedy, gdy required_files nie jest pusta.