Advertisement
  1. Code
  2. Python

Como Escrever, Empacotar e Distribuir uma Biblioteca em Python

Scroll to top
Read Time: 8 min

() 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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.