Como tornar a entrada Snakemake opcional, mas não vazia?

Dec 10 2020

Estou construindo um script SQL a partir de dados de texto. O (parte do) script deve consistir em uma CREATE TABLEdeclaração e uma INSERT INTOdeclaração opcional . Os valores para INSERT INTOdeclaração são retirados da lista de arquivos, cada um pode existir ou não; todos os valores dos arquivos existentes são mesclados. A parte crucial é que a INSERT INTOinstrução deve ser ignorada sempre que nenhum arquivo de dados existir.

Eu criei um script no Snakemake que faz isso. Existem duas regras ambíguas que criam um script: aquela que cria um script para dados vazios e aquela que cria uma tabela, mas insere dados (a ambigüidade é resolvida com ruleorderinstrução).

A parte interessante é a regra que mescla valores de arquivos de dados. Ele deve criar a saída sempre que pelo menos uma entrada estiver presente, e esta regra não deve ser considerada de outra forma. Existem duas dificuldades: tornar cada entrada opcional e evitar que o Snakemake use esta regra sempre que não houver arquivos. Eu fiz isso com um truque:

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: ...

A require_at_least_onefunção pega uma lista de nomes de arquivos e filtra os nomes de arquivos que não representam um arquivo. Isso permite tornar cada entrada opcional. Para o caso de canto, quando nenhum arquivo existe, esta função retorna um valor especial que representa um arquivo não existente. Isso permite podar este galho e preferir aquele que cria um script sem INSERTdeclaração.

Estou com vontade de reinventar a roda, além do mais o truque do "non_existing_file" parece um pouco sujo. Existem maneiras melhores e idiomáticas de fazer isso no Snakemake?

Respostas

silence Feb 11 2021 at 16:24

minha solução seria algo no sentido de que você não deveria forçar o snakemake a usar ou não uma regra dentro da regra, mas especificar quais saídas você precisa e o snakemake decidirá se precisa usar a regra. Então, para seu exemplo, eu faria algo como:

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: ...

Isso executará a regra merge_values ​​apenas se required_files não estiver vazio.