I managed to piece together how to attach a new PDF file to an Evernote note using their Python API, so I thought it might be useful to have a post that has all of this information together in one place. I've put together a complete example that will:
- Authenticate to Evernote using a developer token (Oauth2 is a topic for another day)
- Check if a notebook exists; if not, it will create it for you.
- Create a new note in the notebook
- Attach a PDF file to that note (including calculating the necessary MD5 hash)
- Upload the new note
The complete file is shown at the end of this post, and I'll go through each function separately in the next sections.
1 Imports
Here's the complete set of imports that took me a while to track down, even from Evernote's own examples:
import os | |
import hashlib | |
import sys | |
from evernote.api.client import EvernoteClient | |
import evernote.edam.type.ttypes as Types | |
import evernote.edam.userstore.constants as UserStoreConstants | |
from evernote.edam.error.ttypes import EDAMUserException | |
from evernote.edam.error.ttypes import EDAMSystemException | |
from evernote.edam.error.ttypes import EDAMNotFoundException | |
from evernote.edam.error.ttypes import EDAMErrorCode | |
2 Authentication
And here's how to do the authentication using a developer token (Go to the following places to get a token: Sandbox evernote server or Production Evernote server
def _connect_to_evernote(self, dev_token): | |
user = None | |
try: | |
self.client = EvernoteClient(token=dev_token) | |
self.user_store = self.client.get_user_store() | |
user = self.user_store.getUser() | |
except EDAMUserException as e: | |
err = e.errorCode | |
print("Error attempting to authenticate to Evernote: %s - %s" % (EDAMErrorCode._VALUES_TO_NAMES[err], e.parameter)) | |
return False | |
except EDAMSystemException as e: | |
err = e.errorCode | |
print("Error attempting to authenticate to Evernote: %s - %s" % (EDAMErrorCode._VALUES_TO_NAMES[err], e.message)) | |
sys.exit(-1) | |
if user: | |
print("Authenticated to evernote as user %s" % user.username) | |
return True | |
else: | |
return False |
The important thing is to keep the EvernoteClient object around in self.client, as this will proved the authenticated access to the note stores.
3 Handle notebooks
The next step is to check whether the required notebook is available, or if we need to make it. See the _check_and_make_notebook function.
def _get_notebooks(self): | |
note_store = self.client.get_note_store() | |
notebooks = note_store.listNotebooks() | |
return {n.name:n for n in notebooks} | |
def _create_notebook(self, notebook): | |
note_store = self.client.get_note_store() | |
return note_store.createNotebook(notebook) | |
def _update_notebook(self, notebook): | |
note_store = self.client.get_note_store() | |
note_store.updateNotebook(notebook) | |
return | |
def _check_and_make_notebook(self, notebook_name, stack=None): | |
notebooks = self._get_notebooks() | |
if notebook_name in notebooks: | |
# Existing notebook, so just update the stack if needed | |
notebook = notebooks[notebook_name] | |
if stack: | |
notebook.stack = stack | |
self._update_notebook(notebook) | |
return notebook | |
else: | |
# Need to create a new notebook | |
notebook = Types.Notebook() | |
notebook.name = notebook_name | |
if stack: | |
notebook.stack = stack | |
notebook = self._create_notebook(notebook) | |
return notebook |
We use the get_note_store API call to get all the notebooks, and return a dict with the notebook name mapping to the notebook, in function _get_notebooks. Then, if the desired notebook is present, we update the stack (in Evernote, a notebook can be in a collection called a "stack" of notebooks) and return the notebook pointer. If not, we create a new notebook using the Types.Notebook() call, and store it using the createNotebook API call in the note_store.
4 Create the new note with attachment
Next is the real meat of this example, where we create the note with the attachment:
def _create_evernote_note(self, notebook, filename): | |
# Create the new note | |
note = Types.Note() | |
note.title = os.path.basename(filename) | |
note.notebookGuid = notebook.guid | |
note.content = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">' | |
note.content += '<en-note>My first PDF upload<br/>' | |
# Calculate the md5 hash of the pdf | |
md5 = hashlib.md5() | |
with open(filename,'rb') as f: | |
pdf_bytes = f.read() | |
md5.update(pdf_bytes) | |
md5hash = md5.hexdigest() | |
# Create the Data type for evernote that goes into a resource | |
pdf_data = Types.Data() | |
pdf_data.bodyHash = md5hash | |
pdf_data.size = len(pdf_bytes) | |
pdf_data.body = pdf_bytes | |
# Add a link in the evernote boy for this content | |
link = '<en-media type="application/pdf" hash="%s"/>' % md5hash | |
note.content += link | |
note.content += '</en-note>' | |
# Create a resource for the note that contains the pdf | |
pdf_resource = Types.Resource() | |
pdf_resource.data = pdf_data | |
pdf_resource.mime = "application/pdf" | |
# Create a resource list to hold the pdf resource | |
resource_list = [] | |
resource_list.append(pdf_resource) | |
# Set the note's resource list | |
note.resources = resource_list | |
return note |
We create a new note using Types.Note(), and set its containing notebook using the GUID of the notebook. We then start setting the contents of the note using the Evernote markup language. All the text and attachment links must be inside the "en-note" tag. The content is then built up as follows:
- Read in the PDF file to attach
- Calculate the MD5 hash
- Create a new Data container for Evernote and store the hash, size, and data from the file
- Create a link to this file to insert into the content of the note
- Create a Resource type to hold the PDF Data, and put it into a Resource list
- Append the resource list to the note
- Return this newly formed note
5 Uploading the note
The final step is to upload the note:
def upload_to_notebook(self, filename, notebookname): | |
# Check if the evernote notebook exists | |
print ("Checking for notebook named %s" % notebookname) | |
notebook = self._check_and_make_notebook(notebookname, "my_stack") | |
print("Uploading %s to %s" % (filename, notebookname)) | |
note = self._create_evernote_note(notebook, filename) | |
# Store the note in evernote | |
note_store = self.client.get_note_store() | |
note = note_store.createNote(note) |
6 Complete example
import os | |
import hashlib | |
import sys | |
from evernote.api.client import EvernoteClient | |
import evernote.edam.type.ttypes as Types | |
import evernote.edam.userstore.constants as UserStoreConstants | |
from evernote.edam.error.ttypes import EDAMUserException | |
from evernote.edam.error.ttypes import EDAMSystemException | |
from evernote.edam.error.ttypes import EDAMNotFoundException | |
from evernote.edam.error.ttypes import EDAMErrorCode | |
class EvernoteUpload(object): | |
def __init__(self, dev_token): | |
self._connect_to_evernote(dev_token) | |
def _connect_to_evernote(self, dev_token): | |
user = None | |
try: | |
self.client = EvernoteClient(token=dev_token) | |
self.user_store = self.client.get_user_store() | |
user = self.user_store.getUser() | |
except EDAMUserException as e: | |
err = e.errorCode | |
print("Error attempting to authenticate to Evernote: %s - %s" % (EDAMErrorCode._VALUES_TO_NAMES[err], e.parameter)) | |
return False | |
except EDAMSystemException as e: | |
err = e.errorCode | |
print("Error attempting to authenticate to Evernote: %s - %s" % (EDAMErrorCode._VALUES_TO_NAMES[err], e.message)) | |
sys.exit(-1) | |
if user: | |
print("Authenticated to evernote as user %s" % user.username) | |
return True | |
else: | |
return False | |
def _get_notebooks(self): | |
note_store = self.client.get_note_store() | |
notebooks = note_store.listNotebooks() | |
return {n.name:n for n in notebooks} | |
def _create_notebook(self, notebook): | |
note_store = self.client.get_note_store() | |
return note_store.createNotebook(notebook) | |
def _update_notebook(self, notebook): | |
note_store = self.client.get_note_store() | |
note_store.updateNotebook(notebook) | |
return | |
def _check_and_make_notebook(self, notebook_name, stack=None): | |
notebooks = self._get_notebooks() | |
if notebook_name in notebooks: | |
# Existing notebook, so just update the stack if needed | |
notebook = notebooks[notebook_name] | |
if stack: | |
notebook.stack = stack | |
self._update_notebook(notebook) | |
return notebook | |
else: | |
# Need to create a new notebook | |
notebook = Types.Notebook() | |
notebook.name = notebook_name | |
if stack: | |
notebook.stack = stack | |
notebook = self._create_notebook(notebook) | |
return notebook | |
def _create_evernote_note(self, notebook, filename): | |
# Create the new note | |
note = Types.Note() | |
note.title = os.path.basename(filename) | |
note.notebookGuid = notebook.guid | |
note.content = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">' | |
note.content += '<en-note>My first PDF upload<br/>' | |
# Calculate the md5 hash of the pdf | |
md5 = hashlib.md5() | |
with open(filename,'rb') as f: | |
pdf_bytes = f.read() | |
md5.update(pdf_bytes) | |
md5hash = md5.hexdigest() | |
# Create the Data type for evernote that goes into a resource | |
pdf_data = Types.Data() | |
pdf_data.bodyHash = md5hash | |
pdf_data.size = len(pdf_bytes) | |
pdf_data.body = pdf_bytes | |
# Add a link in the evernote boy for this content | |
link = '<en-media type="application/pdf" hash="%s"/>' % md5hash | |
note.content += link | |
note.content += '</en-note>' | |
# Create a resource for the note that contains the pdf | |
pdf_resource = Types.Resource() | |
pdf_resource.data = pdf_data | |
pdf_resource.mime = "application/pdf" | |
# Create a resource list to hold the pdf resource | |
resource_list = [] | |
resource_list.append(pdf_resource) | |
# Set the note's resource list | |
note.resources = resource_list | |
return note | |
def upload_to_notebook(self, filename, notebookname): | |
# Check if the evernote notebook exists | |
print ("Checking for notebook named %s" % notebookname) | |
notebook = self._check_and_make_notebook(notebookname, "my_stack") | |
print("Uploading %s to %s" % (filename, notebookname)) | |
note = self._create_evernote_note(notebook, filename) | |
# Store the note in evernote | |
note_store = self.client.get_note_store() | |
note = note_store.createNote(note) | |
if __name__ == '__main__': | |
dev_token = "YOUR_DEV_TOKEN" | |
p = EvernoteUpload(dev_token) | |
p.upload_to_notebook('test_sherlock.pdf', 'boom') |