Desenvolvendo o pacote pypi perfeito com suporte para diferentes versões do python

Este é um pequeno manual / história sobre como criar um pacote pypi "perfeito" para python, que qualquer pessoa pode instalar com o comando estimado:


pip install my-perfect-package 

Destina-se a iniciantes, mas exorto os profissionais a expressarem sua opinião sobre como melhorar o pacote "perfeito". Portanto, eu peço gato.


O que significa um pacote "ideal"?


Vou prosseguir com os seguintes requisitos:


  • Código aberto no github;
    Todos devem poder contribuir para o desenvolvimento e agradecer ao autor.
  • Suporte para todas as versões atuais / populares do python (2.7, 3.5, 3.6, 3.7, 3.8);
    Pythons são diferentes, e em algum lugar ainda escrevemos ativamente no 2.7.
  • 100% de cobertura com testes de unidade;
    Os testes de unidade melhoram a arquitetura e automatizam as verificações de regressão.
    Um crachá com um número estimado aumenta a FAQ e define os padrões para os outros .
  • Usando o CI:
    As verificações automáticas são muito convenientes! E um monte de emblemas legais
    • Executando testes de unidade em todas as plataformas e em todas as versões do python;
      Não acredite naqueles que afirmam que o python e os pacotes instalados são multiplataforma , porque você sempre pode encontrar um bug .
    • Verificação de código de estilo;
      Um estilo uniforme melhora a legibilidade e reduz o número de discussões vazias em uma revisão.
    • Analisador de código estático;
      Pesquisa automática por bugs no código? Me dê dois!
  • Documentação real;
    Exemplos de trabalho com o pacote, descrição de métodos / classes e análise de erros típicos - a experiência documentada reduzirá o limite de entrada para iniciantes.
  • Desenvolvimento multiplataforma;
    Infelizmente, é difícil fazer uma contribuição pessoal aos projetos simplesmente porque o desenvolvedor aprimorou as ferramentas do Unix. Por exemplo, usei scripts bash para montagem.
  • O pacote é útil e torna o mundo um lugar melhor.
    Um requisito difícil, já que, a julgar pelo número de pacotes em pypi (~ 210k), os desenvolvedores são altruístas e muito já foi escrito.

Por onde começar?


Como não havia boas idéias, escolhi um tópico muito popular e popular - trabalhar com sistemas de números. A primeira versão deve ser capaz de traduzir números para romano e vice-versa. Para o manual é mais complicado e não é necessário. Ah, sim, o mais importante é o nome: numsys - como uma decodificação de sistemas numéricos. sistema numeral-py .


Como testar?


Peguei python3.7 e primeiro escrevi testes com stubs de função (todos somos TDDs ) usando o módulo unittests padrão.


Eu faço a seguinte estrutura de projeto:


 src/ numeral-system/ __init__.py roman.py tests __init__.py test_roman.py 

Eu não colocarei testes no pacote, então separo palha . Inicialmente, não criei a pasta src/ , mas desenvolvimentos adicionais mostraram que é mais conveniente para mim operar. Isso não é necessário, portanto, à vontade.


Decidi usar o pytest para o lançamento - ele sabe como funcionar perfeitamente com os testes do módulo padrão. Pode parecer um pouco ilógico, mas o módulo padrão para testes para mim parece parecia um pouco mais confortável. Agora eu recomendaria o uso do estilo pytest de escrita.


Mas eles dizem que colocar pytest (como quaisquer outras dependências) no sistema python não é uma ideia muito inteligente ...


Como gerenciar dependências?


Somente virtualenv e requirements.txt podem ser usados. Você pode ser progressivo e usar poesia . Talvez eu use o tox - uma ferramenta para simplificar a automação e os testes, o que também me permitirá gerenciar dependências.


Eu crio uma configuração simples do tox.ini e instalo o pytest :


 [tox] envlist = py37 ;     ,   python3.7 [testenv] ;     deps =  `deps`  ,   . -r requirements.txt ;     -r requirements-test.txt ; commands = pytest ;   

