Home Articles Categories Series
Pythonise Just now

Connecting to a Microsoft Azure Cosmos DB with Python and the MongoDB API

Debugging and connecting to an Azure Cosmos DB using the MongoDB API using PyMongo, PyMODM and MongoEngine


Article Posted on by in Python
Julian Nash · a month ago in Python

Cosmos DB is a cloud database from Microsoft Azure, supporting many APIs including MongoDB.

  • Learn more about Cosmos DB Here

At the time of writing, the connection string provided in the Azure portal wasn't working for me with any of the MongoDB Python drivers that I tried, each requiring some additional arguments in the connection string to successfully establish a connection with the database.

You can find your Cosmos connection details in the CosmosDB connection string tab in the Microsoft Azure portal. Be sure to replace the values in the code samples below with your own.

The solution

After some initial debugging, connecting to Cosmos DB using the MongoDB API required additional arguments in the connection string:

mongodb://<username>:<password>@<host>:<port>/<db_name>?ssl=true&ssl_cert_reqs=CERT_NONE&retrywrites=false
  • ssl=true

Cosmos DB enforces ssl when connecting to the database. We can turn on SSL by providing ssl=true in the connection string. Without it, Python raises the following error:

pymongo.errors.ServerSelectionTimeoutError

However ssl=true by itself wasn't working for me, I had to provide an additional argument in the connection string. This could be because I'm using MacOS and there may be some permission issues, so Linux & Windows users, your mileage may vary:

  • ssl_cert_reqs=CERT_NONE

You can read more about pymongo and SSL Here

Without ssl_cert_reqs=CERT_NONE in the connection string, I got the following error:

pymongo.errors.ServerSelectionTimeoutError: <host>:<port>: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1076)

And finally:

  • retrywrites=false

Without this parameter in the connection string, Python raised the following error:

pymongo.errors.OperationFailure: Retryable writes are not supported. Please disable retryable writes by specifying "retrywrites=false" in the connection string or an equivalent driver specific config.

This seems to be some kind of compatibility issue between the MongoDB API and Cosmos DB. Here's what the MongoDB docs say about Retryable writes:

Retryable writes allow MongoDB drivers to automatically retry certain write operations a single time if they encounter network errors, or if they cannot find a healthy primary in the replica sets or sharded cluster.

My instance of Cosmos DB in Azure is configured to use the MongoDB 3.6 API and I'm running pymongo version 3.9.0:

"Clients require MongoDB drivers updated for MongoDB 3.6 or greater"

I need to do some more research to find out what's going on here, so I'll move on for now.

pymongo and CosmosDB

pymongo is the official native Python driver for MongoDB, used by many other Python Mongo libraries to handle communication with the database:

import os

from pymongo import MongoClient

db_name = os.getenv("MONGO_DB")
host = os.getenv("MONGO_HOST")
port = 10255
username = os.getenv("MONGO_USERNAME")
password = os.getenv("MONGO_PASSWORD")
args = "ssl=true&retrywrites=false&ssl_cert_reqs=CERT_NONE"

connection_uri = f"mongodb://{username}:{password}@{host}:{port}/{db_name}?{args}"

client = MongoClient(connection_uri)

db = client[db_name]
user_collection = db['user']

# Save to the DB
user_collection.insert_one({"email": "amer@foobar.com"})

# Query the DB
for user in user_collection.find():
    print(user)

pymodm and CosmosDB

pymodm is an ODM built on top of pymongo, requiring the same connection string as above. It uses the python class syntax to define collections and documents:

import os

from pymodm.connection import connect
from pymongo.write_concern import WriteConcern
from pymodm import MongoModel, fields

db_name = os.getenv("MONGO_DB")
host = os.getenv("MONGO_HOST")
port = 10255
username = os.getenv("MONGO_USERNAME")
password = os.getenv("MONGO_PASSWORD")
args = "ssl=true&retrywrites=false&ssl_cert_reqs=CERT_NONE"

connection_uri = f"mongodb://{username}:{password}@{host}:{port}/{db_name}?{args}"

connect(connection_uri, alias=db_name)


class User(MongoModel):
    email = fields.CharField()

    class Meta:
        write_concern = WriteConcern(j=True)
        connection_alias = db_name


# Save to the DB
new_user = User(email="jonny5@foobar.com").save()

# Query the DB
for user in User.objects.all():
    print(user.email)

mongoengine and CosmosDB

Finally, I decided to try with mongoengine, a popular ODM (Object Document Mapper) for MongoDB.

mongoengine is built on top of pymongo, however instead of providing a connection string, keyword arguments are provided to the connect function and mongoengine takes care of constructing the connection string for us:

import os
import ssl

import mongoengine as mongo

mongo.connect(
    db=os.getenv("MONGO_DB"),
    host=os.getenv("MONGO_HOST"),
    port=10255,
    username=os.getenv("MONGO_USERNAME"),
    password=os.getenv("MONGO_PASSWORD"),
    authentication_source="admin",
    ssl=True,
    ssl_cert_reqs=ssl.CERT_NONE,
    retrywrites=False
)


class User(mongo.DynamicDocument):
    email = mongo.StringField()


# Save to the DB
new_user = User(email="pedro@foobar.com").save()

# Query the DB
for user in User.objects():
    print(user.email)

References

Previous article
Pythons Enum module
Did you find this article useful?
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
Contents
Loading...