Incluindo arquivos não Python com setup.py

200

Como faço para setup.pyincluir um arquivo que não faz parte do código? (Especificamente, é um arquivo de licença, mas pode ser qualquer outra coisa.)

Quero poder controlar o local do arquivo. Na pasta de origem original, o arquivo está na raiz do pacote. (ou seja, no mesmo nível da parte superior __init__.py.) Quero que ele fique exatamente lá quando o pacote for instalado, independentemente do sistema operacional. Como faço isso?

Ram Rachum
fonte
como você faz isso no momento? sua pergunta anterior indica que você está familiarizado com como adicionar o arquivo de licença. Qual é o seu código que "não funciona"?
SilentGhost 23/10/2009
2
data_files = [('', ['lgpl2.1_license.txt',]),]coloca na pasta Python26.
23610 Ram Rachum
Após um feedback negativo, li sua pergunta novamente e percebi o que estava faltando. Atualizei minha resposta para fornecer uma solução não hackeada para sua pergunta que não exija nenhum módulo adicional (como setuptools ou distribut).
Evan Solha
Obrigado Evan. No entanto, eu estou perfeitamente bem com o uso de setuptools, pois é muito prevalente.
precisa

Respostas:

224

Provavelmente, a melhor maneira de fazer isso é usar a setuptools package_datadiretiva. Isso significa usar setuptools(ou distribute) em vez de distutils, mas essa é uma "atualização" muito simples.

Aqui está um exemplo completo (mas não testado):

from setuptools import setup, find_packages

setup(
    name='your_project_name',
    version='0.1',
    description='A description.',
    packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']),
    package_data={'': ['license.txt']},
    include_package_data=True,
    install_requires=[],
)

Observe as linhas específicas que são críticas aqui:

package_data={'': ['license.txt']},
include_package_data=True,

package_dataé um nome dictde pacote (vazio = todos os pacotes) para uma lista de padrões (pode incluir globs). Por exemplo, se você deseja especificar apenas arquivos no seu pacote, também pode fazer isso:

package_data={'yourpackage': ['*.txt', 'path/to/resources/*.txt']}

A solução aqui definitivamente não é renomear seus pyarquivos não com uma .pyextensão.

Vejo a apresentação de Ian Bicking para mais informações.

UPDATE: Outra abordagem [melhor]

Outra abordagem que funciona bem se você deseja apenas controlar o conteúdo da distribuição de origem ( sdist) e ter arquivos fora do pacote (por exemplo, diretório de nível superior) é adicionar um MANIFEST.inarquivo. Veja a documentação do Python para o formato deste arquivo.

Desde que escrevi essa resposta, descobri que o uso MANIFEST.innormalmente é uma abordagem menos frustrante para garantir apenas a sua distribuição de origem (tar.gz ) possua os arquivos necessários.

Por exemplo, se você deseja incluir o requirements.txtnível superior, inclua recursivamente o diretório "dados" de nível superior:

include requirements.txt
recursive-include data *

No entanto, para que esses arquivos sejam copiados no momento da instalação para a pasta do pacote dentro dos pacotes do site, você precisará fornecer include_package_data=Truea setup()função. Consulte Adicionando arquivos que não são de código para obter mais informações.

Hans L
fonte
5
O package_data também está disponível para scripts de configuração de distutils puros desde o Python 2.3.
Éric Araujo
15
Essa resposta parece sensata, mas não funciona para mim. Como package_data é notoriamente não confiável (requer a coordenação de MANIFEST.in e setup.py para adicionar arquivos ao sdist e instalá-los, como etapas separadas) e o autor desta resposta observa que "não foi testado", alguém pode mais confirmar se funciona para eles? Meu arquivo de licença está incluída na sdist, mas não instalado quando eu corro "python setup.py install" nem "pip instalar Package"
Jonathan Hartley
11
A apresentação de Ian Bicking mostra apenas como instalar dados do pacote para arquivos que estão dentro de um pacote. Meu arquivo LICENSE está no nível superior do meu projeto, ou seja, não está em nenhum pacote. Ainda posso usar package_data? O uso de arquivos de dados não é inicial, porque coloca os arquivos em um local em todo o sistema. não associado ao meu projeto e, para piorar, o local muda dependendo de eu executar "setup.py install" ou "pip install", a partir do mesmo sdist.
Jonathan Hartley
8
Suponho que o motivo pelo qual não funcione para mim é que o arquivo não está localizado em nenhum pacote - é um arquivo LICENSE no nível superior do repositório e, portanto, não pode ser instalado usando 'package_data'
Jonathan Hartley
7
Esta resposta não funciona para mim. Os arquivos adicionais não estão recebendo colocado no arquivo tar ...
lpapp
44

