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:
- Sempre corrija a versão dos produtos finais, pois as versões a serem usadas são de sua responsabilidade.
- 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):
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
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/
Há 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:
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
- Para gerar automaticamente a documentação dos comentários na fonte, adiciono a extensão sphinx.ext.autodoc .
- 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
- 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:
- Confirme a versão do colorama compatível com 3.4;
- 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
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/
. :
:
— .
, :
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