Ming ODM User Guide

Introduction

Ming provides an high-level abstraction for modeling objects in MongoDB. This higher-layer abstraction is referred in this document as the ODM since it is implemented in the “spirit” of object-relational mappers such as SQLAlchemy.

The ODM provides several features beyond those provided by basic PyMongo:

Schema Validation
Because MongoDB is schema-less, there are no guarantees given to the database client of the format of the data that may be returned from a query; you can put any kind of document into a collection that you want. The goal of Ming is to allow you to specify the schema for your data in Python code and then develop in confidence, knowing the format of data you get from a query.
UnitOfWork
Using ODM-enabled sessions allows you to operate on in-memory objects exclusively until you are ready to “flush” all changes to the database. Although MongoDB doesn’t provide transactions, the unit-of-work (UOW) pattern provides some of the benefits of transactions by delaying writes until you are fairly certain nothing is going to go wrong.
IdentityMap
In base Ming, each query returns unique document objects even if those document objects refer to the same document in the database. The identity map in Ming ensures that when two queries each return results that correspond to the same document in the database, the queries will return the same Python object as well.
Relations
Although MongoDB is non-relational, it is still useful to represent relationships between documents in the database. The ODM layer in Ming provides the ability to model relationships between documents as straightforward properties on Python objects.

Installing

To begin working with Ming, you’ll need to download and install a copy of MongoDB. You can find download instructions at http://www.mongodb.org/downloads

In order to install ming, as it’s available on PyPI, you can use pip (We recommend using a virtualenv for development.):

$ pip install ming

Alternatively, if you don’t have pip installed, download it from PyPi and run

$ python setup.py install

Note

To use the bleeding-edge version of Ming, you can get the source from GitHub and install it as above.

Connecting to MongoDB

Ming manages your connection to the MongoDB database using an object known as a DataStore. The DataStore is actually just a thin wrapper around a pymongo connection and Database object. (The actual Database object can always be accessed via the DataStore.db property of the DataStore instance).

>>> from ming import create_datastore
>>>
>>> datastore = create_datastore('mongodb://localhost:27017/tutorial')
>>> datastore
<DataStore None>
>>> # The connection is actually performed lazily
>>> # the first time db is accessed
>>> datastore.db
Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'tutorial')
>>> datastore
<DataStore Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'tutorial')>

Note

Ming also provides a “mongo in memory” implementation, which is non-persistent, in Python, and possibly much faster than MongoDB for very small data sets, as you might use in testing. To use it, just change the connection url to mim://

The ODM Session

Ming, like many object-relational mappers (ORMs), revolves around the idea of model classes. In order to create these classes, we need a way of connecting them to the datastore. Ming uses an object known as a Session to do this.

The session is responsible for this as well as maintaining the unit of work, identity map, and relations between objects. The ODM session itself is not designed to be thread-safe, so Ming provides a thread-local version of the session for safe operation in a multithreaded environment.

Usually you will rely on the session for everything and won’t use the datastore directly, you will just pass it to the Session and rely on the session itself:

>>> from ming import create_datastore
>>> from ming.odm import ThreadLocalODMSession
>>> 
>>> session = ThreadLocalODMSession(
...     bind=create_datastore('mim:///tutorial')
... )
>>> session
TLProxy of <session>
  <UnitOfWork>
    <new>
    <clean>
    <dirty>
    <deleted>
  <imap (0)>
>>> # The database and datastore are still available
>>> # through the session as .db and .bind
>>> session.db
mim.Database(tutorial)
>>> session.bind
<DataStore mim.Database(tutorial)>

Note

If you are using the TurboGears2 web framework the framework sets up a DataStore for you and passes it to your model.init_model function so there is no need to manually create the datastore.

ReplicaSets

Connecting to a ReplicaSet is possible by passing multiple hosts separated by comma to the DataStore and the replicaSet= option as the url:

from ming import create_datastore
from ming.odm import ThreadLocalODMSession

session = ThreadLocalODMSession(
    bind=create_datastore('mongodb://localhost:27017,localhost:27018/?replicaSet=foo')
)

