Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Docbridge

This is an experimental Object-Document Mapping library for MongoDB. You can watch it being developed live on MongoDB's YouTube channel!

Mission Statement

  • Managing large amounts of data in MongoDB while keeping a data schema flexible is challenging.
  • This ODM is not an active record implementation, mapping documents in the database directly into similar objects in code.
  • This ODM is designed to abstract underlying documents, mapping potentially multiple document schemata into a shared object representation.
  • It should also simplify the evolution of documents in the database, automatically migrating individual documents' schemas either on-read or on-write.
  • There should be "escape hatches" so that unforeseen mappings can be implemented, hiding away the implementation code behind hopefully reuseable components.

What Does It Do?

The library currently doesn't interact directly with MongoDB - what it does do is wrap BSON documents returned by PyMongo or Motor.

For example, let's say you have a BSON document like this:

user_data_bson= {'_id': ObjectId('657072b56731c9e580e9dd70'),
'bio': 'Music conference able doctor degree debate. Participant usually above ''relate.',
'birth_date': datetime.datetime(1999, 7, 6, 0, 0),
'email': 'deanjacob@yahoo.com',
'follower_count': 59,
'full_name': 'Deborah White',
'user_id': '4',
'user_name': '@tanya15',
'followers': [{'_id': ObjectId('657072b66731c9e580e9dda6'),
'bio': 'Rich beautiful color life. Relationship instead win ''join enough board successful.',
'user_id': '58',
'user_name': '@rduncan'},
{'_id': ObjectId('657072b66731c9e580e9dd99'),
'bio': 'Picture day couple democratic morning. Environment ''manage opportunity option star food she. Occur imagine ''population single avoid.',
'user_id': '45',
'user_name': '@paynericky'},
]}

You can define a wrapper for it like this:

fromdocbridgeimportDocumentclassUserProfile(Document):
pass

The wrapper doesn't currently do very much - it just makes the dict returned by PyMongo look more like a regular Python class:

profile=UserProfile(user_data_bson, db=None)
print(repr(profile._id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # "4"

The real power of the library (like with most ODMs) comes from attaching field definitions to the class, to transform the way data is looked up on the underlying document.

Here is how the Field class can be used to configure mappings to different field names in the underlying document, or to transform the data in the underlying field, to convert a string to an int:

fromdocbridgeimportDocument, FieldclassUserProfile(Document):
id=Field(field_name="_id") # id maps to the _id doc field.user_id=Field(transform=int) # user_id transforms the field value to an intprofile=UserProfile(user_data_bson, db=None)
print(repr(profile.id)) # ObjectId('657072b56731c9e580e9dd70')print(repr(profile.user_id)) # 4 <- This is an int now!print(
repr(profile.follower_count)
) # 59 <- You can still access other doc fields as attributes.

Fallthrough Fields

There are other types of field, though. FallthroughField is one of them. It allows you to try to look up a field by one name, and if the field is missing, it will try other names that it's been configured with.

Note: This field type will probably disappear, as I may merge its functionality into Field.

fromdocbridgeimportDocument, FallthroughFieldclassUserProfile(Document):
# The `name` attribute will look up the "full_name" field,# and fall back to the "name" if it's missing.name=FallthroughField(
field_names=[
"full_name", # v2"name", # v1
]
)
profile=UserProfile({"full_name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Worksprofile=UserProfile({"name", "Mark Smith"})
assertprofile.name=="Mark Smith"# Also works!

The Subset Pattern

Some support already exists for abstracting MongoDB Design Patterns, like the Subset Pattern. The subset pattern preserves document size at a reasonable level by only embedding a subset of related data - for example, only the first 10 followers on a social media profile. The rest of the followers would be stored in their own collection, and loaded only when necessary.

classFollower(Document):
_id=Field(transform=str)
classProfile(Document):
_id=Field(transform=str)
followers=SequenceField(
type=Follower,
superset_collection="followers",
# The following query will be executed on "followers" if the field# is iterated past the embedded follower subdocuments.superset_query=lambdaob: [
{
"$match": {"user_id": ob.user_id},
},
{"$unwind": "$followers"},
{"$replaceRoot": {"newRoot": "$followers"}},
],
)
# Print all the profile's followers to the screen,# including those in the followers collection:profile=Profile(user_data_bson, db=test_db)
forfollowerinprofile:
print(follower.id)

Live Streams on YouTube

I've been developing docbridge on YouTube. You can catch the live streams at 2pm GMT on Wednesdays, or you can view the recordings:

Episode 1: Building a Simple Data Access Layer

Introducing my plans for the library, and building out the Document class, and the Simple and Fallthrough classes. (The latter two get renamed later to Field and FallthroughField)

Building a Simple Data Access Layer

Episode 2: Testing and Publishing a Python Module

Writing some Pytest test fixtures that will run tests in a transaction, and roll back any changes to the database. Then (attempting to) publish my module to PyPI!

Testing and Publishing a Python Module

Episode 3: Subsets & Joins - Part 1

Joins are a fundamental part of data modeling in MongoDB! This episode adds a field type for embedded arrays, and in the next episode it'll be extended to look up data in other collections!

Subsets & Joins: Part 1

Episode 4: Subsets & Joins - Part 2

More metaprogramming to turn a sequence of items that is split across documents and collections into a single Python sequence.

Subsets & Joins: Part 2

Episode 5: Updating Data - Part 1

It's all very well reading data from the database, but it's also nice to be able to update it!

Updating Data - Part 1

Episode 6: Updating Data - Part 2

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 2

Episode 7: Updating Data - Part 3

It turns out there's quite a lot of work to record and replay updates. Let's get on with it!

Updating Data - Part 3

About

A (currently experimental) Object-Document Mapping library.

Topics

Resources

Stars

17 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages