feature/4493 Improve Evernote Document Loader (#4577)

# Improve Evernote Document Loader

When exporting from Evernote you may export more than one note.
Currently the Evernote loader concatenates the content of all notes in
the export into a single document and only attaches the name of the
export file as metadata on the document.

This change ensures that each note is loaded as an independent document
and all available metadata on the note e.g. author, title, created,
updated are added as metadata on each document.

It also uses an existing optional dependency of `html2text` instead of
`pypandoc` to remove the need to download the pandoc application via
`download_pandoc()` to be able to use the `pypandoc` python bindings.

Fixes #4493 

Co-authored-by: Mike McGarry <mike.mcgarry@finbourne.com>
Co-authored-by: Dev 2049 <dev.dev2049@gmail.com>
searx_updates
Mike McGarry 1 year ago committed by GitHub
parent 729e935ea4
commit ddd595fe81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,39 +9,43 @@
"\n",
">[EverNote](https://evernote.com/) is intended for archiving and creating notes in which photos, audio and saved web content can be embedded. Notes are stored in virtual \"notebooks\" and can be tagged, annotated, edited, searched, and exported.\n",
"\n",
"This notebook shows how to load `EverNote` file from disk."
"This notebook shows how to load an `Evernote` [export](https://help.evernote.com/hc/en-us/articles/209005557-Export-notes-and-notebooks-as-ENEX-or-HTML) file (.enex) from disk.\n",
"\n",
"A document will be created for each note in the export."
]
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 1,
"id": "1a53ece0",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"#!pip install pypandoc\n",
"import pypandoc\n",
"\n",
"pypandoc.download_pandoc()"
"# lxml and html2text are required to parse EverNote notes\n",
"# !pip install lxml\n",
"# !pip install html2text"
]
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 2,
"id": "88df766f",
"metadata": {
"pycharm": {
"name": "#%%\n"
},
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='testing this\\n\\nwhat happens?\\n\\nto the world?\\n', metadata={'source': 'example_data/testing.enex'})]"
"[Document(page_content='testing this\\n\\nwhat happens?\\n\\nto the world?**Jan - March 2022**', metadata={'source': 'example_data/testing.enex'})]"
]
},
"execution_count": 4,
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
@ -49,9 +53,34 @@
"source": [
"from langchain.document_loaders import EverNoteLoader\n",
"\n",
"# By default all notes are combined into a single Document\n",
"loader = EverNoteLoader(\"example_data/testing.enex\")\n",
"loader.load()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "97a58fde",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='testing this\\n\\nwhat happens?\\n\\nto the world?', metadata={'title': 'testing', 'created': time.struct_time(tm_year=2023, tm_mon=2, tm_mday=9, tm_hour=3, tm_min=47, tm_sec=46, tm_wday=3, tm_yday=40, tm_isdst=-1), 'updated': time.struct_time(tm_year=2023, tm_mon=2, tm_mday=9, tm_hour=3, tm_min=53, tm_sec=28, tm_wday=3, tm_yday=40, tm_isdst=-1), 'note-attributes.author': 'Harrison Chase', 'source': 'example_data/testing.enex'}),\n",
" Document(page_content='**Jan - March 2022**', metadata={'title': 'Summer Training Program', 'created': time.struct_time(tm_year=2022, tm_mon=12, tm_mday=27, tm_hour=1, tm_min=59, tm_sec=48, tm_wday=1, tm_yday=361, tm_isdst=-1), 'note-attributes.author': 'Mike McGarry', 'note-attributes.source': 'mobile.iphone', 'source': 'example_data/testing.enex'})]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# It's likely more useful to return a Document for each note\n",
"loader = EverNoteLoader(\"example_data/testing.enex\", load_single_document=False)\n",
"loader.load()"
]
}
],
"metadata": {
@ -70,7 +99,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
"version": "3.9.7"
}
},
"nbformat": 4,

@ -13,4 +13,16 @@
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>testing this</div><div>what happens?</div><div>to the world?</div></en-note> ]]>
</content>
</note>
<note>
<title>Summer Training Program</title>
<created>20221227T015948Z</created>
<note-attributes>
<author>Mike McGarry</author>
<source>mobile.iphone</source>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><b>Jan - March 2022</b></div></en-note> ]]>
</content>
</note>
</en-export>

@ -3,80 +3,146 @@
https://gist.github.com/foxmask/7b29c43a161e001ff04afdb2f181e31c
"""
import hashlib
import logging
from base64 import b64decode
from time import strptime
from typing import Any, Dict, List
from typing import Any, Dict, Iterator, List, Optional
from langchain.docstore.document import Document
from langchain.document_loaders.base import BaseLoader
def _parse_content(content: str) -> str:
from pypandoc import convert_text
text = convert_text(content, "org", format="html")
return text
def _parse_resource(resource: list) -> dict:
rsc_dict: Dict[str, Any] = {}
for elem in resource:
if elem.tag == "data":
# Some times elem.text is None
rsc_dict[elem.tag] = b64decode(elem.text) if elem.text else b""
rsc_dict["hash"] = hashlib.md5(rsc_dict[elem.tag]).hexdigest()
else:
rsc_dict[elem.tag] = elem.text
return rsc_dict
def _parse_note(note: List) -> dict:
note_dict: Dict[str, Any] = {}
resources = []
for elem in note:
if elem.tag == "content":
note_dict[elem.tag] = _parse_content(elem.text)
# A copy of original content
note_dict["content-raw"] = elem.text
elif elem.tag == "resource":
resources.append(_parse_resource(elem))
elif elem.tag == "created" or elem.tag == "updated":
note_dict[elem.tag] = strptime(elem.text, "%Y%m%dT%H%M%SZ")
else:
note_dict[elem.tag] = elem.text
note_dict["resource"] = resources
return note_dict
def _parse_note_xml(xml_file: str) -> str:
"""Parse Evernote xml."""
# Without huge_tree set to True, parser may complain about huge text node
# Try to recover, because there may be "&nbsp;", which will cause
# "XMLSyntaxError: Entity 'nbsp' not defined"
from lxml import etree
context = etree.iterparse(
xml_file, encoding="utf-8", strip_cdata=False, huge_tree=True, recover=True
)
result_string = ""
for action, elem in context:
if elem.tag == "note":
result_string += _parse_note(elem)["content"]
return result_string
class EverNoteLoader(BaseLoader):
"""Loader to load in EverNote files.."""
def __init__(self, file_path: str):
"""EverNote Loader.
Loads an EverNote notebook export file e.g. my_notebook.enex into Documents.
Instructions on producing this file can be found at
https://help.evernote.com/hc/en-us/articles/209005557-Export-notes-and-notebooks-as-ENEX-or-HTML
Currently only the plain text in the note is extracted and stored as the contents
of the Document, any non content metadata (e.g. 'author', 'created', 'updated' etc.
but not 'content-raw' or 'resource') tags on the note will be extracted and stored
as metadata on the Document.
Args:
file_path (str): The path to the notebook export with a .enex extension
load_single_document (bool): Whether or not to concatenate the content of all
notes into a single long Document.
If this is set to True (default) then the only metadata on the document will be
the 'source' which contains the file name of the export.
""" # noqa: E501
def __init__(self, file_path: str, load_single_document: bool = True):
"""Initialize with file path."""
self.file_path = file_path
self.load_single_document = load_single_document
def load(self) -> List[Document]:
"""Load document from EverNote file."""
text = _parse_note_xml(self.file_path)
metadata = {"source": self.file_path}
return [Document(page_content=text, metadata=metadata)]
"""Load documents from EverNote export file."""
documents = [
Document(
page_content=note["content"],
metadata={
**{
key: value
for key, value in note.items()
if key not in ["content", "content-raw", "resource"]
},
**{"source": self.file_path},
},
)
for note in self._parse_note_xml(self.file_path)
if note.get("content") is not None
]
if not self.load_single_document:
return documents
return [
Document(
page_content="".join([document.page_content for document in documents]),
metadata={"source": self.file_path},
)
]
@staticmethod
def _parse_content(content: str) -> str:
try:
import html2text
return html2text.html2text(content).strip()
except ImportError as e:
logging.error(
"Could not import `html2text`. Although it is not a required package "
"to use Langchain, using the EverNote loader requires `html2text`. "
"Please install `html2text` via `pip install html2text` and try again."
)
raise e
@staticmethod
def _parse_resource(resource: list) -> dict:
rsc_dict: Dict[str, Any] = {}
for elem in resource:
if elem.tag == "data":
# Sometimes elem.text is None
rsc_dict[elem.tag] = b64decode(elem.text) if elem.text else b""
rsc_dict["hash"] = hashlib.md5(rsc_dict[elem.tag]).hexdigest()
else:
rsc_dict[elem.tag] = elem.text
return rsc_dict
@staticmethod
def _parse_note(note: List, prefix: Optional[str] = None) -> dict:
note_dict: Dict[str, Any] = {}
resources = []
def add_prefix(element_tag: str) -> str:
if prefix is None:
return element_tag
return f"{prefix}.{element_tag}"
for elem in note:
if elem.tag == "content":
note_dict[elem.tag] = EverNoteLoader._parse_content(elem.text)
# A copy of original content
note_dict["content-raw"] = elem.text
elif elem.tag == "resource":
resources.append(EverNoteLoader._parse_resource(elem))
elif elem.tag == "created" or elem.tag == "updated":
note_dict[elem.tag] = strptime(elem.text, "%Y%m%dT%H%M%SZ")
elif elem.tag == "note-attributes":
additional_attributes = EverNoteLoader._parse_note(
elem, elem.tag
) # Recursively enter the note-attributes tag
note_dict.update(additional_attributes)
else:
note_dict[elem.tag] = elem.text
if len(resources) > 0:
note_dict["resource"] = resources
return {add_prefix(key): value for key, value in note_dict.items()}
@staticmethod
def _parse_note_xml(xml_file: str) -> Iterator[Dict[str, Any]]:
"""Parse Evernote xml."""
# Without huge_tree set to True, parser may complain about huge text node
# Try to recover, because there may be "&nbsp;", which will cause
# "XMLSyntaxError: Entity 'nbsp' not defined"
try:
from lxml import etree
except ImportError as e:
logging.error(
"Could not import `lxml`. Although it is not a required package to use "
"Langchain, using the EverNote loader requires `lxml`. Please install "
"`lxml` via `pip install lxml` and try again."
)
raise e
context = etree.iterparse(
xml_file, encoding="utf-8", strip_cdata=False, huge_tree=True, recover=True
)
for action, elem in context:
if elem.tag == "note":
yield EverNoteLoader._parse_note(elem)

6
poetry.lock generated

@ -10330,11 +10330,11 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\
cffi = ["cffi (>=1.11)"]
[extras]
all = ["O365", "aleph-alpha-client", "anthropic", "arxiv", "atlassian-python-api", "azure-cosmos", "azure-identity", "beautifulsoup4", "clickhouse-connect", "cohere", "deeplake", "docarray", "duckduckgo-search", "elasticsearch", "faiss-cpu", "google-api-python-client", "google-search-results", "gptcache", "gql", "hnswlib", "html2text", "huggingface_hub", "jina", "jinja2", "jq", "lancedb", "lark", "manifest-ml", "networkx", "nlpcloud", "nltk", "nomic", "openai", "opensearch-py", "pdfminer-six", "pexpect", "pgvector", "pinecone-client", "pinecone-text", "protobuf", "psycopg2-binary", "pyowm", "pypdf", "pytesseract", "pyvespa", "qdrant-client", "redis", "sentence-transformers", "spacy", "steamship", "tensorflow-text", "tiktoken", "torch", "transformers", "weaviate-client", "wikipedia", "wolframalpha"]
all = ["O365", "aleph-alpha-client", "anthropic", "arxiv", "atlassian-python-api", "azure-cosmos", "azure-identity", "beautifulsoup4", "clickhouse-connect", "cohere", "deeplake", "docarray", "duckduckgo-search", "elasticsearch", "faiss-cpu", "google-api-python-client", "google-search-results", "gptcache", "gql", "hnswlib", "html2text", "huggingface_hub", "jina", "jinja2", "jq", "lancedb", "lark", "lxml", "manifest-ml", "networkx", "nlpcloud", "nltk", "nomic", "openai", "opensearch-py", "pdfminer-six", "pexpect", "pgvector", "pinecone-client", "pinecone-text", "protobuf", "psycopg2-binary", "pyowm", "pypdf", "pytesseract", "pyvespa", "qdrant-client", "redis", "sentence-transformers", "spacy", "steamship", "tensorflow-text", "tiktoken", "torch", "transformers", "weaviate-client", "wikipedia", "wolframalpha"]
azure = ["azure-core", "azure-cosmos", "azure-identity", "openai"]
cohere = ["cohere"]
embeddings = ["sentence-transformers"]
extended-testing = ["atlassian-python-api", "beautifulsoup4", "beautifulsoup4", "chardet", "jq", "lxml", "pandas", "pdfminer-six", "pymupdf", "pypdf", "pypdfium2", "telethon", "tqdm", "zep-python"]
extended-testing = ["atlassian-python-api", "beautifulsoup4", "beautifulsoup4", "chardet", "html2text", "jq", "lxml", "pandas", "pdfminer-six", "pymupdf", "pypdf", "pypdfium2", "telethon", "tqdm", "zep-python"]
hnswlib = ["docarray", "hnswlib", "protobuf"]
in-memory-store = ["docarray"]
llms = ["anthropic", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "torch", "transformers"]
@ -10345,4 +10345,4 @@ text-helpers = ["chardet"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8.1,<4.0"
content-hash = "2b19b9deca7f83ca14af1f7bc7808bbe7873a91ce4c95381eaad8ea84fe04c0b"
content-hash = "cd116e8f127ccca1c6f700ef17863bae2f101384677448276fe0962dc3fc4cf6"

@ -183,7 +183,8 @@ in_memory_store = ["docarray"]
hnswlib = ["docarray", "protobuf", "hnswlib"]
embeddings = ["sentence-transformers"]
azure = ["azure-identity", "azure-cosmos", "openai", "azure-core"]
all = ["anthropic", "cohere", "openai", "nlpcloud", "huggingface_hub", "jina", "manifest-ml", "elasticsearch", "opensearch-py", "google-search-results", "faiss-cpu", "sentence-transformers", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "pinecone-text", "weaviate-client", "redis", "google-api-python-client", "wolframalpha", "qdrant-client", "tensorflow-text", "pypdf", "networkx", "nomic", "aleph-alpha-client", "deeplake", "pgvector", "psycopg2-binary", "pyowm", "pytesseract", "html2text", "atlassian-python-api", "gptcache", "duckduckgo-search", "arxiv", "azure-identity", "clickhouse-connect", "azure-cosmos", "lancedb", "lark", "pexpect", "pyvespa", "O365", "jq", "docarray", "protobuf", "hnswlib", "steamship", "pdfminer-six", "gql"]
all = ["anthropic", "cohere", "openai", "nlpcloud", "huggingface_hub", "jina", "manifest-ml", "elasticsearch", "opensearch-py", "google-search-results", "faiss-cpu", "sentence-transformers", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "pinecone-text", "weaviate-client", "redis", "google-api-python-client", "wolframalpha", "qdrant-client", "tensorflow-text", "pypdf", "networkx", "nomic", "aleph-alpha-client", "deeplake", "pgvector", "psycopg2-binary", "pyowm", "pytesseract", "html2text", "atlassian-python-api", "gptcache", "duckduckgo-search", "arxiv", "azure-identity", "clickhouse-connect", "azure-cosmos", "lancedb", "lark", "pexpect", "pyvespa", "O365", "jq", "docarray", "protobuf", "hnswlib", "steamship", "pdfminer-six", "gql", "lxml"]
# An extra used to be able to add extended testing.
# Please use new-line on formatting to make it easier to add new packages without
# merge-conflicts
@ -201,7 +202,8 @@ extended_testing = [
"beautifulsoup4",
"pandas",
"telethon",
"zep-python"
"zep-python",
"html2text"
]
[tool.ruff]

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230613T110102Z" application="Evernote" version="10.56.9">
</en-export>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230611T011239Z" application="Evernote" version="10.56.9">
<note>
<title>Test</title>
<created>20230511T011217Z</created>
<updated>20240714T011228Z</updated>
<note-attributes>
<author>Michael McGarry</author>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>abc</div></en-note> ]]>
</content>
</note>
<note>
<title>Summer Training Program</title>
<created>20221227T015948Z</created>
<note-attributes>
<author>Mike McGarry</author>
<source>mobile.iphone</source>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><b>Jan - March 2022</b></div></en-note> ]]>
</content>
</note>
</en-export>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230611T011239Z" application="Evernote" version="10.56.9">
<note>
<title>Summer Training Program</title>
<created>20221227T015948Z</created>
<source>mobile.iphone</source>
<note-attributes>
<author>Mike McGarry</author>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><b>Jan - March 2022</b></div></en-note> ]]>
</content>
</note>
</en-export>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230611T011239Z" application="Evernote" version="10.56.9">
<note>
<title>Summer Training Program</title>
<created>20221227T015948Z</created>
<source>mobile.iphone</source>
<note-attributes>
<author>Mike McGarry</author>
</note-attributes>
<content>
</content>
</note>
</en-export>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230611T011239Z" application="Evernote" version="10.56.9">
<note>
<title>Summer Training Program</title>
<created>20221227T015948Z</created>
<source>mobile.iphone</source>
<note-attributes>
<author>Mike McGarry</author>
</note-attributes>
</note>
</en-export>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230611T011239Z" application="Evernote" version="10.56.9">
<note>
<content>
I only have content, no metadata
</content>
</note>
</en-export>

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
<en-export export-date="20230619T022508Z" application="Evernote" version="10.56.9">
<note>
<title>Tea Mug Design</title>
<created>20180719T085818Z</created>
<updated>20230513T110142Z</updated>
<note-attributes>
<author>Michael McGarry</author>
<latitude>43.777825</latitude>
<longitude>11.249122222222221</longitude>
<source>mobile.iphone</source>
</note-attributes>
<content>
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><br/></div><en-media hash="8ab763800efcb0865f5d55e8a0e43eb2" type="image/jpeg" style="--en-naturalWidth:200; --en-naturalHeight:135;" /><div><br/></div><div>When you pick this mug up with your thumb on top and middle finger through the loop, your ring finger slides into the mug under the loop where it is too hot to touch and burns you.</div><div><br/></div><div>If you try and pick it up with your thumb and index finger you cant hold the mug. </div><div><br/></div></en-note> ]]>
</content>
<resource>
<data encoding="base64">
/9j/4AAQSkZJRgABAQEASABIAAD/4SGyRXhpZgAASUkqAAgAAAALAA8BAgAGAAAAkgAAABABAgAKAAAAmAAAABIBAwABAAAAAQAAABoBBQABAAAAogAAABsBBQABAAAAqgAAACgBAwABAAAAAgAAADEBAgANAAAAsgAAADIBAgAUAAAAwAAAABMCAwABAAAAAQAAAGmHBAABAAAA1AAAACWIBAABAAAAdgYAAKwHAABBcHBsZQBpUGhvbmUgNnMA
SAAAAAEAAABIAAAAAQAAAEdJTVAgMi4xMC4yMgAAMjAyMzowNToxOSAxMjoyMzo1NAAgAJqCBQABAAAAWgIAAJ2CBQABAAAAYgIAACKIAwABAAAAAgAAACeIAwABAAAAMgAAAACQBwAEAAAAMDIyMQOQAgAUAAAAagIAAASQAgAUAAAAfgIAAAGRBwAEAAAAAQIDAAGSCgABAAAAkgIAAAKSBQABAAAAmgIAAAOSCgABAAAAogIAAASSCgABAAAA
qgIAAAeSAwABAAAAAwAAAAmSAwABAAAAEAAAAAqSBQABAAAAsgIAABSSAwAEAAAAugIAAHySBwBqAwAAwgIAAJGSAgAEAAAANjU1AJKSAgAEAAAANjU1AACgBwAEAAAAMDEwMAGgAwABAAAAAQAAAAKgBAABAAAAwA8AAAOgBAABAAAA0AsAABeiAwABAAAAAgAAAAGjBwABAAAAAQAAAAKkAwABAAAAAAAAAAOkAwABAAAAAAAAAAWkAwABAAAA
HQAAAAakAwABAAAAAAAAADKkBQAEAAAALAYAADOkAgAGAAAATAYAADSkAgAjAAAAUgYAAAAAAAABAAAAIQAAAAsAAAAFAAAAMjAxODowNzoxOSAxMDo1NzoyOQAyMDE4OjA3OjE5IDEwOjU3OjI5APkIAADGAQAALx8AALUNAAB/BwAACQIAAAAAAAABAAAAUwAAABQAAAB2DAEH8QL0AkFwcGxlIGlPUwAAAU1NAA4AAQAJAAAAAQAAAAkAAgAH
AAACLgAAALwAAwAHAAAAaAAAAuoABAAJAAAAAQAAAAEABQAJAAAAAQAAAKcABgAJAAAAAQAAAKoABwAJAAAAAQAAAAEACAAKAAAAAwAAA1IACQAJAAAAAQAAERMADgAJAAAAAQAAAAAAFAAJAAAAAQAAAAQAFwAJAAAAAQAAAAAAGQAJAAAAAQAAAAAAHwAJAAAAAQAAAAAAAAAAYnBsaXN0MDBPEQIAcQFiAcIARAGZAL0A3ADnANEAygAdAaEB
6AGMAf4AxgGJAVgBHQEYAbYAhAFHAckArQC8AP0AvwBgAT8CWwFlAYABVgEvAd8BrQEMAeMA5gDyAPoA+ACeACoBCAFhAfsAbQEzARIBRAJxAbAAhwCKAJcAuQAiAZABrgCkAJsAwQBgAR4BkgCyANUAxQDLALsAvgC9AMcAGAETAfIAZABHAFABNAGUAJUAhACCAG0AaQBmAHEAsADaAHUArABwAFcANQEhAZkAjwCEAH0AbQBqAGcAcgC0AO8A
hQDMAGwAXgCZAJUAmgCLAIIAeABsAGkAZwBzALYA5QDcAJ4AggBnAIwAlQCaAIsAggB1AGwAaQBlAHUAugDHAKkAqgCsAKoAigCPAI4AhQCBAHYAbgBoAGYAdQC4AKEApgDDAMQAwgB9AH8AfABsAHAAcgBuAGYAZABsAIEAhwCXAMAA3ADYAHMAdABtAGIAPQBKAFQAUgBPAFUAXAB9AI4AsQDaAO0AcABwAGEAXwA/ACYAJgApAC8ATQBfAHYA
iwCmANUA7wBuAGkAXABTAF8AOAAzADoAUQBiAGkAdACGAKcAzQDhAG0AZABdAFUAbABNAFEAWgBiAG4AbwB0AIMAmgC1AMgAVwBZAFwAXgBtAHkAawBwAHUAgAB0AGIAUQBJAEsASwAACAAAAAAAAAIBAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAIMYnBsaXN0MDDUAQIDBAUGBwhVZmxhZ3NVdmFsdWVZdGltZXNjYWxlVWVwb2NoEAETAAEiu4VX
CRsSO5rKABAACBEXHSctLzg9AAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAAAAAAAD////RTAAAM5P///mMAApoe///z2QAAHNtTAAAAFAAAAFMAAAAUAAAACwAAAAUAAAALAAAABQAAAEFwcGxlAGlQaG9uZSA2cyBiYWNrIGNhbWVyYSA0LjE1bW0gZi8yLjIAAA8AAQACAAIAAABOAAAAAgAFAAMAAAAwBwAAAwACAAIAAABFAAAABAAFAAMA
AABIBwAABQABAAEAAAAAAAAABgAFAAEAAABgBwAABwAFAAMAAABoBwAADAACAAIAAABLAAAADQAFAAEAAACABwAAEAACAAIAAABUAAAAEQAFAAEAAACIBwAAFwACAAIAAABUAAAAGAAFAAEAAACQBwAAHQACAAsAAACYBwAAHwACAAcAAACkBwAAAAAAACsAAAABAAAALgAAAAEAAACxDwAAZAAAAAsAAAABAAAADgAAAAEAAAA0FgAAZAAAAES1
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
PWtFEXMf/9n/4Q9maHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3
dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOmlwdGNFeHQ9Imh0dHA6Ly9pcHRjLm9yZy9zdGQvSXB0YzR4bXBFeHQvMjAwOC0wMi0yOS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDov
L25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpwbHVzPSJodHRwOi8vbnMudXNlcGx1cy5vcmcvbGRmL3htcC8xLjAvIiB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6eG1wPSJodHRwOi8v
bnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6NmU2ZjA1YWYtMmIxNi00MzVhLWIyOTUtOWEyYWMzZDE1MzFmIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjgwNmY5ODJhLTk4M2YtNGVmYi04OWI0LTk1OTQ2ZGFjMDUzMiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlk
OjU5ZDY1NTllLTY3MjUtNDQwZC1iNGUyLWJlMjAzMzk3ZDdkYiIgR0lNUDpBUEk9IjIuMCIgR0lNUDpQbGF0Zm9ybT0iTWFjIE9TIiBHSU1QOlRpbWVTdGFtcD0iMTY4NDQ2MzA0MTM1OTkxNCIgR0lNUDpWZXJzaW9uPSIyLjEwLjIyIiBkYzpGb3JtYXQ9ImltYWdlL2pwZWciIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4gPGlwdGNF
eHQ6TG9jYXRpb25DcmVhdGVkPiA8cmRmOkJhZy8+IDwvaXB0Y0V4dDpMb2NhdGlvbkNyZWF0ZWQ+IDxpcHRjRXh0OkxvY2F0aW9uU2hvd24+IDxyZGY6QmFnLz4gPC9pcHRjRXh0OkxvY2F0aW9uU2hvd24+IDxpcHRjRXh0OkFydHdvcmtPck9iamVjdD4gPHJkZjpCYWcvPiA8L2lwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PiA8aXB0Y0V4dDpS
ZWdpc3RyeUlkPiA8cmRmOkJhZy8+IDwvaXB0Y0V4dDpSZWdpc3RyeUlkPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6Y2hhbmdlZD0iLyIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplODdhNDhhZi1mZmYxLTRlOGUtYTllYi1kOTFjM2IwNmFkNGQiIHN0RXZ0OnNvZnR3
YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTWFjIE9TKSIgc3RFdnQ6d2hlbj0iMjAyMy0wNS0xM1QyMTowMDozNysxMDowMCIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0OmNoYW5nZWQ9Ii8iIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6Nzc1NmU4NTktY2M3ZS00OGE3LTlkZWUtOGU5NjcyNTkyM2YzIiBzdEV2dDpzb2Z0
d2FyZUFnZW50PSJHaW1wIDIuMTAgKE1hYyBPUykiIHN0RXZ0OndoZW49IjIwMjMtMDUtMTlUMTI6MjQ6MDErMTA6MDAiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDxwbHVzOkltYWdlU3VwcGxpZXI+IDxyZGY6U2VxLz4gPC9wbHVzOkltYWdlU3VwcGxpZXI+IDxwbHVzOkltYWdlQ3JlYXRvcj4gPHJkZjpTZXEvPiA8L3BsdXM6
SW1hZ2VDcmVhdG9yPiA8cGx1czpDb3B5cmlnaHRPd25lcj4gPHJkZjpTZXEvPiA8L3BsdXM6Q29weXJpZ2h0T3duZXI+IDxwbHVzOkxpY2Vuc29yPiA8cmRmOlNlcS8+IDwvcGx1czpMaWNlbnNvcj4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
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
</data>
<mime>image/jpeg</mime>
<resource-attributes>
<file-name>IMG_4686v3.JPG</file-name>
<source-url>en-cache://tokenKey%3D%22AuthToken%3AUser%3A113979823%22+fff281d8-1e2b-214a-61f5-a152ca30a105+8ab763800efcb0865f5d55e8a0e43eb2+https://www.evernote.com/shard/s612/res/a5e074a6-0ed8-9df5-4843-a375d7c8d257</source-url>
</resource-attributes>
</resource>
</note>
</en-export>

@ -0,0 +1,176 @@
import os
import pathlib
import time
import pytest
from langchain.document_loaders import EverNoteLoader
@pytest.mark.requires("lxml", "html2text")
class TestEverNoteLoader:
@staticmethod
def example_notebook_path(notebook_name: str) -> str:
current_dir = pathlib.Path(__file__).parent
return os.path.join(current_dir, "sample_documents", notebook_name)
def test_loadnotebook_eachnoteisindividualdocument(self) -> None:
loader = EverNoteLoader(
self.example_notebook_path("sample_notebook.enex"), False
)
documents = loader.load()
assert len(documents) == 2
def test_loadnotebook_eachnotehasexpectedcontentwithleadingandtrailingremoved(
self,
) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook.enex"), False
).load()
content_note1 = documents[0].page_content
assert content_note1 == "abc"
content_note2 = documents[1].page_content
assert content_note2 == "**Jan - March 2022**"
def test_loademptynotebook_emptylistreturned(self) -> None:
documents = EverNoteLoader(
self.example_notebook_path("empty_export.enex"), False
).load()
assert len(documents) == 0
def test_loadnotewithemptycontent_emptydocumentcontent(self) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook_emptynote.enex"), False
).load()
note = documents[0]
assert note.page_content == ""
def test_loadnotewithmissingcontenttag_emptylistreturned(
self,
) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook_missingcontenttag.enex"), False
).load()
assert len(documents) == 0
def test_loadnotewithnometadata_documentreturnedwithsourceonly(
self,
) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook_missingmetadata.enex"), False
).load()
note = documents[0]
assert note.page_content == "I only have content, no metadata"
assert len(note.metadata) == 1
assert "source" in note.metadata
assert "sample_notebook_missingmetadata.enex" in note.metadata["source"]
def test_loadnotebookwithimage_notehasplaintextonlywithresourcesremoved(
self,
) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook_with_media.enex"), False
).load()
note = documents[0]
assert (
note.page_content
== """\
When you pick this mug up with your thumb on top and middle finger through the
loop, your ring finger slides into the mug under the loop where it is too hot
to touch and burns you.
If you try and pick it up with your thumb and index finger you cant hold the
mug."""
)
def test_loadnotebook_eachnotehasexpectedmetadata(self) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook.enex"), False
).load()
metadata_note1 = documents[0].metadata
assert "title" in metadata_note1.keys()
assert "created" in metadata_note1.keys()
assert "updated" in metadata_note1.keys()
assert "note-attributes.author" in metadata_note1.keys()
assert (
"content" not in metadata_note1.keys()
) # This should be in the content of the document instead
assert (
"content-raw" not in metadata_note1.keys()
) # This is too large to be stored as metadata
assert (
"resource" not in metadata_note1.keys()
) # This is too large to be stored as metadata
assert metadata_note1["title"] == "Test"
assert metadata_note1["note-attributes.author"] == "Michael McGarry"
assert isinstance(metadata_note1["created"], time.struct_time)
assert isinstance(metadata_note1["updated"], time.struct_time)
assert metadata_note1["created"].tm_year == 2023
assert metadata_note1["created"].tm_mon == 5
assert metadata_note1["created"].tm_mday == 11
assert metadata_note1["updated"].tm_year == 2024
assert metadata_note1["updated"].tm_mon == 7
assert metadata_note1["updated"].tm_mday == 14
metadata_note2 = documents[1].metadata
assert "title" in metadata_note2.keys()
assert "created" in metadata_note2.keys()
assert "updated" not in metadata_note2.keys()
assert "note-attributes.author" in metadata_note2.keys()
assert "note-attributes.source" in metadata_note2.keys()
assert "content" not in metadata_note2.keys()
assert "content-raw" not in metadata_note2.keys()
assert (
"resource" not in metadata_note2.keys()
) # This is too large to be stored as metadata
assert metadata_note2["title"] == "Summer Training Program"
assert metadata_note2["note-attributes.author"] == "Mike McGarry"
assert metadata_note2["note-attributes.source"] == "mobile.iphone"
assert isinstance(metadata_note2["created"], time.struct_time)
assert metadata_note2["created"].tm_year == 2022
assert metadata_note2["created"].tm_mon == 12
assert metadata_note2["created"].tm_mday == 27
def test_loadnotebookwithconflictingsourcemetadatatag_sourceoffilepreferred(
self,
) -> None:
documents = EverNoteLoader(
self.example_notebook_path("sample_notebook_2.enex"), False
).load()
assert "sample_notebook_2.enex" in documents[0].metadata["source"]
assert "mobile.iphone" not in documents[0].metadata["source"]
def test_returnsingledocument_loadnotebook_eachnoteiscombinedinto1document(
self,
) -> None:
loader = EverNoteLoader(
self.example_notebook_path("sample_notebook.enex"), True
)
documents = loader.load()
assert len(documents) == 1
def test_returnsingledocument_loadnotebook_notecontentiscombinedinto1document(
self,
) -> None:
loader = EverNoteLoader(
self.example_notebook_path("sample_notebook.enex"), True
)
documents = loader.load()
note = documents[0]
assert note.page_content == "abc**Jan - March 2022**"
Loading…
Cancel
Save