setuptools: localização da pasta de dados do pacote

94

Eu uso setuptools para distribuir meu pacote python. Agora preciso distribuir arquivos de dados adicionais.

Pelo que reuni da documentação do setuptools, preciso ter meus arquivos de dados dentro do diretório do pacote. No entanto, prefiro ter meus arquivos de dados dentro de um subdiretório no diretório raiz.

O que eu gostaria de evitar:

/ #root
|- src/
|  |- mypackage/
|  |  |- data/
|  |  |  |- resource1
|  |  |  |- [...]
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

O que eu gostaria de ter em vez disso:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Eu simplesmente não me sinto confortável em ter tantos subdiretórios, se não for essencial. Não consigo encontrar um motivo pelo qual eu / tenho / para colocar os arquivos dentro do diretório do pacote. Também é complicado trabalhar com tantos subdiretórios IMHO aninhados. Ou há algum bom motivo que justifique essa restrição?

phant0m
fonte
8
Eu fiz uma pergunta semelhante sobre o uso de 'data_files' para distribuir recursos (documentos, imagens, etc): stackoverflow.com/questions/5192386/… ... e as (duas) respostas disseram usar 'package_data' no lugar. Agora estou usando dados de pacote, mas isso implica que tenho que colocar meus dados e documentos dentro do meu pacote, ou seja, misturados com meu código-fonte. Eu não gosto disso. Ao fazer o grep em meu código-fonte, encontro não apenas a definição de classe que estou procurando, mas também as dezenas de menções que obtêm em meus arquivos RST, HTML e intermediários. :-(
Jonathan Hartley,
2
Sei que esta resposta está muito atrasada, @JonathanHartley, mas você pode transformar qualquer diretório em um "pacote" adicionando um __init__.pyarquivo, mesmo se esse arquivo estiver em branco. Portanto, você pode manter um diretório de dados separado com um __init__.pyarquivo vazio para fazer com que pareça um pacote. Isso deve evitar que o grep de sua árvore de origem os pegue, mas ainda será reconhecido como um pacote pelo python e suas ferramentas de construção.
dhj
@dhj Uma ideia interessante, obrigado.
Jonathan Hartley
4
@dhj o único problema com essa abordagem é o python achar que você instalou um pacote chamado 'dados'. Se outro pacote que você instalou tentar empacotar dados da mesma maneira, você terá dois pacotes de 'dados' conflitantes instalados.
dedos dos pés

Respostas:

111

Opção 1: instalar como dados do pacote

A principal vantagem de colocar arquivos de dados dentro da raiz de seu pacote Python é que isso permite que você evite se preocupar com onde os arquivos ficarão no sistema de um usuário, que pode ser Windows, Mac, Linux, alguma plataforma móvel ou dentro de um Egg. Você sempre pode encontrar o diretório datarelativo à raiz do seu pacote Python, não importa onde ou como ele está instalado.

Por exemplo, se eu tiver um layout de projeto como este:

project/
    foo/
        __init__.py
        data/
            resource1/
                foo.txt

Você pode adicionar uma função para __init__.pylocalizar um caminho absoluto para um arquivo de dados:

import os

_ROOT = os.path.abspath(os.path.dirname(__file__))
def get_data(path):
    return os.path.join(_ROOT, 'data', path)

print get_data('resource1/foo.txt')

Saídas:

/Users/pat/project/foo/data/resource1/foo.txt

Depois que o projeto for instalado como um Egg, o caminho para dataserá alterado, mas o código não precisa ser alterado:

/Users/pat/virtenv/foo/lib/python2.6/site-packages/foo-0.0.0-py2.6.egg/foo/data/resource1/foo.txt

Opção 2: instalar em local fixo

A alternativa seria colocar seus dados fora do pacote Python e então:

  1. Ter a localização datatransmitida por meio de um arquivo de configuração, argumentos de linha de comando ou
  2. Incorpore o local em seu código Python.

Isso é muito menos desejável se você planeja distribuir seu projeto. Se você realmente quiser fazer isso, você pode instalar dataonde quiser no sistema de destino, especificando o destino para cada grupo de arquivos, passando uma lista de tuplas:

from setuptools import setup
setup(
    ...
    data_files=[
        ('/var/data1', ['data/foo.txt']),
        ('/var/data2', ['data/bar.txt'])
        ]
    )

Atualizado : exemplo de uma função shell para executar grep recursivamente em arquivos Python:

atlas% function grep_py { find . -name '*.py' -exec grep -Hn $* {} \; }
atlas% grep_py ": \["
./setup.py:9:    package_data={'foo': ['data/resource1/foo.txt']}
samplebias
fonte
7
Muito obrigado por me ajudar a enfrentar a situação. Portanto, estou feliz em usar o package_data como você (e todos os outros) sugere. No entanto: Sou apenas eu quem acha que colocar seus dados e documentos dentro do diretório de origem do pacote é inconvenientemente confuso? (por exemplo, o grep na minha fonte retorna dezenas de resultados indesejados da minha documentação. Eu poderia adicionar os parâmetros '--exclude-dir' ao grep sempre que o usar, o que pode ser diferente de um projeto para o outro, mas parece nojento) É é possível incluir um subdiretório 'src' dentro do diretório do meu pacote sem interromper as importações, etc
Jonathan Hartley
Normalmente, só coloco arquivos de dados que o pacote requer no diretório do pacote. Gostaria de instalar os documentos como data_files. Além disso, você pode criar um alias de shell para grep para ignorar arquivos não-Python, algo como grep_py.
samplebias
Ei, samplebias. Obrigado pelas atualizações. Não se trata apenas de grep, é tudo , de pesquisa em arquivos de editor de texto a ctags a awk. Vou tentar reordenar meu projeto para colocar docs em data_files como você sugere, veja como isso funciona. Volto em breve ... :-)
Jonathan Hartley
... parece funcionar bem. Obrigado por me colocar no caminho certo. Os +50 pontos de reputação são saborosos?
Jonathan Hartley
Obrigado! Bom saber, que bom que deu certo e você está fazendo progresso!
samplebias
13

Acho que encontrei um bom compromisso que lhe permitirá manter a seguinte estrutura:

/ #root
|- data/
|  |- resource1
|  |- [...]
|- src/
|  |- mypackage/
|  |  |- __init__.py
|  |  |- [...]
|- setup.py

Você deve instalar os dados como package_data, para evitar os problemas descritos na resposta samplebias, mas para manter a estrutura do arquivo você deve adicionar ao seu setup.py:

try:
    os.symlink('../../data', 'src/mypackage/data')
    setup(
        ...
        package_data = {'mypackage': ['data/*']}
        ...
    )
finally:
    os.unlink('src/mypackage/data')

Desta forma criamos a estrutura apropriada "just in time", e mantemos nossa árvore de origem organizada.

Para acessar esses arquivos de dados dentro do seu código, você "simplesmente" usa:

data = resource_filename(Requirement.parse("main_package"), 'mypackage/data')

Ainda não gosto de ter que especificar 'mypackage' no código, pois os dados não podem ter nada a ver necessariamente com este módulo, mas acho que é um bom compromisso.

polvoazul
fonte
-4

Eu acho que você pode basicamente fornecer qualquer coisa como argumento * data_files * para setup () .

lgautier
fonte
Hmm ... eu posso ver que está na documentação do distutils, mas não consigo ver na documentação do setuptools. De qualquer forma, como eu seria capaz de acessá-lo eventualmente?
phant0m
Acho que data_files só deve ser usado para dados que são compartilhados entre vários pacotes. por exemplo, se você pipetar a instalação do PyPI, os arquivos listados em data_files serão instalados nos diretórios diretamente no diretório de instalação principal do Python. (ou seja, não em Python27 / Lib / site-packages / mypackage, mas em paralelo com 'Python27 / Lib')
Jonathan Hartley