Skip to content
This repository was archived by the owner on Dec 1, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions .circleci/config.yml

This file was deleted.

46 changes: 46 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Test
'on':
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch: {}
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- '3.11'
- '3.12'
- '3.13'
mongodb-version:
- 5.0.30
- 6.0.19
- 7.0.16
pymongo-version:
- 3.13.0
- 4.2.0
- 4.6.3
- 4.10.1
services:
mongodb:
image: mongo:${{ matrix.mongodb-version }}
ports:
- 27017:27017
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: 'python -m pip install --upgrade pip

pip install "pymongo==${{ matrix.pymongo-version }}"

pip install -r test-requirements.txt'
- name: Run tests
run: pytest tests/
84 changes: 14 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,27 @@ MongoMallard

MongoMallard is a fast ORM-like layer on top of PyMongo, based on MongoEngine.

* Repository: https://github.com/elasticsales/mongoengine
* Repository: https://github.com/closeio/mongoengine
* See [README_MONGOENGINE](https://github.com/elasticsales/mongoengine/blob/master/README_MONGOENGINE.rst) for MongoEngine's README.
* See [DIFFERENCES](https://github.com/elasticsales/mongoengine/blob/master/DIFFERENCES.md) for differences between MongoEngine and MongoMallard.


Benchmarks
----------

Sample run on a 2.7 GHz Intel Core i5 running OS X 10.8.3
Sample run on a Apple M3 Max running Sonoma 14.6.1

<table>
<tr>
<th></th>
<th>MongoEngine 0.8.2 (ede9fcf)</th>
<th>MongoMallard (478062c)</th>
<th>Speedup</th>
</tr>
<tr>
<td>Doc initialization</td>
<td>52.494us</td>
<td>25.195us</td>
<td>2.08x</td>
</tr>
<tr>
<td>Doc getattr</td>
<td>1.339us</td>
<td>0.584us</td>
<td>2.29x</td>
</tr>
<tr>
<td>Doc setattr</td>
<td>3.064us</td>
<td>2.550us</td>
<td>1.20x</td>
</tr>
<tr>
<td>Doc to mongo</td>
<td>49.415us</td>
<td>26.497us</td>
<td>1.86x</td>
</tr>
<tr>
<td>Load from SON</td>
<td>61.475us</td>
<td>4.510us</td>
<td>13.63x</td>
</tr>
<tr>
<td>Save to database</td>
<td>434.389us</td>
<td>289.972us</td>
<td>2.29x</td>
</tr>
<tr>
<td>Load from database</td>
<td>558.178us</td>
<td>480.690us</td>
<td>1.16x</td>
</tr>
<tr>
<td>Save/delete big object to database</td>
<td>98.838ms</td>
<td>65.789ms</td>
<td>1.50x</td>
</tr>
<tr>
<td>Serialize big object from database</td>
<td>31.390ms</td>
<td>20.265ms</td>
<td>1.55x</td>
</tr>
<tr>
<td>Load big object from database</td>
<td>41.159ms</td>
<td>1.400ms</td>
<td>29.40x</td>
</tr>
</table>
| | MongoEngine | MongoMallard | Speedup |
|---|---|---|---|
| Doc initialization | 10.113us | 3.219us | 3.14x |
| Doc getattr | 0.086us | 0.086us | 1.00x |
| Doc setattr | 0.549us | 0.211us | 2.60x |
| Doc to mongo | 5.991us | 3.181us | 1.88x |
| Load from SON | 12.094us | 0.685us | 17.66x |
| Save to database | 259.094us | 218.945us | 1.18x |
| Load from database | 260.192us | 246.576us | 1.06x |
| Save/delete big object to database | 18.510ms | 8.925ms | 2.07x |
| Serialize big object from database | 4.058ms | 2.346ms | 1.73x |
| Load big object from database | 11.205ms | 0.655ms | 17.11x |

See [tests/benchmark.py](https://github.com/elasticsales/mongoengine/blob/master/tests/benchmark.py) for source code.
2 changes: 1 addition & 1 deletion mongoengine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
__all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
list(queryset.__all__) + signals.__all__ + list(errors.__all__))

VERSION = (0, 8, 2)
VERSION = (0, 8, 3)
MALLARD = True


Expand Down
39 changes: 33 additions & 6 deletions mongoengine/base/document.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import operator
import warnings
from functools import partial

import pymongo
Expand All @@ -13,6 +14,7 @@
from mongoengine.base.common import get_document, ALLOW_INHERITANCE
from mongoengine.base.datastructures import BaseDict, BaseList
from mongoengine.base.fields import ComplexBaseField
from mongoengine.pymongo_support import LEGACY_JSON_OPTIONS

__all__ = ('BaseDocument', 'NON_FIELD_ERRORS')

Expand Down Expand Up @@ -193,14 +195,39 @@ def validate(self, clean=True):
message = "ValidationError (%s:%s) " % (self._class_name, pk)
raise ValidationError(message, errors=errors)

def to_json(self):
"""Converts a document to JSON"""
return json_util.dumps(self.to_mongo())
def to_json(self, json_options=None):
"""Convert this document to JSON."""
if json_options is None:
warnings.warn(
"No 'json_options' are specified! Falling back to "
"LEGACY_JSON_OPTIONS with uuid_representation=PYTHON_LEGACY. "
"For use with other MongoDB drivers specify the UUID "
"representation to use. This will be changed to "
"uuid_representation=UNSPECIFIED in a future release.",
DeprecationWarning,
stacklevel=2,
)
json_options = LEGACY_JSON_OPTIONS
return json_util.dumps(self.to_mongo(), json_options=json_options)

@classmethod
def from_json(cls, json_data):
"""Converts json data to an unsaved document instance"""
return cls._from_son(json_util.loads(json_data))
def from_json(cls, json_data, json_options=None):
"""Converts json data to a Document instance.

:param str json_data: The json data to load into the Document.
"""
if json_options is None:
warnings.warn(
"No 'json_options' are specified! Falling back to "
"LEGACY_JSON_OPTIONS with uuid_representation=PYTHON_LEGACY. "
"For use with other MongoDB drivers specify the UUID "
"representation to use. This will be changed to "
"uuid_representation=UNSPECIFIED in a future release.",
DeprecationWarning,
stacklevel=2,
)
json_options = LEGACY_JSON_OPTIONS
return cls._from_son(json_util.loads(json_data, json_options=json_options))

def __expand_dynamic_values(self, name, value):
"""expand any dynamic values to their correct types / values"""
Expand Down
31 changes: 21 additions & 10 deletions mongoengine/connection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pymongo
from pymongo import (MongoClient, MongoReplicaSetClient, ReadPreference,
uri_parser)
import warnings
from pymongo import MongoClient, ReadPreference, uri_parser
from pymongo.common import _UUID_REPRESENTATIONS

__all__ = [
'DEFAULT_CONNECTION_NAME',
Expand Down Expand Up @@ -36,6 +37,7 @@ def register_connection(
slaves=None,
username=None,
password=None,
uuidrepresentation=None,
**kwargs
):
"""Add a connection.
Expand Down Expand Up @@ -84,7 +86,22 @@ def register_connection(
})
if "replicaSet" in host:
conn_settings['replicaSet'] = True

if "uuidrepresentation" in uri_dict:
uuidrepresentation = uri_dict.get('uuidrepresentation')

if uuidrepresentation is None:
warnings.warn(
"No uuidrepresentation is specified! Falling back to "
"'pythonLegacy' which is the default for pymongo 3.x. "
"For compatibility with other MongoDB drivers this should be "
"specified as 'standard' or '{java,csharp}Legacy' to work with "
"older drivers in those languages. This will be changed to "
"'unspecified' in a future release.",
DeprecationWarning,
stacklevel=3,
)

conn_settings['uuidrepresentation'] = uuidrepresentation or 'pythonLegacy'
conn_settings.update(kwargs)
_connection_settings[alias] = conn_settings

Expand Down Expand Up @@ -129,18 +146,16 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
conn_settings['slaves'] = slaves
conn_settings.pop('read_preference', None)

connection_class = MongoClient
if 'replicaSet' in conn_settings:
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
# Discard port since it can't be used on MongoReplicaSetClient
conn_settings.pop('port', None)
# Discard replicaSet if not base string
if not isinstance(conn_settings['replicaSet'], str):
conn_settings.pop('replicaSet', None)
connection_class = MongoReplicaSetClient

try:
_connections[alias] = connection_class(**conn_settings)
_connections[alias] = MongoClient(**conn_settings)
except Exception as e:
raise ConnectionError("Cannot connect to database %s :\n%s" % (alias, e))
return _connections[alias]
Expand All @@ -155,10 +170,6 @@ def get_db(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
conn = get_connection(alias)
conn_settings = _connection_settings[alias]
db = conn[conn_settings['name']]
# Authenticate if necessary
if conn_settings['username'] and conn_settings['password']:
db.authenticate(conn_settings['username'],
conn_settings['password'])
_dbs[alias] = db
return _dbs[alias]

Expand Down
8 changes: 4 additions & 4 deletions mongoengine/context_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,14 @@ def __init__(self):

def __enter__(self):
""" On every with block we need to drop the profile collection. """
self.db.set_profiling_level(0)
self.db.command({"profile": 0})
self.db.system.profile.drop()
self.db.set_profiling_level(2)
self.db.command({"profile": 2})
return self

def __exit__(self, t, value, traceback):
""" Reset the profiling level. """
self.db.set_profiling_level(0)
self.db.command({"profile": 0})

def __eq__(self, value):
""" == Compare querycounter. """
Expand Down Expand Up @@ -220,7 +220,7 @@ def __repr__(self):
def _get_count(self):
""" Get the number of queries. """
ignore_query = {"ns": {"$ne": "%s.system.indexes" % self.db.name}}
count = self.db.system.profile.find(ignore_query).count() - self.counter
count = self.db.system.profile.count_documents(filter=ignore_query) - self.counter
self.counter += 1
return count

Expand Down
5 changes: 4 additions & 1 deletion mongoengine/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME
from mongoengine.context_managers import (set_write_concern, switch_db,
switch_collection)
from mongoengine.pymongo_support import list_collection_names

__all__ = ('Document', 'EmbeddedDocument', 'DynamicDocument',
'DynamicEmbeddedDocument', 'OperationError',
Expand Down Expand Up @@ -147,7 +148,9 @@ def _get_collection(cls):
max_size = cls._meta['max_size'] or 10000000 # 10MB default
max_documents = cls._meta['max_documents']

if collection_name in db.collection_names():
if collection_name in list_collection_names(
db, include_system_collections=True
):
cls._collection = db[collection_name]
# The collection already exists, check if its capped
# options match the specified capped options
Expand Down
Loading
Loading