When connecting to a ReplicaSet some useful options are available that define how Ming should behave when writing to the database. Additionally to replicaSet option you might want to check:

  • w=X Which states how many members of the replica set should have replicated the data before and insertion/update is considered done. X can be a number or majority for more then half. See MongoDB Write Concern Option for additional details.
  • readPreference=[primary|primaryPreferred|secondary|secondaryPreferred|nearest] which states if queries should only happen on the replicaset master of if they can be performed on secondary nodes.

See MongoDB Documentation for a full list of available options inside the mongo url.

Authentication

Connecting to a MongoDB instance or cluster using authentication can be done by passing the username and password values in the url itself:

from ming import create_datastore
from ming.odm import ThreadLocalODMSession

session = ThreadLocalODMSession(
    bind=create_datastore('mongodb://myuser:mypassword@localhost:27017/dbname')
)

Mapped Classes and Documents

In MongoDB documents are just plain dictionaries. In Ming ODM layer documents are represented by mapped classes, which inherit from ming.odm.mapped_class.MappedClass. MappedClasses do not descend from dict, to emphasize the difference between a mapped class (which may contain relations to other objects) and a MongoDB document (which may not).

To start working with MappedClasses you need a few more imports:


from ming import schema
from ming.odm import MappedClass
from ming.odm import FieldProperty, ForeignIdProperty

Mapped Classes also define the schema of your data, it means that whenever you are storing data into the MappedClass it gets validated against the schema. The attributes available to the document are declared as FieldProperty instances and their type for validation is specified in a declarative manner:

class WikiPage(MappedClass):
    class __mongometa__:
        session = session
        name = 'wiki_page'

    _id = FieldProperty(schema.ObjectId)
    title = FieldProperty(schema.String(required=True))
    text = FieldProperty(schema.String(if_missing=''))

The schema instances passed to FieldProperty also provide some options to ensure that they are not empty or to provide a default value when they are. See FancySchemaItem class for a list of available options.

Note

For a full list of available schema types refer to ming.schema module.

At the end of the model file, you should call Mapper.compile_all() to ensure that Ming has full information on all mapped classes:


from ming.odm import Mapper
Mapper.compile_all()

Type Annotations

Some type annotations are in Ming, but you need to add a hint to each class to help. The primary goal so far is to improve IDE experience. They may or may not work with mypy. Add some imports and the query: line to your models like this:

import typing

if typing.TYPE_CHECKING:
    from ming.odm.mapper import Query

...

class WikiPage(MappedClass):
    class __mongometa__:
        session = session
        name = 'wiki_page'

    query: 'Query[WikiPage]'

    ...

Creating Objects

Once we have the boilerplate out of the way, we can create instances of the WikiPage as any other Python class. One thing to notice is that we don’t explicitly call the save() method on the WikiPage; that will be called for us automatically when we flush() the session:

>>> wp = WikiPage(title='FirstPage',
...               text='This is a page')
>>> wp
<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='FirstPage' text='This is a page'>

The previous line will just create a new WikiPage and store it inside the ODM UnitOfWork and IdentityMap in new state:

>>> session
TLProxy of <session>
  <UnitOfWork>
    <new>
      <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
          title='FirstPage' text='This is a page'>
    <clean>
    <dirty>
    <deleted>
  <imap (1)>
    WikiPage : 64135757bd1c2ce4e0454e0c => <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
        title='FirstPage' text='This is a page'>

As soon as we flush our session the Unit of Work is processed, all the changes will be reflected on the database itself and the object switches to clean state:

>>> session.flush()
>>> session
TLProxy of <session>
  <UnitOfWork>
    <new>
    <clean>
      <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
          title='FirstPage' text='This is a page'>
    <dirty>
    <deleted>
  <imap (1)>
    WikiPage : 64135757bd1c2ce4e0454e0c => <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
        title='FirstPage' text='This is a page'>

By default the session will keep track of the objects that has already seen and that are currently in clean state (which means that they have not been modified since the last flush to the session). If you want to trash away those objects from the session itself you can call the clear method

>>> session.clear()
>>> session
TLProxy of <session>
  <UnitOfWork>
    <new>
    <clean>
    <dirty>
    <deleted>
  <imap (0)>

Clearing the session gives you a brand new session, so keep in mind that after clearing it, it won’t have track anymore of the previous items that were created. While it is possible to flush the session multiple times, it is common practice in web applications to clear it only once at the end of the request.

Note

Both flushing the session and clearing it at the end of every request in web application can be provided automatically by the MingMiddleware WSGI Middleware.

Note

In case you are using the TurboGears2 Web Framework there is no need to use the MingMiddleware as TurboGears will automatically flush and clear the session for your at the end of each request.

Querying Objects

Once we have a WikiPage in the database, we can retrieve it using the .query attribute. The query attribute is a proxy to the Session query features which expose three methods that make possible to query objects _ClassQuery.get(), ODMSession.find() and ODMSession.find_and_modify():

>>> wp = WikiPage.query.get(title='FirstPage')
>>> 
>>> # Note IdentityMap keeps only one copy of the object when they are the same
>>> wp2 = WikiPage.query.find({'text': 'This is a page'}).first()
>>> wp is wp2
True

As you probably noticed while we directly retrieved the object when calling .get() we needed to append the .first() call to the result of .find(). That is because ODMSession.find() actually returns an ODMCursor which allows to retrieve multiple results, just get the first or count them:

>>> # Create a new page to see find in action
>>> wp2 = WikiPage(title='SecondPage', text='This is a page')
>>> session.flush()
>>> 
>>> WikiPage.query.find({'text': 'This is a page'}).count()
2
>>> WikiPage.query.find({'text': 'This is a page'}).first()
<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='FirstPage' text='This is a page'>
>>> WikiPage.query.find({'text': 'This is a page'}).all()
[<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='FirstPage' text='This is a page'>, <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0d')
  title='SecondPage' text='This is a page'>]

ODMSession.find() uses the MongoDB query language directly, so if you are already familiar with pymongo the same applies. Otherwise you can find the complete query language specification on MongoDB Query Operators documentation.

The find operator also allows to retrieve only some fields of the document, thus avoiding the cost of validating them all. This is especially useful to speed up queries in case of big subdocuments or list of subdocuments:

>>> WikiPage.query.find({'text': re.compile(r'^This')}, projection=('title',)).all()
[<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='FirstPage' text=''>, <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0d')
  title='SecondPage' text=''>]

Note

Keep in mind that properties validation still applies, so when using the fields option you will still get the if_missing value if they declare one and the query will fail if the fields where required.

Notice that querying again a document that was retrieved with a fields limit won’t add the additional fields unless the refresh option is used:

>>> WikiPage.query.find({}).first()
<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='FirstPage' text=''>
>>> WikiPage.query.find({}, refresh=True).first()
<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='FirstPage' text='This is a page'>

Also applying the fields limit to a document already in the IdentityMap won’t do anything unless refresh is used (which is usually not what you want as you already paid the cost of validating the attributes, so there is not much benefit in throwing them away):

>>> docs = WikiPage.query.find({'text': re.compile(r'^This')}, projection=('title',)).all()
>>> docs[0].title, docs[0].text
('FirstPage', 'This is a page')
>>> docs[1].title, docs[1].text
('SecondPage', '')

Editing Objects

We already know how to create and get back documents, but an ODM won’t be of much use if it didn’t enable editing them. As Ming exposes MongoDB documents as objects, to update a MongoDB object you can simply change one of its properties and the UnitOfWork will track that the object needs to be updated:

>>> wp = WikiPage.query.get(title='FirstPage')
>>> session
TLProxy of <session>
  <UnitOfWork>
    <new>
    <clean>
      <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
          title='FirstPage' text='This is a page'>
    <dirty>
    <deleted>
  <imap (1)>
    WikiPage : 64135757bd1c2ce4e0454e0c => <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
        title='FirstPage' text='This is a page'>
>>> 
>>> wp.title = 'MyFirstPage'
>>> # Notice that the object has been marked dirty
>>> session
TLProxy of <session>
  <UnitOfWork>
    <new>
    <clean>
    <dirty>
      <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
          title='MyFirstPage' text='This is a page'>
    <deleted>
  <imap (1)>
    WikiPage : 64135757bd1c2ce4e0454e0c => <WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
        title='MyFirstPage' text='This is a page'>
>>> 
>>> # Flush the session to actually apply the changes
>>> session.flush()

Another option to edit an object is to actually rely on ODMSession.find_and_modify() method which will query the object and update it atomically:

>>> WikiPage.query.find_and_modify({'title': 'MyFirstPage'},
...                                update={'$set': {'text': 'This is my first page'}},
...                                new=True)
<WikiPage _id=ObjectId('64135757bd1c2ce4e0454e0c')
  title='MyFirstPage' text='This is my first page'>

This is often used to increment counters or acquire global locks in mongodb

Note

find_and_modify always refreshes the object in the IdentityMap, so the object in your IdentityMap will always get replaced with the newly retrieved value. Make sure you properly flushed any previous change to the object and use the new option to avoid retrieving a stale version of the object if you plan to modify it.

Ming also provides a way to update objects outside the ODM through ODMSession.update(). This might be handy when you want to update the object but don’t need the object back or don’t want to pay the cost of retrieving and validating it:

>>> WikiPage.query.update({'_id': wp._id}, {'$set': {'text': 'This is my first page!!!'}})
{'connectionId': None, 'updatedExisting': True, 'err': None, 'ok': 1.0, 'n': 1, 'nModified': 1}
>>> 
>>> # Update doesn't fetch back the document, so
>>> # we still have the old value in the IdentityMap
>>> WikiPage.query.get(wp._id).text
'This is my first page'
>>> 
>>> # Unless we refresh it.
>>> wp = session.refresh(wp)
>>> wp.text
'This is my first page!!!'

Note

The ODMSession.refresh() method provides a convenient way to refresh state of objects from the database, this is like querying them back with .find() using the refresh option. So keep in mind that any change not yet persisted on the database will be lost.

Deleting Objects

Deleting objects can be performed by calling the _InstQuery.delete() method which is directly exposed on objects:

>>> wp = WikiPage.query.get(title='MyFirstPage')
>>> wp.delete()
>>> 
>>> # We flush the session to actually delete the object
>>> session.flush()
>>> 
>>> # The object has been deleted and so is not on the DB anymore.
>>> WikiPage.query.find({'title': 'MyFirstPage'}).count()
0

In case you need to delete multiple objects and don’t want to query them back to call .delete() on each of them you can use ODMSession.remove() method to delete objects using a query:

>>> WikiPage.query.find().count()
1
>>> WikiPage.query.remove({})
>>> 
>>> WikiPage.query.find().count()
0

Working with SubDocuments

Ming, like MongoDB, allows for documents to be arbitrarily nested. For instance, we might want to keep a metadata property on our WikiPage that kept tag and category information.

To do this, we just declare a WikiPageWithMetadata which will provide a metadata nested document that contains an array of tags and an array of categories. This is made possible by the schema.Object and schema.Array types:

class WikiPageWithMetadata(MappedClass):
    class __mongometa__:
        session = session
        name = 'wiki_page_with_metadata'

    _id = FieldProperty(schema.ObjectId)
    title = FieldProperty(schema.String(required=True))
    text = FieldProperty(schema.String(if_missing=''))

    metadata = FieldProperty(schema.Object({
        'tags': schema.Array(schema.String),
        'categories': schema.Array(schema.String)
    }))

Now, what happens when we create a page and try to update it?

>>> wpm = WikiPageWithMetadata(title='MyPage')
>>> session.flush()
>>> 
>>> # Get back the object to see that Ming creates the correct structure for us
>>> session.clear()
>>> wpm = WikiPageWithMetadata.query.get(title='MyPage')
>>> wpm.metadata
I{'categories': [], 'tags': []}
>>> 
>>> # We can append or edit subdocuments like any other property
>>> wpm.metadata['tags'].append('foo')
>>> wpm.metadata['tags'].append('bar')
>>> session.flush()
>>> 
>>> # Check that ming updated everything on flush
>>> session.clear()
>>> wpm = WikiPageWithMetadata.query.get(title='MyPage')
>>> wpm.metadata
I{'categories': [], 'tags': ['foo', 'bar']}

Ming creates the structure for us automatically. (If we had wanted to specify a different default value for the metadata property, we could have done so using the if_missing parameter, of course.)

Note

In case you want to store complex subdocuments that don’t need validation or for which you don’t want validation for speed reasons you can use the schema.Anything type which can store subdocuments and arrays without validating them.

Relating Classes

The real power of the ODM comes in being able to handle relations between objects. On Ming this is made possible by the ForeignIdProperty and RelationProperty which provide a way to declare references to other documents and retrieve them.

