Integrando MongoDB con Python usando PyMongo

MongoDB es una base de datos no relacional basada en documentos que permite una gran y fácil escalabilidad horizontal. En este artículo, usaremos PyMongo para conectarnos a una base de datos MongoDB y realizar operaciones CRUD usando Python.

Introducción

En esta publicación, nos sumergiremos en MongoDB como un almacén de datos desde la perspectiva de Python. Con ese fin, escribiremos un guión simple para mostrar lo que podemos lograr y los beneficios que podemos obtener de ello.

Las aplicaciones web, como muchas otras aplicaciones de software, funcionan con datos. La organización y el almacenamiento de estos datos son importantes, ya que dictan cómo interactuamos con las diversas aplicaciones a nuestra disposición. El tipo de datos manejados también puede influir en cómo llevamos a cabo este proceso.

Las bases de datos nos permiten organizar y almacenar estos datos, al mismo tiempo que controlamos cómo almacenamos, accedemos y aseguramos la información.

Bases de datos NoSQL

Hay dos tipos principales de bases de datos: bases de datos relacionales y bases de datos no relacionales.

Las bases de datos relacionales nos permiten almacenar, acceder y manipular datos en relación con otro dato en la base de datos. Los datos se almacenan en tablas organizadas con filas y columnas con relaciones que vinculan la información entre tablas. Para trabajar con estas bases de datos, usamos Structured Query Language (SQL) y los ejemplos incluyen mysql y postgresql.

Las bases de datos no relacionales almacenan datos ni en relación ni en forma tabular, como en las bases de datos relacionales. También se conocen como bases de datos NoSQL ya que no usamos SQL para interactuar con ellas.

Además, las bases de datos NoSQL se pueden dividir en almacenes de valores clave, almacenes de gráficos, almacenes de columnas y almacenes de documentos, en los que se incluye MongoDB.

MongoDB y cuándo usarlo

MongoDB es un almacén de documentos y una base de datos no relacional. Nos permite almacenar datos en colecciones que se componen de documentos.

En MongoDB, un documento es simplemente un formato de serialización binaria similar a JSON denominado BSON, o Binary-JSON, y tiene un tamaño máximo de 16 megabytes. Este límite de tamaño se aplica para garantizar un uso eficiente de la memoria y el ancho de banda durante la transmisión.

MongoDB también proporciona la Especificación GridFS en caso de que sea necesario almacenar archivos más grandes que el límite establecido.

Los documentos se componen de pares de valores de campo, al igual que en los datos JSON normales. Sin embargo, este formato BSON también puede contener más tipos de datos, como tipos de Fecha y tipos de Datos binarios. BSON fue diseñado para ser liviano, fácilmente transitable y eficiente al codificar y decodificar datos hacia y desde BSON.

Al ser un almacén de datos NoSQL, MongoDB nos permite disfrutar de las ventajas que conlleva el uso de una base de datos no relacional sobre una relacional. Una ventaja es que ofrece una alta escalabilidad al escalar horizontalmente de manera eficiente a través de fragmentación o partición de los datos y colocarlos en varias máquinas.

MongoDB también nos permite almacenar grandes volúmenes de datos estructurados, semiestructurados y no estructurados sin tener que mantener relaciones entre ellos. Al ser de código abierto, el costo de implementar MongoDB se mantiene bajo para solo mantenimiento y experiencia.

Como cualquier otra solución, existen desventajas al usar MongoDB. La primera es que no mantiene relaciones entre los datos almacenados. Debido a esto, es difícil realizar Transacciones ACID que aseguren la consistencia.

La complejidad aumenta cuando se trata de admitir transacciones ACID. MongoDB, al igual que otros almacenes de datos NoSQL, no es tan maduro como las bases de datos relacionales y esto puede dificultar la búsqueda de expertos.

La naturaleza no relacional de MongoDB lo hace ideal para el almacenamiento de datos en situaciones específicas sobre sus contrapartes relacionales. Por ejemplo, un escenario donde MongoDB es más adecuado que una base de datos relacional es cuando el formato de datos es flexible y no tiene relaciones.

Con datos flexibles/no relacionales, no necesitamos mantener las propiedades ACID al almacenar datos a diferencia de las bases de datos relacionales. MongoDB también nos permite escalar fácilmente los datos en nuevos nodos.