Inicialmente, indiquei explicitamente as dependências, mas a prática de integração com serviços de terceiros mostrou que a melhor maneira ainda seria armazenar as dependências no arquivo requirements.txt .


Um momento muito sutil surge. Corrigir a versão atual no momento do desenvolvimento ou sempre colocar a mais recente?


Se você confirmar, durante a instalação poderá haver conflitos entre pacotes devido a diferentes versões das dependências usadas. Se você não confirmar, o pacote poderá parar de funcionar repentinamente. A última situação é muito desagradável para os produtos finais, quando todas as compilações podem "ficar vermelhas" em uma noite devido a uma pequena atualização da dependência implícita. E de acordo com a lei de Murphy, isso acontecerá no dia do lançamento.


Portanto, desenvolvi uma regra para mim:


  1. Sempre corrija a versão dos produtos finais, pois as versões a serem usadas são de sua responsabilidade.
  2. Não corrija a versão usada para pacotes instalados. Ou limite-o a um intervalo, se a funcionalidade do pacote exigir.

O que vem a seguir?


Estou escrevendo testes!


Preencho o corpo das funções, adiciono comentários e faço os testes executar corretamente.
Neste ponto, a maioria dos desenvolvedores geralmente para (ainda acredito que todo mundo escreve testes =), publica o pacote e corrompe bugs. Mas eu segui em frente e pular na toca do coelho .


Como trabalhar com diferentes versões do python?


Na configuração tox , tox especifico o lançamento de testes em todas as versões interessantes do python:


 [tox] envlist = py{27,35,36,37,38} 

Usando pyenv, eu entrego as versões necessárias para mim localmente, para que o tox possa encontrá-las e criar ambientes de teste.


Onde estão os 100% estimados?


Vou adicionar uma medida da cobertura do código - para isso, há um excelente pacote de cobertura e uma integração não menos excelente com o pytest - pytest-cov .
requirements-test.txt agora se parece com isso:


 six=1.13.0 pytest=4.6.7 pytest-cov=2.8.1 parameterized=0.7.1 

De acordo com a regra acima, eu corrijo versões de pacotes que são usados ​​para executar testes.


Eu mudo o comando test run:


 deps = -r requirements.txt -r requirements-test.txt commands = pytest \ --cov=src/ \ --cov-config="{toxinidir}/tox.ini" \ --cov-append 

Estou coletando estatísticas de cobertura para todo o código da pasta src/ - o próprio pacote ( numeral_system / ) e necessário para o código de teste ( tests / ) - não quero que os testes contenham partes não executáveis?


--cov-append comando --cov-append todas as estatísticas coletadas para cada chamada em uma versão diferente do python em uma, porque a cobertura para o segundo e o terceiro python pode ser diferente (código dependente da versão e módulo seis !), Mas, no total, dê 100 % Um exemplo simples:


 if sys.version_info > (3, 0): # Python 3 code in this block else: # Python 2 code in this block 

Adicione um novo ambiente para criar um relatório de cobertura.


 [testenv:coverage_report] deps = coverage commands = coverage html ;   ,   coverage report --include="src/*" --fail-under=100 -m ;    100%  

E adiciono à lista de ambientes depois de executar os testes em todas as versões do python.


 [tox] envlist = py{27,35,36,37,38} coverage_report 

Após executar o comando tox , a pasta tox contém o arquivo index.html com um relatório bonito deve aparecer na raiz do projeto.


Para o emblema cobiçado, integro 100% ao serviço codecov , que já se integra ao github e permite visualizar o histórico de alterações na cobertura do código. Para fazer isso, é claro, você precisará criar uma conta lá.


O ambiente de lançamento final é o seguinte:


 [testenv:coverage_report] deps = coverage==5.0.2 codecov==2.0.15 commands = coverage html coverage report --include="src/*" --fail-under=100 -m coverage xml codecov -f coverage.xml --token=2455dcfa-f9fc-4b3a-b94d-9765afe87f0f ;     codecov,    

