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)