Para realizar o que você está descrevendo, serão necessários dois passos ...

  • O arquivo precisa ser adicionado ao tarball de origem
  • O setup.py precisa ser modificado para instalar o arquivo de dados no caminho de origem

Etapa 1: para adicionar o arquivo ao tarball de origem, inclua-o no MANIFEST

Crie um modelo MANIFEST na pasta que contém setup.py

O MANIFEST é basicamente um arquivo de texto com uma lista de todos os arquivos que serão incluídos no tarball de origem.

Aqui está a aparência do MANIFEST para o meu projeto:

  • CHANGELOG.txt
  • INSTALL.txt
  • LICENSE.txt
  • pypreprocessor.py
  • README.txt
  • setup.py
  • test.py
  • TODO.txt

Nota: Enquanto sdist faz adicionar alguns arquivos automaticamente , prefiro especificá-los explicitamente para ter certeza em vez de prever o que ele faz e não faz.

Etapa 2: para instalar o arquivo de dados na pasta de origem, modifique setup.py

Como você deseja adicionar um arquivo de dados (LICENSE.txt) à pasta de instalação de origem, é necessário modificar o caminho de instalação de dados para corresponder ao caminho de instalação de origem. Isso é necessário porque, por padrão, os arquivos de dados são instalados em um local diferente dos arquivos de origem.

Para modificar o diretório de instalação de dados para corresponder ao diretório de instalação de origem ...

Puxe as informações do diretório de instalação do distutils com:

from distutils.command.install import INSTALL_SCHEMES

Modifique o diretório de instalação de dados para corresponder ao diretório de instalação de origem:

for scheme in INSTALL_SCHEMES.values():
    scheme['data'] = scheme['purelib']

E adicione o arquivo de dados e o local em setup ():

data_files=[('', ['LICENSE.txt'])]

Nota: As etapas acima devem realizar exatamente o que você descreveu de maneira padrão, sem a necessidade de bibliotecas de extensão.

Evan Plaice
fonte
10
O MANIFEST apenas controla os arquivos incluídos no tarball de origem (produzido pelo sdist). Os arquivos listados lá não serão instalados.
David Cournapeau 17/11
@ David Eu não percebi o quão longe eu estava na minha primeira abordagem. Atualizei a resposta para estar correta para realizar o que a pergunta estava solicitando sem exigir bibliotecas adicionais de terceiros.
Evan Solha
3
@ Éric Alguma razão específica para isso? e você tem uma alternativa viável de instalação que não requer pacotes de terceiros (como setup_tools) para funcionar. Eu escolhi o distutils sobre o setuptools, porque ele está incluído na instalação básica do python e eu estava construindo módulos para o PYPI. Agora deveria haver uma maneira melhor de fazer isso usando o distutils2, mas eu não toquei em python por um bom tempo para não saber como. Como você parece ter conhecimento sobre distutils2, acho que seria benéfico para o resto de nós ter uma alternativa adequada para distutils2.
Evan Plaice
6
Como foi mencionado em outros threads package_data, não funcionará se o arquivo não estiver no pacote.
Gringo Suave
2
@ ÉricAraujo: Não é uma má idéia usar esta solução, pois não há outra maneira. É um design ruim para distutils - isso é verdade. Mas é uma API pública de fato que nunca mudará, porque quebrará muitas coisas. Vamos torcer para que o distutils2 forneça as melhores formas recomendadas.
Anatoly techtonik
15

crie MANIFEST.inna raiz do projeto com recursive-includeo diretório necessário ou includecom o nome do arquivo.

include LICENSE
include README.rst
recursive-include package/static *
recursive-include package/templates *

documentação pode ser encontrada aqui

Tudo é muito
fonte
7

Eu queria postar um comentário em uma das perguntas, mas não tenho reputação suficiente para isso>.>

Aqui está o que funcionou para mim (depois de consultar os documentos):

package_data={
    'mypkg': ['../*.txt']
},

include_package_data: False

Estranhamente, a última linha também foi crucial para mim (você também pode omitir esse argumento de palavra-chave - funciona da mesma maneira).

O que isso faz é copiar todos os arquivos de texto em seu diretório raiz ou de nível superior (um nível acima do pacote mypkg você deseja distribuir).

Espero que isto ajude!

rv.kvetch
fonte
Eu estava procurando uma maneira de não ter que criar um MANIFEST.in, isso funcionou para mim. A última linha também foi crucial para mim. Minhas linhas eraminclude_package_data=False, package_data={ "": ["../CHANGELOG.md"] },
Mendhak
7