Agora resta apenas adicionar um link ao emblema no README.rst :


 |Code Coverage| .. |Code Coverage| image:: https://codecov.io/gh/zifter/numeral-system-py/branch/master/graph/badge.svg :target: https://codecov.io/gh/zifter/numeral-system-py 

Como formatar e analisar o código?


Muitos analisadores não existem, porque, na maioria das vezes, se complementam. Portanto, integrarei analisadores estáticos populares que verificarão a conformidade com o PEP8 , encontrarão possíveis problemas e corrigir todos os erros formate uniformemente o código.


Você deve considerar imediatamente onde especificar os parâmetros para o ajuste fino dos analisadores. Para fazer isso, você pode usar o arquivo setup.cfg , setup.cfg , um único arquivo personalizado ou arquivos específicos para analisadores. Decidi usar o tox.ini diretamente, pois você pode copiar o tox.ini para projetos futuros.


Isort


O isort é um utilitário para formatar importações.


Eu crio o ambiente a seguir para executar o isort no modo de formato de código.


 [testenv:isort] changedir = {toxinidir}/src deps = isort==4.3.21 commands = isort -y -sp={toxinidir}/tox.ini 

Infelizmente, o isort não pode especificar uma pasta para formatação. Portanto, você deve alterar o diretório de inicialização via changedir e especificar o caminho para o arquivo com as configurações -sp={toxinidir}/tox.ini . A opção -y é necessária para desativar o modo interativo.


Para executar testes, você precisa de um modo de verificação - para isso, existe um sinalizador --check-only :


 [testenv:isort-check] changedir = {toxinidir}/src deps = isort==4.3.21 commands = isort --check-only -sp={toxinidir}/tox.ini 

preto


Em seguida, integro-me ao formatador de código preto . Eu faço isso por analogia com o isort :


 [testenv:black] deps = black==19.10b0 commands = black src/ [testenv:black-check] deps = black==19.10b0 commands = black --check src/ 

Tudo funciona bem, mas há um conflito com o isort - há uma diferença na formatação das importações .


Em um dos comentários, encontrei uma configuração isort minimamente compatível, que usei:


 [isort] multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 use_parentheses=True line_length=88 

flake8


Em seguida, integro- me aos analisadores estáticos flake8 .


 [testenv:flake8-check] deps = flake8==3.7.9 commands = flake8 --config=tox.ini src/ 

problemas com a integração com o black novamente. Temos que adicionar um ajuste fino que, de fato, é recomendado pelo próprio black :


 [flake8] max-line-length=88 ignore=E203 

Infelizmente, a primeira vez que não funcionou. E231 missing whitespace after ',' com o erro E231 missing whitespace after ',' , tive que adicionar este erro para ignorar:


 [flake8] max-line-length=88 ignore=E203,E231 

pilão


Integrar com analisadores de código estático pylint


 [testenv:pylint-check] deps = {[testenv]deps} # pylint  ,     pylint==2.4.4 commands = pylint --rcfile=tox.ini src/ 

Imediatamente me deparei com restrições estranhas - nomes de funções de 30 caracteres (sim, escrevo nomes muito longos de métodos de teste) e avisos sobre a presença de TODO no código.
Eu tenho que adicionar algumas exceções:


 [MESSAGES CONTROL] disable=fixme,invalid-name 

Além disso, o momento desagradável é que os desenvolvedores do pylint já enterraram o python2.7 e não estão mais desenvolvendo um pacote para ele. Portanto, as verificações devem ser executadas no pacote atual do python3.7 .
Adicione a linha apropriada à configuração:


 [tox] envlist = isort-check black-check flake8-check pylint-check py{27,35,36,37,38} coverage_report basepython = python3.7 

Também é importante para a execução de testes em plataformas diferentes, pois a versão padrão do python nos sistemas de CI é diferente.


O que há com a CI?


Appveyor


