Muitas das vezes que precisamos persistir dados chave-valor optamos por utilizar bases de dados focados somente em armazenar esse tipo de dado: as chamadas key-value stores, como por exemplo Riak, Redis e Memcached (esse último, persiste apenas em memória). Porém, adicionar mais um software/serviço em uma aplicação trás mais trabalho para a equipe (deployment, manutenção) e maior possibilidade de falhas (não somente de segurança, mas relacionadas a lentidão e downtime também).
Caso você já tenha o MongoDB rodando em sua infraestrutura e necessite de uma key-value store, você poderá utilizar a biblioteca Python mongodict, que desenvolvi em julho de 2012 para aproveitar a infra já disponível e, ao mesmo tempo, poder utilizar os dados persistidos com facilidade.
Dicionários no MongoDB
Desenvolvedores Python são hash-addicted (rá!) - usamos muito os
dicionários, para tudo (às vezes até excessivamente). Meu objetivo ao
desenvolver o mongodict foi justamente aproveitar a interface que
já conhecemos de dicionários (dict[chave] = valor
) para persistir os dados no
MongoDB sem precisar utilizar uma API diferente da que já estamos
acostumados.
Instalando
Sem mais blá blá blá, vamos lá: o mongodict está disponível no PyPI então, para instalá-lo, basta executar o comando:
pip install mongodict
Ele funciona em Python 2.7 e 3.3.
Usando
A biblioteca é bem simples: existe apenas uma classe MongoDict
dentro do
módulo. Aprenda com exemplos:
from mongodict import MongoDict
# Cria uma instância do "dicionário", já conectando no mongod
# Argumentos `host`, `port`, `database` e `collection` são opcionais
mydict = MongoDict()
mydict['answer'] = 42
print(mydict['answer']) # 42
print('answer' in mydict) # True
del mydict['answer']
print('answer' in mydict) # False
mydict.update({'spam': 'eggs', 'ham': 'damn'})
for key, value in mydict.items():
print('{} = {}'.format(key, value))
# ham = damn
# spam = eggs
Para assegurar que a classe MongoDict
segue o protocolo
MutableMapping, utilizei os testes do próprio
CPython!
:-)
Serialização de Dados
O mongodict utiliza a biblioteca pickle para serializar/desserializar os dados (apenas os valores, não as chaves), portanto, qualquer dado que puder ser serializado com a pickle poderá ser salvo.
Porém, em alguns casos é desejável alterar o serializador. Digamos, por
exemplo, que eu esteja salvando o conteúdo de arquivos HTML e queira
compactá-los para economizar espaço em meu servidor - daí basta passar o
parâmetro codec
:
from zlib import compress, decompress
from urllib import urlopen
from mongodict import MongoDict
mydict = MongoDict(codec=(compress, decompress))
url = 'http://www.CursoDeArduino.com.br/'
mydict['curso-de-arduino'] = urlopen(url).read()
print(mydict['curso-de-arduino'])
# <... imprime o HTML da página ...>
Nesse caso, se conectarmos no MongoDB diretamente podemos ver o tamanho armazenado lá:
colecao = mydict._collection
documento = colecao.find_one({'_id': 'curso-de-arduino'})
print(len(documento['v'])) # 5763
print(len(mydict['curso-de-arduino'])) # 20076
Cuidado com Objetos Mutáveis
Caso o valor de uma das chaves seja um objeto mutável (por exemplo: uma lista), lembre-se que alterar o objeto recuperado do MongoDict não irá atualizá-lo no banco (pois ele fica em memória e é um objeto Python nativo). Por exemplo:
from mongodict import MongoDict
mydict = MongoDict()
mydict['compras'] = ['tomate', 'rúcula', 'queijo']
mydict['compras'].append('azeite')
print(mydict['compras'])
# ['tomate', 'r\xc3\xbacula', 'queijo']
Para corrigir isso, devemos explicitamente atualizar o valor da chave:
compras = mydict['compras']
compras.append('azeite')
mydict['compras'] = compras
print(mydict['compras'])
# ['tomate', 'r\xc3\xbacula', 'queijo', 'azeite']
Autenticação
Para autenticar-se no servidor MongoDB, utilize o parâmetro auth
:
from mongodict import MongoDict
mydict = MongoDict(auth=('user', 'myprecious'))
Dica Bônus
Caso você ache chato ter que acessar mydict['chave']
e prefira
mydict.chave
, utilize a biblioteca attrdict. Instale-a a partir
do PyPI:
pip install attrdict
E para usar, é bem fácil:
from attrdict import AttrDict
from mongodict import MongoDict
mydict = AttrDict(MongoDict())
mydict.answer = 42
mydict.question = '?'
print('Answer to "{question}" = {answer}'.format(**mydict))
Você só conseguirá acessar como atributo o primeiro nível, ou seja,
mydict.chave.outra_chave
não funcionará (utilize
mydict.chave['outra_chave']
).
Contribuindo
Caso queira contribuir, acesse o issue tracker do mongodict no GitHub. Fique à vontade para relatar bugs, sugerir funcionalidades e enviar pull requests! =)