Sin embargo, con todas sus ventajas, MongoDB no es ideal cuando nuestros datos son de naturaleza relacional. Por ejemplo, si estamos almacenando registros de clientes y sus pedidos.

En esta situación, necesitaremos una base de datos relacional para mantener las relaciones entre nuestros datos, que son importantes. Tampoco es adecuado usar MongoDB si necesitamos cumplir con las propiedades de ACID.

Interacción con MongoDB a través de Mongo Shell {#interacción con mongodb a través de mongoshell}

Para trabajar con MongoDB necesitaremos instalar el MongoDB Server, el cual podemos descargar desde la página de inicio oficial. Para esta demostración, utilizaremos el Community Server gratuito.

El servidor MongoDB viene con una Concha Mongo que podemos usar para interactuar con el servidor a través de la terminal.

Para activar el shell, simplemente escriba mongo en su terminal. Recibirá información sobre la configuración del servidor MongoDB, incluida la versión de MongoDB y Mongo Shell, junto con la URL del servidor.

Por ejemplo, nuestro servidor se ejecuta en:

1
mongodb://127.0.0.1:27017

En MongoDB, se utiliza una base de datos para albergar colecciones que contienen documentos. A través del shell de Mongo, podemos crear una nueva base de datos o cambiar a una existente usando el comando use:

1
> use SeriesDB

Cada operación que ejecutemos después de esto se efectuará en nuestra base de datos SeriesDB. En la base de datos, almacenaremos colecciones, que son similares a las tablas en las bases de datos relacionales.

Por ejemplo, para los fines de este tutorial, agreguemos algunas series a la base de datos:

1
2
3
4
5
> db.series.insertMany([
... { name: "Game of Thrones", year: 2012},
... { name: "House of Cards", year: 2013 },
... { name: "Suits", year: 2011}
... ])

Nos reciben con:

1
2
3
4
5
6
7
8
{
    "acknowledged" : true,
    "insertedIds" : [
        ObjectId("5e300724c013a3b1a742c3b9"),
        ObjectId("5e300724c013a3b1a742c3ba"),
        ObjectId("5e300724c013a3b1a742c3bb")
    ]
}

Para recuperar todos los documentos almacenados en nuestra colección series, usamos db.inventory.find({}), cuyo equivalente en SQL es SELECT * FROM series. Pasar una consulta vacía (es decir, {}) devolverá todos los documentos:

1
2
3
4
5
6
> db.series.find({})

{ "_id" : ObjectId("5e3006258c33209a674d1d1e"), "name" : "The Blacklist", "year" : 2013 }
{ "_id" : ObjectId("5e300724c013a3b1a742c3b9"), "name" : "Game of Thrones", "year" : 2012 }
{ "_id" : ObjectId("5e300724c013a3b1a742c3ba"), "name" : "House of Cards", "year" : 2013 }
{ "_id" : ObjectId("5e300724c013a3b1a742c3bb"), "name" : "Suits", "year" : 2011 }

También podemos consultar datos usando la condición de igualdad, por ejemplo, para devolver todas las series de TV que se estrenaron en 2013:

1
2
3
> db.series.find({ year: 2013 })
{ "_id" : ObjectId("5e3006258c33209a674d1d1e"), "name" : "The Blacklist", "year" : 2013 }
{ "_id" : ObjectId("5e300724c013a3b1a742c3ba"), "name" : "House of Cards", "year" : 2013 }

El equivalente de SQL sería SELECT * FROM series WHERE year=2013.

MongoDB también nos permite actualizar documentos individuales usando db.collection.UpdateOne(), o realizar actualizaciones por lotes usando db.collection.UpdateMany(). Por ejemplo, para actualizar el año de lanzamiento de Suits:

1
2
3
4
5
6
7
> db.series.updateOne(
{ name: "Suits" },
{
    $set: { year: 2010 }
}
)
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

Finalmente, para eliminar documentos, Mongo Shell ofrece las funciones db.collection.deleteOne() y db.collection.deleteMany().

Por ejemplo, para eliminar todas las series que se estrenaron en 2012, ejecutaríamos:

1
2
> db.series.deleteMany({ year: 2012 })
{ "acknowledged" : true, "deletedCount" : 2 }

Se puede encontrar más información sobre las operaciones CRUD en MongoDB en la referencia en línea incluyendo más ejemplos, realizando operaciones con condiciones, atomicidad y mapeo de conceptos SQL a los conceptos y la terminología de MongoDB.

Integración de Python con MongoDB

MongoDB proporciona controladores y herramientas para interactuar con un almacén de datos de MongoDB utilizando varios lenguajes de programación, incluidos Python, JavaScript, Java, Go y C#, entre otros.

PyMongo es el controlador oficial de MongoDB para Python, y lo usaremos para crear un script simple que usaremos para manipular los datos almacenados en nuestra base de datos SeriesDB.

Con python 3.6+ y Entorno virtual instalados en nuestras máquinas, permítanos crea un entorno virtual para nuestra aplicación e instala PyMongo a través de pip:

1
2
3
$ virtualenv --python=python3 env --no-site-packages
$ source env/bin/activate
$ pip install pymongo

Usando PyMongo, vamos a escribir un script simple que podemos ejecutar para realizar diferentes operaciones en nuestra base de datos MongoDB.

Conectando a MongoDB

Primero, importamos pymongo en nuestro mongo_db_script.py y creamos un cliente conectado a nuestra instancia local de MongoDB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pymongo

# Create the client
client = MongoClient('localhost', 27017)

# Connect to our database
db = client['SeriesDB']

# Fetch our series collection
series_collection = db['series']

Hasta ahora, hemos creado un cliente que se conecta a nuestro servidor MongoDB y lo usamos para obtener nuestra base de datos 'SeriesDB'. Luego buscamos nuestra colección 'series' y la almacenamos en un objeto.

Creación de documentos {#creación de documentos}

Para hacer que nuestro script sea más conveniente, escribiremos funciones que envuelvan PyMongo para permitirnos manipular fácilmente los datos. Usaremos Diccionarios de Python para representar documentos y pasaremos estos diccionarios a nuestras funciones. Primero, creemos una función para insertar datos en nuestra colección 'series':

1
2
3
4
5
6
7
# Imports truncated for brevity

def insert_document(collection, data):
    """ Function to insert a document into a collection and
    return the document's id.
    """
    return collection.insert_one(data).inserted_id

Esta función recibe una colección y un diccionario de datos e inserta los datos en la colección proporcionada. Luego, la función devuelve un identificador que podemos usar para consultar con precisión el objeto individual de la base de datos.

También debemos tener en cuenta que MongoDB agrega una clave _id adicional a nuestros documentos, cuando no se proporcionan, al crear los datos.

Ahora intentemos agregar un programa usando nuestra función:

1
2
3
4
5
new_show = {
    "name": "FRIENDS",
    "year": 1994
}
print(insert_document(series_collection, new_show))

La salida es:

1
5e4465cfdcbbdc68a6df233f

Cuando ejecutamos nuestro script, el _id de nuestro nuevo programa se imprime en la terminal y podemos usar este identificador para obtener el programa más adelante.

Podemos proporcionar un valor _id en lugar de asignarlo automáticamente, lo que proporcionaríamos en el diccionario:

1
2
3
4
5
new_show = {
    "_id": "1",
    "name": "FRIENDS",
    "year": 1994
}

Y si intentáramos almacenar un documento con un _id existente, nos recibiría un error similar al siguiente:

1
DuplicateKeyError: E11000 duplicate key error index: SeriesDB.series.$id dup key: { : 1}

Recuperando documentos {#recuperando documentos}

Para recuperar documentos de la base de datos, usaremos find_document(), que consulta nuestra colección en busca de documentos únicos o múltiples. Nuestra función recibirá un diccionario que contiene los elementos por los que queremos filtrar y un argumento opcional para especificar si queremos un documento o varios documentos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Imports and previous code truncated for brevity

def find_document(collection, elements, multiple=False):
    """ Function to retrieve single or multiple documents from a provided
    Collection using a dictionary containing a document's elements.
    """
    if multiple:
        results = collection.find(elements)
        return [r for r in results]
    else:
        return collection.find_one(elements)

Y ahora, usemos esta función para encontrar algunos documentos:

1
2
result = find_document(series_collection, {'name': 'FRIENDS'})
print(result)

Al ejecutar nuestra función, no proporcionamos el parámetro múltiple y el resultado es un solo documento:

1
{'_id': ObjectId('5e3031440597a8b07d2f4111'), 'name': 'FRIENDS', 'year': 1994}

Cuando se proporciona el parámetro “múltiple”, el resultado es una lista de todos los documentos de nuestra colección que tienen un atributo “nombre” establecido en “AMIGOS”.

Actualización de documentos

Nuestra siguiente función, update_document(), se usará para actualizar un solo documento específico. Usaremos el _id del documento y la colección a la que pertenece a la hora de localizarlo:

1
2
3
4
5
6
# Imports and previous code truncated for brevity

def update_document(collection, query_elements, new_values):
    """ Function to update a single document in a collection.
    """
    collection.update_one(query_elements, {'$set': new_values})

Ahora, insertemos un documento:

1
2
3
4
5
new_show = {
    "name": "FRIENDS",
    "year": 1995
}
id_ = insert_document(series_collection, new_show)

Una vez hecho esto, actualicemos el documento, que especificaremos usando el _id devuelto al agregarlo:

1
update_document(series_collection, {'_id': id_}, {'name': 'F.R.I.E.N.D.S'})

Y finalmente, vamos a buscarlo para verificar que el nuevo valor se haya colocado e imprimamos el resultado:

1
2
result = find_document(series_collection, {'_id': id_})
print(result)

Cuando ejecutamos nuestro script, podemos ver que nuestro documento ha sido actualizado:

1
{'_id': ObjectId('5e30378e96729abc101e3997'), 'name': 'F.R.I.E.N.D.S', 'year': 1995}

Eliminación de documentos

Y finalmente, escribamos una función para borrar documentos:

1
2
3
4
5
6
# Imports and previous code truncated for brevity

def delete_document(collection, query):
    """ Function to delete a single document from a collection.
    """
    collection.delete_one(query)

Dado que estamos usando el método delete_one, solo se puede eliminar un documento por llamada, incluso si la consulta coincide con varios documentos.

Ahora, usemos la función para eliminar una entrada:

1
delete_document(series_collection, {'_id': id_})

Si intentamos recuperar ese mismo documento:

1
2
result = find_document(series_collection, {'_id': id_})
print(result)

Somos recibidos con el resultado esperado:

1
None

Próximos pasos

Hemos resaltado y usado algunos de los métodos de PyMongo para interactuar con nuestro servidor MongoDB desde un script de Python. Sin embargo, no hemos utilizado todos los métodos disponibles a través del módulo.

Todos los métodos disponibles se pueden encontrar en la documentación oficial de PyMongo y están clasificados según los submódulos.

Hemos escrito un script simple que realiza una funcionalidad CRUD rudimentaria en una base de datos MongoDB. Si bien podríamos importar las funciones en una base de código más compleja, o en una aplicación Flask/Django, por ejemplo, estos marcos ya tienen bibliotecas para lograr los mismos resultados. Estas bibliotecas lo hacen más fácil, más conveniente y nos ayudan a conectarnos de manera más segura a MongoDB.

Por ejemplo, con Django podemos usar bibliotecas como Motor Django MongoDB y [Djongo](https://pypi.org/project /djongo/), mientras que Flask tiene Matraz-PyMongo que ayuda a cerrar la brecha entre Flask y PyMongo para facilitar la conectividad perfecta a una base de datos MongoDB.

Conclusión

MongoDB es un almacén de documentos y se incluye en la categoría de bases de datos no relacionales (NoSQL). Tiene ciertas ventajas en comparación con las bases de datos relacionales, así como algunas desventajas.

Si bien no es adecuado para todas las situaciones, aún podemos usar MongoDB para almacenar datos y manipular los datos de nuestras aplicaciones de Python usando PyMongo entre otras bibliotecas, lo que nos permite aprovechar el poder de MongoDB en situaciones en las que es más adecuado.

Por lo tanto, depende de nosotros examinar cuidadosamente nuestros requisitos antes de tomar la decisión de utilizar MongoDB para almacenar datos.

El script que hemos escrito en esta publicación se puede encontrar en GitHub.