A common use case if for example adding comments on our WikiPage, to do so we need to declare a new WikiComment class:

class WikiComment(MappedClass):
    class __mongometa__:
        session = session
        name = 'wiki_comment'

    _id = FieldProperty(schema.ObjectId)
    page_id = ForeignIdProperty('WikiPage')
    text = FieldProperty(schema.String(if_missing=''))

    page = RelationProperty('WikiPage')

Here, we have defined a ForeignIdProperty page_id to reference the original Wikipage. This tells Ming to create a field in WikiComment that represents a “foreign key” into the WikiPage._id field. This provide a guide to ming on how to resolve the relationship between WikiPage and WikiComment and how to build queries to fetch the related objects.

In order to actually use the relationship, however, we must use a RelationProperty to reference the related class.

In this case, we will use the property page to access the page about which this comment refers. While to access the comments from the WikiPage we will add a comments property to the page itself:

class WikiPage(MappedClass):
    class __mongometa__:
        session = session
        name = 'wiki_page'

    _id = FieldProperty(schema.ObjectId)
    title = FieldProperty(schema.String(required=True))
    text = FieldProperty(schema.String(if_missing=''))

    comments = RelationProperty('WikiComment')

Now actually use these classes, we need to create some comments:

>>> # Get a page for which to add the comments
>>> wp = WikiPage.query.get(title='MyFirstPage')
>>> 
>>> # Create some comments
>>> WikiComment(page_id=wp._id, text='A comment')
<WikiComment _id=ObjectId('64135757bd1c2ce4e0454e10')
  page_id=ObjectId('64135757bd1c2ce4e0454e0f') text='A
  comment'>
>>> WikiComment(page_id=wp._id, text='Another comment')
<WikiComment _id=ObjectId('64135757bd1c2ce4e0454e11')
  page_id=ObjectId('64135757bd1c2ce4e0454e0f') text='Another
  comment'>
>>> session.flush()

And voilà, you have related objects. To get them back you can just use the RelationProperty available in the WikiPage as comments:

