Как сделать ввод Snakemake необязательным, но не пустым?
Я создаю сценарий SQL из текстовых данных. Сценарий (часть) должен состоять из CREATE TABLE
оператора и необязательного INSERT INTO
оператора. Значения для INSERT INTO
оператора берутся из списка файлов, каждый может существовать, а может и не существовать; все значения существующих файлов объединяются. Важнейшей частью является то, что INSERT INTO
оператор должен пропускаться, если ни один файл данных не существует.
Я создал сценарий в Snakemake, который это делает. Есть два неоднозначных правила, которые создают сценарий: одно, которое создает сценарий для пустых данных, и другое, которое создает таблицу, но вставляет данные (неоднозначность разрешается с помощью ruleorder
оператора).
Интересная часть - это правило, объединяющее значения из файлов данных. Он должен создавать выходные данные всякий раз, когда присутствует хотя бы один вход, и это правило не должно рассматриваться иначе. Есть две трудности: сделать каждый ввод необязательным и запретить Snakemake использовать это правило, когда файлы не существуют. Я сделал это с помощью трюка:
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
Функция принимает список имен файлов, и отфильтровывает те имена файлов , которые не представляют файл. Это позволяет сделать каждый ввод необязательным. В крайнем случае, когда ни один файл не существует, эта функция возвращает специальное значение, представляющее несуществующий файл. Это позволяет обрезать эту ветку и предпочесть ту, которая создает скрипт без INSERT
оператора.
Мне хочется изобретать велосипед, к тому же трюк с "non_existing_file" выглядит немного грязно. Есть ли лучшие и идиоматические способы сделать это в Snakemake?
Ответы
Моим решением было бы что-то вроде того, что вы не должны заставлять snakemake использовать или не использовать правило внутри правила, но укажите, какие выходные данные вам нужны, и snakemake решит, нужно ли использовать правило. Итак, для вашего примера я бы сделал что-то как:
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: ...
Это приведет к выполнению правила merge_values, только если required_files не пусто.