Integre-se com o appveyor - CI para Windows. A configuração inicial é simples - tudo pode ser feito na interface, faça o download do arquivo yaml e o envie para o repositório.


 version: 0.0.{build} install: - cmd: >- C:\\Python37\\python -m pip install --upgrade pip C:\\Python37\\pip install tox build: off test_script: - cmd: C:\\Python37\\tox 

Aqui eu especifico explicitamente a versão do python3.7 , já que o python2.7 será usado por padrão (e o tox também usará essa versão, embora eu tenha especificado explicitamente o python3.7 ).
O link para o emblema, como de costume, é adicionado ao README.rst


 |Build status Appveyor| .. |Build status Appveyor| image:: https://ci.appveyor.com/api/projects/status/github/zifter/numeral-system-py?branch=master&svg=true :target: https://ci.appveyor.com/project/zifter/numeral-system-py 

Travis ci


Depois disso, integro-me ao Travis CI -CI no Linux (e no MacOS com Windows, mas as Python builds are not available on the macOS and Windows environments . A instalação é um pouco mais complicada, pois o arquivo de configuração será usado diretamente no repositório. Algumas iterações de tentativa e erro - a configuração está pronta, eu esmago em um belo commit e a solicitação de mesclagem está pronta.


 language: python python: 3.8 # dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) addons: apt: sources: - deadsnakes packages: - python3.5 - python3.6 - python3.7 - pypy install: - pip install tox script: - tox 

( Pergunta retórica: E por que projetos de IC gostam tanto do formato yaml? )


python3.8 a versão do python3.8 , uma vez que não funcionou corretamente através do addon , e o Travis CI cria virtualenv partir da versão especificada.


Pessoas familiarizadas com o Travis CI podem perguntar: por que não especificar explicitamente as versões em python dessa maneira? Afinal, o Travis CI cria automaticamente virtualenv e executa os comandos necessários.


O motivo é que precisamos coletar dados de cobertura de código de todas as versões. Mas os testes serão executados em diferentes trabalhos em paralelo, e é por isso que não funcionará para coletar um relatório de cobertura geral.


Claro, tenho certeza de que um pouco mais de compreensão e isso pode ser corrigido.


Por tradição, um link para um emblema também é adicionado ao README.rst


 |Build Status Travis CI| .. |Build Status Travis CI| image:: https://travis-ci.org/zifter/numeral-system-py.svg?branch=master :target: https://travis-ci.org/zifter/numeral-system-py 

A documentação


Eu acho que todos os desenvolvedores python usaram o serviço pelo menos uma vez - readthedocs.org . Parece-me que este é o melhor serviço para hospedar sua documentação.
Vou usar a ferramenta padrão para gerar a documentação do Sphinx . Sigo as etapas do manual inicial e obtenho a seguinte estrutura:


 src/ docs/ build/ #      html  source/ #     _static/ #   , ,  _templates/ #     conf.py #    index.rst #    make.bat Makefile # make     make 

Em seguida, você precisa executar as etapas mínimas para configurar:


  1. github por padrão, sugere a criação de um arquivo README.md no formato Markdown , quando como sphinx por padrão, sugere o uso de ReStructuredText .

Portanto, tive que reescrevê-lo no formato .rst . E se eu tivesse lido o manual de início até o fim pelo menos uma vez, percebi que a sphinx pode fazê-lo no Markdown .
README.rst arquivo README.rst em index.rst


 .. include:: ../../README.rst 

  1. Para gerar automaticamente a documentação dos comentários na fonte, adiciono a extensão sphinx.ext.autodoc .
  2. Eu conf.py pasta conf.py pacote ao conf.py Isso permitirá que o sphinx importe nosso código para análise.
     import os import sys sys.path.insert(0, os.path.abspath('./../../src')) import numeral_system 
  3. Eu adiciono a pasta docs/source/api-docs e largo o arquivo de descrição para cada módulo lá. A documentação deve ser gerada automaticamente a partir dos comentários:
     Roman numeral system ========================= .. automodule:: numeral_system.roman :members: 

Depois disso, o projeto está pronto para revelar sua descrição ao mundo. Você precisa criar uma conta (de preferência através de uma conta no github ) e importar seu projeto. As etapas detalhadas estão descritas nas instruções .
Por tradição, eu crio um ambiente em tox :


 [testenv:gen_docs] deps = -r docs/requirements.txt commands = sphinx-build -b html docs/source/ docs/build/ 

Eu uso o sphinx-build explicitamente, em vez de make , pois ele não existe no Windows. E não quero violar o princípio do desenvolvimento de plataforma cruzada.


Assim que as alterações forem congeladas, o readthedocs.org coletará automaticamente a documentação e a publicará.


Mas ... A Build failed . Não confirmei as sphinx_rtd_theme sphinx e sphinx_rtd_theme e esperava que o readthedocs.org as versões atuais. Mas isso não é verdade. Eu conserto:


 sphinx==2.3.1 sphinx_rtd_theme==0.4.3 

E eu crio um arquivo de configuração especial .readthedocs.yml para readthedocs.org , no qual descrevo o ambiente para iniciar a compilação:


 python: version: 3.7 install: - requirements: docs/requirements.txt - requirements: requirements.txt 

Foi aqui que o fato foi útil de que as dependências estão nos arquivos requirements.txt . Estou aguardando a compilação e a documentação fica disponível .


Adicione o selo novamente:


 |Docs| .. |Docs| image:: https://readthedocs.org/projects/numeral-system-py/badge/?version=latest&style=flat :target: https://numeral-system-py.readthedocs.io/en/latest/ 

Licenciamento


Vale a pena considerar a escolha de uma licença para o pacote.
Este é um tópico muito extenso, então eu li este artigo . Basicamente, a escolha é entre o MIT e o Apache 2.0 . Gostei da frase que foi retirada do contexto com sucesso:


 MIT       

Concordo plenamente, farei isso: se os planos mudarem, você poderá alterar facilmente a licença (embora as versões anteriores estejam sob a antiga).


Mais uma vez, adicione um emblema crachá deus :


 |License| .. |License| image:: https://img.shields.io/badge/License-MIT-yellow.svg :target: https://opensource.org/licenses/MIT 

Aqui você pode encontrar emblemas para todas as licenças.


Como fazer upload para pypi?


Primeiro, você precisa criar uma conta no pypi.org . Em seguida, prossiga com a preparação da embalagem.


Eu crio o setup.cfg


É necessário descrever corretamente a configuração para instalar / construir o pacote. Eu segui as instruções . É possível definir dados via setup.py , mas não há opções para definir alguns parâmetros. Portanto, use o arquivo setup.cfg , no qual você pode especificar todas as nuances. Encontrou um pequeno modelo sobre como preencher esse arquivo. Como resultado, eu uso esse e aquele arquivo - é mais conveniente.


Este arquivo também pode ser usado para definir pylint , flake8 e outras configurações, mas não o fiz.


Como montar um pacote?


Mais uma vez, estou escrevendo um ambiente que me ajudará a montar o pacote necessário:


 [testenv:build_wheel] skip_install = True deps = ;     wheel docutils pygments commands = python -c 'import shutil; (shutil.rmtree(p, ignore_errors=True) for p in ["build", "dist"]);' python setup.py sdist bdist_wheel 

Por que estou excluindo pastas usando python? Quero cumprir o requisito de desenvolvimento de plataforma cruzada, não há uma maneira conveniente de fazer isso no Windows e no Unix.


Eu inicio o ambiente de teste:


 tox -e build_wheel 

Como resultado, na pasta dist eu recebo:


 dist/ numeral-system_py-0.1.0.tar.gz numeral_system-py-0.1.0-py2.py3-none-any.whl 

Eu preencho!


Na verdade não.


Para começar, vale a pena verificar se o pacote funciona corretamente. Vou fazer o upload para o repositório de pacotes de teste. Portanto, você precisa criar outra conta, mas já em test.pypi.org .


Eu uso o pacote de cordéis para isso - uma ferramenta para preencher artefatos no PyPi.


 [testenv:test_upload] skip_install = True deps = twine ;    commands = python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* 

Inicialmente, o projeto era chamado numsys , mas ao tentar preenchê-lo, percebi que já havia um pacote com o mesmo nome! E o que é mais irritante - ele também sabe como converter em números romanos :) Ele não ficou muito chateado e renomeado para numeral-system-py numerais numeral-system-py .


