Getting started¶
Now it’s time to begin doing some work with Cloudant and Python. For working code samples of any of the API’s please go to our test suite.
Connections¶
In order to manage a connection you must first initialize the connection by
constructing either a Cloudant
or CouchDB
client. Since connecting to
the Cloudant managed service provides extra end points as compared to a CouchDB
server, we provide the two different client implementations in order to
connect to the desired database service. Once the client is constructed,
you follow that up by connecting to the server, performing your tasks, and
then disconnecting from the server.
Later in the Context managers section we will see how to simplify this process through the use of the Python with statement.
Note: If you require retrying requests after an HTTP 429 error, the
Replay429Adapter
can be added when constructing a Cloudant
client and configured with an initial back off and retry count.
Note: Currently, the connect and read timeout will wait forever for
a HTTP connection or a response on all requests. A timeout can be
set using the timeout
argument when constructing a client.
Connecting with a client¶
# Use CouchDB to create a CouchDB client
# from cloudant.client import CouchDB
# client = CouchDB(USERNAME, PASSWORD, url='http://127.0.0.1:5984', connect=True)
# Use Cloudant to create a Cloudant client using account
from cloudant.client import Cloudant
client = Cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME, connect=True)
# or using url
# client = Cloudant(USERNAME, PASSWORD, url='https://acct.cloudant.com')
# or with a 429 replay adapter that includes configured retries and initial backoff
# client = Cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME,
# adapter=Replay429Adapter(retries=10, initialBackoff=0.01))
# or with a connect and read timeout of 5 minutes
# client = Cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME,
# timeout=300)
# Perform client tasks...
session = client.session()
print('Username: {0}'.format(session['userCtx']['name']))
print('Databases: {0}'.format(client.all_dbs()))
# Disconnect from the server
client.disconnect()
Authentication¶
When constructing a Cloudant
client, you can authenticate using the
cookie authentication functionality.
The server will always attempt to automatically renew the cookie
shortly before its expiry. However, if the client does not send a
request to the server during this renewal window and
auto_renew=False
then the cookie is not renewed.
Using auto_renew=True
will attempt to renew the cookie at
any point during the lifetime of the session when either of the
following statements hold true:
- The server returns a
credentials_expired
error message. - The server returns a
401 Unauthorized
status code. - The server returns a
403 Forbidden
status code.
# Create client using auto_renew to automatically renew expired cookie auth
client = Cloudant(USERNAME, PASSWORD, url='https://acct.cloudant.com',
connect=True,
auto_renew=True)
Identity and Access Management (IAM)¶
IBM Cloud Identity & Access Management enables you to securely authenticate users and control access to all cloud resources consistently in the IBM Bluemix Cloud Platform.
See IBM Cloud Identity and Access Management for more information.
The production IAM token service at https://iam.cloud.ibm.com/identity/token is used
by default. You can set an IAM_TOKEN_URL
environment variable to override
this.
You can easily connect to your Cloudant account using an IAM API key:
# Authenticate using an IAM API key
client = Cloudant.iam(ACCOUNT_NAME, API_KEY, connect=True)
If you need to authenticate to a server outside of the cloudant.com domain, you can use the url parameter:
# Authenticate using an IAM API key to an account outside of the cloudant.com domain
client = Cloudant.iam(None, API_KEY, url='https://private.endpoint.example', connect=True)
Resource sharing¶
The Cloudant
or CouchDB
client objects make HTTP calls using the requests
library.
requests
uses the urllib3 library which features
connection pooling and thread safety.
Connection pools can be managed by using the requests
library’s
HTTPAdapter
when constructing a Cloudant
or ClouchDB
client instance.
The default number set by the urllib3
library for cached connection pools is 10.
Use the HTTPAdapter
argument pool_connections
to set the number of
urllib3 connection pools to cache, and the pool_maxsize
argument to set the
maximum number of connections to save in the pool.
Although the client
session is documented as thread safe and it’s possible for a
static client
to be accessible by multiple threads, there are still cases that do not
guarantee thread safe execution. It’s recommended to use one client
object per thread.
# Create client with 15 cached pool connections and a max pool size of 100
httpAdapter = HTTPAdapter(pool_connections=15, pool_maxsize=100)
client = Cloudant(USERNAME, PASSWORD, url='https://acct.cloudant.com'
connect=True,
adapter=httpAdapter)
Note: Idle connections within the pool may be terminated by the server, so will not remain open indefinitely meaning that this will not completely remove the overhead of creating new connections.
Using library in app server environment¶
This library can be used in an app server, and the example
below shows how to use client
in a flask
app server.
from flask import Flask
import atexit
app = Flask(__name__)
@app.route('/')
def hello_world():
# Cookie authentication can be renewed automatically using ``auto_renew=True``
# which is typically what you would require when running in an application
# server where the connection may stay open for a long period of time
# Note: Each time you instantiate an instance of the Cloudant client, an
# authentication request will be made to Cloudant to retrieve the session cookie.
# If the performance overhead of this call is a concern for you, consider
# using vanilla python requests with a custom subclass of HTTPAdapter that
# performs the authentication call to Cloudant when it establishes the http
# connection during the creation of the connection pool.
client = Cloudant(USERNAME, PASSWORD, url='https://acct.cloudant.com',
connect=True,
auto_renew=True)
# do something with client
return 'Hello World!'
# When shutting down the app server, use ``client.disconnect()`` to properly
# logout and end the ``client`` session
@atexit.register
def shutdown():
client.disconnect()
Databases¶
Once a connection is established you can then create a database, open an existing database, or delete a database. The following examples assume a client connection has already been established.
Creating a database¶
# Create a database using an initialized client
# The result is a new CloudantDatabase or CouchDatabase based on the client
my_database = client.create_database('my_database')
# You can check that the database exists
if my_database.exists():
print('SUCCESS!!')
Opening a database¶
Opening an existing database is done by supplying the name of an existing
database to the client. Since the Cloudant
and CouchDB
classes are
sub-classes of dict
, this can be accomplished through standard Python
dict
notation.
# Open an existing database
my_database = client['my_database']
Deleting a database¶
# Delete a database using an initialized client
client.delete_database('my_database')
Partitioned Databases¶
Partitioned databases introduce the ability for a user to create logical groups of documents called partitions by providing a partition key with each document.
Warning
Your Cloudant cluster must have the partitions
feature enabled.
A full list of enabled features can be retrieved by calling the
client metadata()
method.
Creating a partitioned database¶
db = client.create_database('mydb', partitioned=True)
Handling documents¶
The document ID contains both the partition key and document key in the form
<partitionkey>:<documentkey>
where:
- Partition Key (string). Must be non-empty. Must not contain colons (as this is the partition key delimiter) or begin with an underscore.
- Document Key (string). Must be non-empty. Must not begin with an underscore.
Be aware that _design
documents and _local
documents must not contain a
partition key as they are global definitions.
Create a document
partition_key = 'Year2'
document_key = 'julia30'
db.create_document({
'_id': ':'.join((partition_key, document_key)),
'name': 'Jules',
'age': 6
})
Get a document
doc = db[':'.join((partition_key, document_key))]
Creating design documents¶
To define partitioned indexes you must set the partitioned=True
optional
when constructing the new DesignDocument
class.
ddoc = DesignDocument(db, document_id='view', partitioned=True)
ddoc.add_view('myview','function(doc) { emit(doc.foo, doc.bar); }')
ddoc.save()
To define a partitioned Cloudant Query index you may set the
partitioned=True
optional, but it is not required as the index will be
partitioned by default in a partitioned database. Conversely, you must
set the partitioned=False
optional if you wish to create a global
(non-partitioned) index in a partitioned database.
index = db.create_query_index(
design_document_id='query',
index_name='foo-index',
fields=['foo'],
partitioned=True
)
index.create()
Querying data¶
A partition key can be specified when querying data so that results can be constrained to a specific database partition.
Warning
To run partitioned queries the database itself must be partitioned.
Query
results = self.db.get_partitioned_query_result(
partition_key, selector={'foo': {'$eq': 'bar'}})
for result in results:
...
See get_partitioned_query_result()
for a
full list of supported parameters.
Search
results = self.db.get_partitioned_search_result(
partition_key, search_ddoc['_id'], 'search1', query='*:*')
for result in results['rows']:
....
See get_partitioned_search_result()
for a full list of supported parameters.
Views (MapReduce)
results = self.db.get_partitioned_view_result(
partition_key, view_ddoc['_id'], 'view1')
for result in results:
....
See get_partitioned_view_result()
for a
full list of supported parameters.
Documents¶
Working with documents using this library is handled through the use of Document objects and Database API methods. A document context manager is also provided to simplify the process. This is discussed later in the Context managers section. The examples that follow demonstrate how to create, read, update, and delete a document. These examples assume that either a CloudantDatabase or a CouchDatabase object already exists.
Creating a document¶
# Create document content data
data = {
'_id': 'julia30', # Setting _id is optional
'name': 'Julia',
'age': 30,
'pets': ['cat', 'dog', 'frog']
}
# Create a document using the Database API
my_document = my_database.create_document(data)
# Check that the document exists in the database
if my_document.exists():
print('SUCCESS!!')
Retrieving a document¶
Accessing a document from a database is done by supplying the document
identifier of an existing document to either a CloudantDatabase
or a
CouchDatabase
object. Since the CloudantDatabase
and CouchDatabase
classes are sub-classes of dict
, this is accomplished through standard
dict
notation.
my_document = my_database['julia30']
# Display the document
print(my_document)
Checking if a document exists¶
You can check if a document exists in a database the same way you would check
if a dict
has a key-value pair by key.
doc_exists = 'julia30' in my_database
if doc_exists:
print('document with _id julia30 exists')
Retrieve all documents¶
You can also iterate over a CloudantDatabase
or a CouchDatabase
object
to retrieve all documents in a database.
# Get all of the documents from my_database
for document in my_database:
print(document)
Update a document¶
# First retrieve the document
my_document = my_database['julia30']
# Update the document content
# This can be done as you would any other dictionary
my_document['name'] = 'Jules'
my_document['age'] = 6
# You must save the document in order to update it on the database
my_document.save()
Delete a document¶
# First retrieve the document
my_document = my_database['julia30']
# Delete the document
my_document.delete()
Dealing with results¶
If you want to get Pythonic with your returned data content, we’ve added a
Result
class that provides a key accessible, sliceable, and iterable
interface to result collections. To use it, construct a Result
object
passing in a reference to a raw data callable such as the all_docs
method
from a database object or a view
object itself, which happens to be defined
as callable and then access the data as you would using standard Python key
access, slicing, and iteration techniques. The following set of examples
illustrate Result
key access, slicing and iteration over a result collection
in action. It assumes that either a CloudantDatabase
or a CouchDatabase
object already exists.
from cloudant.result import Result, ResultByKey
# Retrieve Result wrapped document content.
# Note: The include_docs parameter is optional and is used to illustrate that view query
# parameters can be used to customize the result collection.
result_collection = Result(my_database.all_docs, include_docs=True)
# Get the result at a given location in the result collection
# Note: Valid result collection indexing starts at 0
result = result_collection[0] # result is the 1st in the collection
result = result_collection[9] # result is the 10th in the collection
# Get the result for matching a key
result = result_collection['julia30'] # result is all that match key 'julia30'
# If your key is an integer then use the ResultByKey class to differentiate your integer
# key from an indexed location within the result collection which is also an integer.
result = result_collection[ResultByKey(9)] # result is all that match key 9
# Slice by key values
result = result_collection['julia30': 'ruby99'] # result is between and including keys
result = result_collection['julia30': ] # result is after and including key
result = result_collection[: 'ruby99'] # result is up to and including key
# Slice by index values
result = result_collection[100: 200] # result is between 100 to 200, including 200th
result = result_collection[: 200] # result is up to and including the 200th
result = result_collection[100: ] # result is after the 100th
# Iterate over the result collection
for result in result_collection:
print(result)
This example retrieves the query result from the specified database based on the query parameters provided, updates the
document, and saves the document in the remote database.
By default, the result is returned as a QueryResult
which uses the skip and limit query parameters internally to
handle slicing and iteration through the query result collection. For more detail on slicing and iteration, refer
to the QueryResult
documentation.
# Retrieve documents where the name field is 'foo'
selector = {'name': {'$eq': 'foo'}}
docs = my_database.get_query_result(selector)
for doc in docs:
# Create Document object from dict
updated_doc = Document(my_database, doc['_id'])
updated_doc.update(doc)
# Update document field
updated_doc['name'] = 'new_name'
# Save document
updated_doc.save()
Context managers¶
Now that we’ve gone through the basics, let’s take a look at how to simplify the process of connection, database acquisition, and document management through the use of Python with blocks and this library’s context managers.
Handling your business using with blocks saves you from having to connect and disconnect your client as well as saves you from having to perform a lot of fetch and save operations as the context managers handle these operations for you.
This example uses the cloudant
context helper to illustrate the
process but identical functionality exists for CouchDB through the couchdb
and couchdb_admin_party
context helpers.
from cloudant import cloudant
# ...or use CouchDB variant
# from cloudant import couchdb
# Perform a connect upon entry and a disconnect upon exit of the block
with cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME) as client:
# ...or use CouchDB variant
# with couchdb(USERNAME, PASSWORD, url=COUCHDB_URL) as client:
# Perform client tasks...
session = client.session()
print('Username: {0}'.format(session['userCtx']['name']))
print('Databases: {0}'.format(client.all_dbs()))
# Create a database
my_database = client.create_database('my_database')
if my_database.exists():
print('SUCCESS!!')
# You can open an existing database
del my_database
my_database = client['my_database']
The following example uses the Document
context manager. Here we make
multiple updates to a single document. Note that we don’t save to the server
after each update. We only save once to the server upon exiting the Document
context manager.
Warning
Uncaught exceptions inside the with
block will prevent your
document changes being saved to the remote server. However, changes
will still be applied to your local document object.
from cloudant import cloudant
from cloudant.document import Document
with cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME) as client:
my_database = client.create_database('my_database')
# Upon entry into the document context, fetches the document from the
# remote database, if it exists. Upon exit from the context, saves the
# document to the remote database with changes made within the context
# or creates a new document.
with Document(database, 'julia006') as document:
# If document exists, it's fetched from the remote database
# Changes are made locally
document['name'] = 'Julia'
document['age'] = 6
# The document is saved to the remote database
# Display a Document
print(my_database['julia30'])
# Delete the database
client.delete_database('my_database')
print('Databases: {0}'.format(client.all_dbs()))
Always use the _deleted
document property to delete a document from within
a Document
context manager. For example:
with Document(my_database, 'julia30') as doc:
doc['_deleted'] = True
You can also delete non underscore prefixed document keys to reduce the size of the request.
Warning
Don’t use the doc.delete()
method inside your Document
context manager. This method immediately deletes the document on
the server and clears the local document dictionary. A new, empty
document is still saved to the server upon exiting the context
manager.
Endpoint access¶
If for some reason you need to call a Cloudant/CouchDB endpoint directly rather using the API you can still benefit from the Cloudant/CouchDB client’s authentication and session usage by directly accessing its underlying Requests session object.
Access the session object using the r_session
attribute on your client
object. From there, use the session to make requests as the user the client is
set up with. The following example shows a GET
to the _all_docs
endpoint, but obviously you can use this for any HTTP request to the
Cloudant/CouchDB server. This example assumes that either a Cloudant
or a
CouchDB
client object already exists.
# Define the end point and parameters
end_point = '{0}/{1}'.format(client.server_url, 'my_database/_all_docs')
params = {'include_docs': 'true'}
# Issue the request
response = client.r_session.get(end_point, params=params)
# Display the response content
print(response.json())
TLS 1.2 Support¶
The TLS protocol is used to encrypt communications across a network to ensure that transmitted data remains private. There are three released versions of TLS: 1.0, 1.1, and 1.2. All HTTPS connections use TLS.
If your server enforces the use of TLS 1.2 then the python-cloudant client will continue to work as expected (assuming you’re running a version of Python/OpenSSL that supports TLS 1.2).