>>> # Load the original page
>>> wp = WikiPage.query.get(title='MyFirstPage')
>>> 
>>> # View its comments
>>> wp.comments
I[<WikiComment _id=ObjectId('64135757bd1c2ce4e0454e10')
  page_id=ObjectId('64135757bd1c2ce4e0454e0f') text='A
  comment'>, <WikiComment _id=ObjectId('64135757bd1c2ce4e0454e11')
  page_id=ObjectId('64135757bd1c2ce4e0454e0f') text='Another
  comment'>]
>>> wp.comments[0].page is wp
True

Relations also support updating by replacing the value of the RelationProperty that drives the relation:

>>> # Load the original page
>>> wp = WikiPage.query.get(title='MyFirstPage')
>>> len(wp.comments)
2
>>> 
>>> wk = WikiComment(text='A comment')
>>> wk.page = wp
>>> session.flush()
>>> 
>>> # Refresh the page to see the relation change even on its side.
>>> wp = session.refresh(wp)
>>> len(wp.comments)
3

While it is not possible to append or remove values from RelationProperty that are a list (like the WikiPage.comments in our example it is still possible to replace its value:

>>> # Load the original page
>>> wp = WikiPage.query.get(title='MyFirstPage')
>>> len(wp.comments)
3
>>> 
>>> wk = WikiComment(text='A comment')
>>> wp.comments = wp.comments + [wk]
>>> session.flush()
>>> 
>>> # Refresh the page to see the relation change even on its side.
>>> wp = session.refresh(wp)
>>> len(wp.comments)
4
>>> # Refresh the comment to see the relation change even on its side.
>>> wk = session.refresh(wk)
>>> wk.page is wp
True

Note

You should treat ming relations that contain more than one value as tuples, they are stored as an immutable list.

List based Relationships (Many-to-Many)

Ming supports many-to-many relationships by using MongoDB arrays. Instead of storing the foreign id as an ObjectId it can be storead as an array of ObjectId.

This can be achieved by using the uselist=True option of ForeignIdProperty:

class Parent(MappedClass):
    class __mongometa__:
        name='parent'
        session = session

    _id = FieldProperty(schema.ObjectId)
    name = FieldProperty(schema.String(required=True))
    _children = ForeignIdProperty('Child', uselist=True)

    children = RelationProperty('Child')
class Child(MappedClass):
    class __mongometa__:
        name='child'
        session = session

    _id = FieldProperty(schema.ObjectId)
    name = FieldProperty(schema.String(required=True))

    parents = RelationProperty('Parent')

Then you can create and relate objects as you would with any other kind of relationship:

>>> # Create a bunch of Parents and Children
>>> p1, p2 = Parent(name='p1'), Parent(name='p2')
>>> c1, c2 = Child(name='c1'), Child(name='c2')
>>> 
>>> # Relate them through their relationship
>>> p1.children = [c1, c2]
>>> c2.parents = [p1, p2]
>>> session.flush()
>>> 
>>> # Fetch them back to see if the relations are correct
>>> session.clear()
>>> p1, p2 = Parent.query.find({}).sort('name').all()
>>> c1, c2 = Child.query.find({}).sort('name').all()
>>> 
>>> len(c1.parents)  # c1 was only assigned to p1
1
>>> len(c2.parents)  # c2 was assigned both to p1 and p2
2
>>> len(p1.children) == 2 and c1 in p1.children
True
>>> len(p2.children) == 1 and p2.children[0] is c2
True

Forcing a ForeignIdProperty

By default the RelationProperty will automatically detect the relationship side and foreign id property. In case the automatic detection fails it is possible to manually specify it through the via="propertyname" option of the RelationProperty:

class WebSite(MappedClass):
    class __mongometa__:
        name='web_site'
        session = self.session

    _id = FieldProperty(schema.ObjectId)

    _index = ForeignIdProperty('WebPage')
    index = RelationProperty('WebPage', via='_index')

    pages = RelationProperty('WebPage', via='_website')

class WebPage(MappedClass):
    class __mongometa__:
        name='web_page'
        session = self.session

    _id = FieldProperty(schema.ObjectId)

    _website = ForeignIdProperty('WebSite')
    website = RelationProperty('WebSite', via='_website')

Forcing a Relationship side

While it is possible to force a specific ForeignIdProperty there are cases when it is also necessary to force the relationship side. This might be the case if both sides of the relationship own a property with the specified name, a common case is a circular relationship where both sides are the same class.

Specifying the side is possible by passing a tuple to the via property with the second value being True or False depending on the fact that the specified ForeignIdProperty should be considered owned by this side of the relationship or not:

class WebPage(MappedClass):
    class __mongometa__:
        name='web_page'
        session = self.session

    _id = FieldProperty(int)

    children = RelationProperty('WebPage')
    _children = ForeignIdProperty('WebPage', uselist=True)
    parents = RelationProperty('WebPage', via=('_children', False))

Dropping Down Below the ODM

There might be cases when you want to directly access the pymongo layer or get outside of the ODM for speed reasons or to get access to additional features.

This can be achieved by dropping down to the Ming Foundation Layer through the ODM Mapper. The Foundation Layer is a lower level API that provides validation and schema enforcement over plain dictionaries. The Mapper has a collection attribute that points to the Foundation Layer collection. The Foundation Layer adds additional features over plain dictionaries through the m property:

>>> from ming.odm import mapper
>>> mongocol = mapper(WikiPage).collection.m.collection
>>> mongocol
mim.Collection(mim.Database(odm_tutorial), wiki_page)
>>> 
>>> mongocol.find_one({'title': 'MyFirstPage'})
{'_id': ObjectId('64135757bd1c2ce4e0454e0f'), 'text': 'This is my first page!!!', 'title': 'MyFirstPage'}

You can also operate at the Foundation Layer through the Mapper and ODMSession by accessing the low level Session implementation and mapped Collection:

>>> from ming.odm import mapper
>>> wikipage_mapper = mapper(WikiPage)
>>> 
>>> # Mapper.collection is the foundation layer collection
>>> founding_WikiPage = wikipage_mapper.collection
>>> 
>>> # Retrieve the foundation layer session
>>> founding_Session = session.impl
>>> 
>>> # The foundation layer still returns dictionaries, but validation is performed.
>>> founding_Session.find(founding_WikiPage, {'title': 'MyFirstPage'}).all()
[{'_id': ObjectId('64135757bd1c2ce4e0454e0f'), 'text': 'This is my first page!!!', 'title': 'MyFirstPage'}]