Agora você precisa instalar o pacote a partir do ambiente de teste. A validação também deve ser automatizada:


 [testenv:test_venv] skip_install = True ;         deps = ;   commands = pip install -i https://test.pypi.org/simple/ numeral-system-py 

Agora você só precisa executar:


 tox -e test_venv ... test_venv: commands_succeeded congratulations :) 

Parece funcionar :)


Agora definitivamente derramar!


Sim


Eu crio um ambiente para carregar no repositório de produção.


 [testenv:pypi_upload] skip_install = True deps = twine commands = python -m twine upload dist/* 

E o ambiente para verificação da produção.


 [testenv:pypi_venv] skip_install = True deps = ;    commands = pip install numeral-system-py 

Tudo funciona?


Verifico com comandos simples:


 > virtualenv venv > source venv/bin/activate (venv) > pip install numeral-system-py (venv) > python >>> import numeral_system >>> numeral_system.roman.encode(7) 'VII' 

Está tudo ótimo!


Eu cortei o release no github , colecionei o pacote e o preenchi no propi pypi.


Observações


Atualizações de dependência


Durante a preparação deste artigo, foi lançada uma nova versão do pytest, na qual, de fato, o suporte ao python 3.4 foi descartado (na verdade, no pacote colorama ). Havia duas opções:


  1. Confirme a versão do colorama compatível com 3.4;
  2. Drop support 3.4 :)

A favor da segunda opção, o último argumento foi que o pip abandonou o suporte 3.4 na versão 19.1.


Também existem dependências fixas na forma de analisadores, formatadores e outros serviços. Essas dependências podem ser atualizadas ao mesmo tempo. Se tiver sorte, você só sairá com a versão de atualização; caso contrário, terá que corrigir o código ou até mesmo adicionar as configurações.


TravisCI


Não suporta python para MacOS e Windows. Há dificuldades em executar o tox para todas as versões do python no mesmo trabalho.


Versão do pacote


É necessário aderir ao controle de versão semântico , a saber, o formato:


 MAJOR.MINOR.PATCH 

Duplicação de meta informações


A versão do pacote e alguns outros parâmetros devem ser especificados para instalar o pacote (em setup.cfg ou setup.py ) e na documentação. Para evitar duplicação, ele fez uma indicação apenas no pacote numeral_system/__init__.py :


 __version__ = '0.2.0' 

E então em setup.py uso explicitamente essa variável


 setup(version=numeral_system.__version__) 

O mesmo vale para docs/source/conf.py


 release = numeral_system.__version__ 

O acima é válido para qualquer meta informação - REAMDE.rst , descrições de projetos, licenças, nomes de autores e muito mais.


É verdade que isso leva ao fato de que o pacote está sendo importado no momento da montagem, o que pode ser indesejável.


Duplicação de Dependências


, requirements.txt setup.cfg .
, — setup.cfg .
. , requirements-dev.txt .



, src/ . :


  • ;
  • , , ;

:


  • PyCharm ( ?) — src , .


— .
, :


 Add badge with supported version Support for py38 

:


 Try fix py38 env creating Try fix py38 env creating Try fix py38 env creating Fix check 

, 3 ! Travis CI.


, . — , .


, . CHANGELOG .



, Markdown . , rst .


Emblemas


— , , , . , .
coverage .


-


, , , . six , , type hint , asyncio — .
python2.7 , . , python3.5+, . , , CI . .


?


, :



Conclusão


open source , .
, python github , .
stackoverflow.com issues github .
. . ?..


, , .
, .


github .


Obrigada

Source: https://habr.com/ru/post/pt483512/


All Articles