Etapa 1: crie um MANIFEST.inarquivo na mesma pasta com setup.py

Etapa 2: inclua o caminho relativo para os arquivos que você deseja adicionarMANIFEST.in

include README.rst
include docs/*.txt
include funniest/data.json

Etapa 3: defina include_package_data=Truea setup()função para copiar esses arquivos no pacote do site

A referência está aqui.

debuglife
fonte
6

É 2019 e aqui está o que está funcionando - apesar dos conselhos aqui e ali, o que eu encontrei na internet que meio documentado está usando setuptools_scm, passou como opções parasetuptools.setup . Isso incluirá todos os arquivos de dados com versão em seu VCS, seja git ou qualquer outro, no pacote wheel e fará "pip install" a partir do repositório git para trazer esses arquivos.

Então, acabei de adicionar essas duas linhas à chamada de instalação em "setup.py". Nenhuma instalação extra ou importação requerida:

    setup_requires=['setuptools_scm'],
    include_package_data=True,

Não há necessidade de listar manualmente package_data ou em um arquivo MANIFEST.in - se estiver com versão, ele será incluído no pacote. Os documentos sobre "setuptools_scm" enfatizam a criação de um número de versão a partir da posição de confirmação e desconsideram a parte realmente importante da adição dos arquivos de dados. (Não posso me importar menos se meu arquivo de roda intermediária for nomeado "* 0.2.2.dev45 + g3495a1f" ou usará o número da versão codificada "0.3.0dev0" que eu digitei - mas deixando arquivos cruciais para o programa trabalho por trás é um pouco importante)

jsbueno
fonte
5

Em setup.py em setup (:

setup(
   name = 'foo library'
   ...
  package_data={
   'foolibrary.folderA': ['*'],     # All files from folder A
   'foolibrary.folderB': ['*.txt']  #All text files from folder B
   },
Dashing Adam Hughes
fonte
1
Na verdade, isso não faz nada para atingir o objetivo do OP. Tudo o que você escreve package_datanão terá influência sobre o que setup.py installfaz, a menos que você modifique o próprio comando install. A menos que esses arquivos estejam no diretório do pacote, o que geralmente é algo que você deseja evitar.
Wwxvw 06/12/19
3

Aqui está uma resposta mais simples que funcionou para mim.

Primeiro, pelo comentário de um desenvolvedor Python acima, setuptools não é necessário:

package_data is also available to pure distutils setup scripts 
since 2.3.  Éric Araujo

Isso é ótimo porque colocar um requisito setuptools em seu pacote significa que você precisará instalá-lo também. Em resumo:

from distutils.core import setup

setup(
    # ...snip...
    packages          = ['pkgname'],
    package_data      = {'pkgname': ['license.txt']},
)
Gringo Suave
fonte
1
Ele vai reclamar o diretório pkgamenão existe
Anthony Kong
1

Eu só queria acompanhar algo que encontrei ao trabalhar com o Python 2.7 no Centos 6. A adição de package_data ou data_files como mencionado acima não funcionou para mim. Adicionei um MANIFEST.IN aos arquivos que eu queria, que colocou os arquivos não-python no tarball, mas não os instalou na máquina de destino via RPM.

No final, consegui colocar os arquivos em minha solução usando as "opções" nas ferramentas de configuração / instalação. Os arquivos de opções permitem modificar várias seções do arquivo de especificação em setup.py. Do seguinte modo.

from setuptools import setup


setup(
    name='theProjectName',
    version='1',
    packages=['thePackage'],
    url='',
    license='',
    author='me',
    author_email='[email protected]',
    description='',
    options={'bdist_rpm': {'install_script': 'filewithinstallcommands'}},
)

arquivo - MANIFEST.in:

include license.txt

arquivo - com os comandos de instalação:

mkdir -p $RPM_BUILD_ROOT/pathtoinstall/
#this line installs your python files
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
#install license.txt into /pathtoinstall folder
install -m 700 license.txt $RPM_BUILD_ROOT/pathtoinstall/
echo /pathtoinstall/license.txt >> INSTALLED_FILES
Scott Bowers
fonte
-12

Descobri uma solução alternativa: renomeei meu nome lgpl2.1_license.txtpara lgpl2.1_license.txt.pye coloquei algumas aspas triplas ao redor do texto. Agora não preciso usar a data_filesopção nem especificar caminhos absolutos. Torná-lo um módulo Python é feio, eu sei, mas o considero menos feio do que especificar caminhos absolutos.

Ram Rachum
fonte
7
Veja meu post. Não precisa ser feio. É difícil encontrar um bom exemplo na rede, porque é difícil encontrar uma boa documentação para configurar pacotes.
Evan Plaice