() translation by (you can also view the original English article)
Python é uma ótima linguagem de programação, mas empacotamento é um dos seus pontos fracos. Esse é um fato já conhecido pela comunidade. A instalação, importação, uso e criação de pacotes melhorou muito ao longo dos anos, mas ainda não está no mesmo nível que novas linguagens de programação como Go e Rust que aprenderam muito com as batalhas do Python e de outras linguagens mais maduras.
Nesse tutorial, nós vamos aprender tudo que você precisa saber para escrever, empacotar e distribuir seus próprios pacotes.
Como Escrever uma Biblioteca em Python
Uma biblioteca Python é uma coleção coerente de módulos Python que são organizados como um pacote Python. No geral, isso significa que todos os módulos vivem dentro do mesmo diretório e que esse diretório está armazenado em um dos caminhos de busca que o Python realiza para carregá-lo.
Vamos, rapidamente, escrever um pacote em Python 3 para ilustrar todos esses conceitos.
O Pacote "Pathology"
Python 3 tem um excelente objeto Path, que é uma melhora incrível sobre as fraquezas que o módulo os.path no Python 2 apresentava. Mas ainda falta uma funcionalidade crucial - encontrar o caminho o script atual. Isso é muito importante quando você quer localizar arquivos de acesso relativos ao script que você está editando.
Em muitos casos, o script pode ser instalado em qualquer local, então você não pode utilizar caminhos absolutos, e o diretório de trabalho pode ser configurado para qualquer valor, e por isso você não pode utilizar caminhos relativos. Se você quiser acessar um arquivo em um sub-diretório ou em um diretório-pai, você precisa descobrir onde o script está localizado.
Aqui está como você pode fazer isso em Python:
1 |
import pathlib |
2 |
|
3 |
script_dir = pathlib.Path(__file__).parent.resolve() |
Para acessar o arquivo chamado 'file.txt' que está localizado em um sub-diretório de onde o script está, chamado de 'data', você pode utilizar a seguinte linha de código: print(open(str(script_dir/'data/file.txt').read())
Com o pacote pathology, você tem um método script_dir, e pode utilizá-lo da seguinte forma:
1 |
from pathology.Path import script_dir |
2 |
|
3 |
print(open(str(script_dir()/'data/file.txt').read()) |
4 |
Sim, é bem fácil. O pacote 'pathology' é muito simples. Ele chama a própria classe Path - a partir do módulo pathlib, e adiciona uma método estático script_dir() que sempre retorna o caminho do script considerado.
Aqui está a implementação:
1 |
import pathlib |
2 |
import inspect |
3 |
|
4 |
class Path(type(pathlib.Path())): |
5 |
@staticmethod |
6 |
def script_dir(): |
7 |
print(inspect.stack()[1].filename) |
8 |
p = pathlib.Path(inspect.stack()[1].filename) |
9 |
return p.parent.resolve() |
Devido à implementação multi-plataforma da pathlib.Path, você pode derivar a sua implementação diretamente dela a partir de uma sub-classe específica (PosixPath ou WindowsPath). A resolução do diretório do script usa o módulo 'inspect' para descobir o nome do arquivo.
Testando o Pacote "Pathology"
Sempre que você escrever algo mais complexo, você deve testá-lo. O módulo pathology não é exceção. Aqui está os testes utilizando o framework padrão para testes unitários:
1 |
import os |
2 |
import shutil |
3 |
from unittest import TestCase |
4 |
from pathology.path import Path |
5 |
|
6 |
|
7 |
class PathTest(TestCase): |
8 |
def test_script_dir(self): |
9 |
expected = os.path.abspath(os.path.dirname(__file__)) |
10 |
actual = str(Path.script_dir()) |
11 |
self.assertEqual(expected, actual) |
12 |
|
13 |
def test_file_access(self): |
14 |
script_dir = os.path.abspath(os.path.dirname(__file__)) |
15 |
subdir = os.path.join(script_dir, 'test_data') |
16 |
if Path(subdir).is_dir(): |
17 |
shutil.rmtree(subdir) |
18 |
os.makedirs(subdir) |
19 |
file_path = str(Path(subdir)/'file.txt') |
20 |
content = '123' |
21 |
open(file_path, 'w').write(content) |
22 |
test_path = Path.script_dir()/subdir/'file.txt' |
23 |
actual = open(str(test_path)).read() |
24 |
|
25 |
self.assertEqual(content, actual) |
O Caminho Python
Pacotes Python devem ser instalados em algum lugar que faça parte do caminho de busca que o Python realiza para carregar novos módulos. Essa lista de caminhos é uma lista de diretórios e está disponível em sys.path
. Aqui está o meu atual sys.path:
1 |
>>> print('\n'.join(sys.path)) |
2 |
|
3 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python36.zip |
4 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6 |
5 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/lib-dynload |
6 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages |
7 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg |
Note que a primeira linha vazia representa o diretório atual, então você pode sempre importar módulos que estão no diretório atual de trabalho. Além disso você pode adicionar ou remover diretórios do sys.path.
Você também pode definir uma variável de ambiente PYTHONPATH, e existem alguns modos de controlá-la. O site-packages
está incluso por padrão, e pra lá que vão os pacotes que você instala utilizando o PIP.
Como Empacotar uma Biblioteca em Python
Agora que você tem seu código testado, vamos empacotar isso tudo em uma biblioteca. Python provê um jeito fácil de fazer isso via o módulo setup. Você cria um arquivo chamado setup.py no diretório raiz do seu pacote. Então, para criar um código de distribuição, você roda: python setup.py sdist
Para criar um arquivo binário chamado de 'wheel', você roda: python setup.py bdist_wheel
Aqui está o arquivo setup.py da nossa biblioteca pathology:
1 |
from setuptools import setup, find_packages |
2 |
|
3 |
setup(name='pathology', |
4 |
version='0.1', |
5 |
url='https://github.com/the-gigi/pathology', |
6 |
license='MIT', |
7 |
author='Gigi Sayfan', |
8 |
author_email='the.gigi@gmail.com', |
9 |
description='Add static script_dir() method to Path', |
10 |
packages=find_packages(exclude=['tests']), |
11 |
long_description=open('README.md').read(), |
12 |
zip_safe=False) |
Ele inclui algumas informações em adição ao item 'packages' que usa a função find_packages()
importada do setuptools
para encontrar sub-pacotes.
Vamos construir uma fonte de distribuição:
1 |
$ python setup.py sdist |
2 |
running sdist |
3 |
running egg_info |
4 |
creating pathology.egg-info |
5 |
writing pathology.egg-info/PKG-INFO |
6 |
writing dependency_links to pathology.egg-info/dependency_links.txt |
7 |
writing top-level names to pathology.egg-info/top_level.txt |
8 |
writing manifest file 'pathology.egg-info/SOURCES.txt' |
9 |
reading manifest file 'pathology.egg-info/SOURCES.txt' |
10 |
writing manifest file 'pathology.egg-info/SOURCES.txt' |
11 |
warning: sdist: standard file not found: should have one of README, README.rst, README.txt |
12 |
|
13 |
running check |
14 |
creating pathology-0.1 |
15 |
creating pathology-0.1/pathology |
16 |
creating pathology-0.1/pathology.egg-info |
17 |
copying files to pathology-0.1... |
18 |
copying setup.py -> pathology-0.1 |
19 |
copying pathology/__init__.py -> pathology-0.1/pathology |
20 |
copying pathology/path.py -> pathology-0.1/pathology |
21 |
copying pathology.egg-info/PKG-INFO -> pathology-0.1/pathology.egg-info |
22 |
copying pathology.egg-info/SOURCES.txt -> pathology-0.1/pathology.egg-info |
23 |
copying pathology.egg-info/dependency_links.txt -> pathology-0.1/pathology.egg-info |
24 |
copying pathology.egg-info/not-zip-safe -> pathology-0.1/pathology.egg-info |
25 |
copying pathology.egg-info/top_level.txt -> pathology-0.1/pathology.egg-info |
26 |
Writing pathology-0.1/setup.cfg |
27 |
creating dist |
28 |
Creating tar archive |
29 |
removing 'pathology-0.1' (and everything under it) |
O aviso (em inglês: 'warning') é porque eu utilizei um arquivo README.md não padrão. O resultado é um arquivo tar-gzipped dentro do diretório de distribuição:
1 |
$ ls -la dist |
2 |
total 8 |
3 |
drwxr-xr-x 3 gigi.sayfan gigi.sayfan 102 Apr 18 21:20 .
|
4 |
drwxr-xr-x 12 gigi.sayfan gigi.sayfan 408 Apr 18 21:20 .. |
5 |
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 1223 Apr 18 21:20 pathology-0.1.tar.gz
|
E aqui está uma distribuição binária:
1 |
$ python setup.py bdist_wheel |
2 |
running bdist_wheel |
3 |
running build |
4 |
running build_py |
5 |
creating build |
6 |
creating build/lib |
7 |
creating build/lib/pathology |
8 |
copying pathology/__init__.py -> build/lib/pathology |
9 |
copying pathology/path.py -> build/lib/pathology |
10 |
installing to build/bdist.macosx-10.7-x86_64/wheel |
11 |
running install |
12 |
running install_lib |
13 |
creating build/bdist.macosx-10.7-x86_64 |
14 |
creating build/bdist.macosx-10.7-x86_64/wheel |
15 |
creating build/bdist.macosx-10.7-x86_64/wheel/pathology |
16 |
copying build/lib/pathology/__init__.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology |
17 |
copying build/lib/pathology/path.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology |
18 |
running install_egg_info |
19 |
running egg_info |
20 |
writing pathology.egg-info/PKG-INFO |
21 |
writing dependency_links to pathology.egg-info/dependency_links.txt |
22 |
writing top-level names to pathology.egg-info/top_level.txt |
23 |
reading manifest file 'pathology.egg-info/SOURCES.txt' |
24 |
writing manifest file 'pathology.egg-info/SOURCES.txt' |
25 |
Copying pathology.egg-info to build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1-py3.6.egg-info |
26 |
running install_scripts |
27 |
creating build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1.dist-info/WHEEL |
O pacote pathology contém só módulos puramente escritos em Python, então um pacote universal pode ser construído. Se o seu pacote inclui arquivos em C, você vai ter que construir um 'wheel' separada para cada plataforma:
1 |
$ ls -la dist |
2 |
total 16 |
3 |
drwxr-xr-x 4 gigi.sayfan gigi.sayfan 136 Apr 18 21:24 .
|
4 |
drwxr-xr-x 13 gigi.sayfan gigi.sayfan 442 Apr 18 21:24 .. |
5 |
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 2695 Apr 18 21:24 pathology-0.1-py3-none-any.whl
|
6 |
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 1223 Apr 18 21:20 pathology-0.1.tar.gz
|
Para um mergulho mais profundo no assunto de empacotamento de bibliotecas Python, confira Como Escrever Seus Próprios Pacotes em Python.
Como Distribuir um Pacote Python
Python tem uma central de repositórios chamada PyPI (Python Packages Index). Quando você instala um pacote utilizando PIP, ele irá baixar o pacote do PyPI (a menos que você determine um repositório diferente). Para distribuir nosso pacote, nós precisamos fazer o upload dele para o PyPI e prover algumas informações extras. Os passos são:
- Criar uma conta no PyPI (somente uma).
- Registrar seu pacote.
- Fazer o upload do seu pacote.
Criar uma Conta
Você pode criar uma conta no website do PyPI. Então, crie um arquivo .pypirc no seu diretório inicial.
1 |
[distutils] |
2 |
index-servers=pypi |
3 |
|
4 |
[pypi] |
5 |
repository = https://pypi.python.org/pypi |
6 |
username = the_gigi |
A título de teste, você pode adicionar um "'pypitext' index server" no seu arquivo .pypirc:
1 |
[distutils] |
2 |
index-servers= |
3 |
pypi
|
4 |
pypitest
|
5 |
|
6 |
[pypitest] |
7 |
repository = https://testpypi.python.org/pypi |
8 |
username = the_gigi |
9 |
|
10 |
[pypi] |
11 |
repository = https://pypi.python.org/pypi |
12 |
username = the_gigi |
Registrar seu Pacote
Se essa é a primeira versão do seu pacote, você precisa registrá-lo dentro do PyPI. Use o comando de registro do setup.py. Ele vai pedir por sua senha. Note que eu o apontei para o repositório de teste aqui:
1 |
$ python setup.py register -r pypitest |
2 |
running register |
3 |
running egg_info |
4 |
writing pathology.egg-info/PKG-INFO |
5 |
writing dependency_links to pathology.egg-info/dependency_links.txt |
6 |
writing top-level names to pathology.egg-info/top_level.txt |
7 |
reading manifest file 'pathology.egg-info/SOURCES.txt'
|
8 |
writing manifest file 'pathology.egg-info/SOURCES.txt'
|
9 |
running check |
10 |
Password: |
11 |
Registering pathology to https://testpypi.python.org/pypi |
12 |
Server response (200): OK |
Fazer o Upload do seu Pacote
Agora que o seu pacote está registrado, nós podemos fazer o upload dele. Eu recomendo usar o twine, que é mais seguro. Instale-o usualmente através do pip install twine
. Então, faça o upload do seu pacote utilizando o twine e fornecendo a sua senha:
1 |
$ twine upload -r pypitest -p <redacted> dist/* |
2 |
Uploading distributions to https://testpypi.python.org/pypi |
3 |
Uploading pathology-0.1-py3-none-any.whl |
4 |
[================================] 5679/5679 - 00:00:02 |
5 |
Uploading pathology-0.1.tar.gz |
6 |
[================================] 4185/4185 - 00:00:01 |
Para um mergulho mais profundo nesse tópico de distruição de pacotes, confira Como Compartilhar seus Pacotes Python.
Conclusão
Nesse tutorial, nós passamos por todo o processo de escrever uma biblioteca Python, empacotá-la, e distribuí-la no PyPI. Nesse ponto, você deve ter todas as ferramentas necessárias para escrever e compartilhar suas bibliotecas com o mundo.
Em adição, não se hesite em conferir o que nós temos disponível para venda e estudo no nosso maketplace, e por favor, envie qualquer dúvida e dê seu feedback utilizando a seção abaixo.