Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ env/
htmlcov/
venv
venv3
.python-version
61 changes: 33 additions & 28 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ MongoEngine
===========
:Info: MongoEngine is an ORM-like layer on top of PyMongo.
:Repository: https://github.com/MongoEngine/mongoengine
:Author: Harry Marr (http://github.com/hmarr)
:Maintainer: Bastien Gerard (http://github.com/bagerard)
:Author: Harry Marr (https://github.com/hmarr)
:Maintainer: Bastien Gerard (https://github.com/bagerard)

.. image:: https://github.com/MongoEngine/mongoengine/actions/workflows/github-actions.yml/badge.svg?branch=master
:target: https://github.com/MongoEngine/mongoengine/actions
Expand All @@ -13,23 +13,24 @@ MongoEngine
:target: https://coveralls.io/github/MongoEngine/mongoengine?branch=master

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:target: https://github.com/psf/black

.. image:: https://pepy.tech/badge/mongoengine/month
:target: https://pepy.tech/project/mongoengine

.. image:: https://img.shields.io/pypi/v/mongoengine.svg
:target: https://pypi.python.org/pypi/mongoengine
:target: https://pypi.org/project/mongoengine/


.. image:: https://readthedocs.org/projects/mongoengine-odm/badge/?version=latest
:target: https://readthedocs.org/projects/mongoengine-odm/builds/
:target: https://mongoengine-odm.readthedocs.io/


⚠️ **Warning:** ``mongoengine.org`` is no longer controlled by the MongoEngine
project and appears to be an expired domain takeover. The official MongoEngine
project is maintained on `GitHub <https://github.com/MongoEngine/mongoengine>`_
and documented on
`ReadTheDocs <https://mongoengine-odm.readthedocs.io/>`_.
`Read the Docs <https://mongoengine-odm.readthedocs.io/>`_.

About
=====
Expand All @@ -44,31 +45,30 @@ Supported MongoDB Versions
MongoEngine is currently tested against MongoDB v4.4, v5.0, v6.0, v7.0 and
v8.0. Future versions should be supported as well, but aren't actively tested
at the moment. Make sure to open an issue or submit a pull request if you
experience any problems with a more recent MongoDB versions.
experience any problems with more recent MongoDB versions.

Installation
============
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
`pip <https://pip.pypa.io/>`_. You can then use ``python -m pip install -U mongoengine``.
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
and thus you can use ``easy_install -U mongoengine``. Another option is
`pipenv <https://docs.pipenv.org/>`_. You can then use ``pipenv install mongoengine``
to both create the virtual environment and install the package. Otherwise, you can
download the source from `GitHub <https://github.com/MongoEngine/mongoengine>`_ and
run ``python setup.py install``.
MongoEngine requires Python 3.10 or newer. Install it from PyPI with:

.. code-block:: console

$ python -m pip install -U mongoengine

The support for Python2 was dropped with MongoEngine 0.20.0
To install a source checkout, run ``python -m pip install .`` from the
repository root.

Dependencies
============
All of the dependencies can easily be installed via `python -m pip <https://pip.pypa.io/>`_.
At the very least, you'll need these two packages to use MongoEngine:
MongoEngine requires:

- pymongo>=3.12
- PyMongo >=3.12,<5.0

The following optional packages enable additional functionality:

If you utilize a ``DateTimeField``, you might also use a more flexible date parser:

- dateutil>=2.1.0
- python-dateutil >=2.1.0

If you need to use an ``ImageField`` or ``ImageGridFsProxy``:

Expand Down Expand Up @@ -107,7 +107,7 @@ Some simple examples of what MongoEngine code looks like:
>>> post1.save()

# Create a link-based post
>>> post2 = LinkPost(title='MongoEngine Docs', url='hmarr.com/mongoengine')
>>> post2 = LinkPost(title='Example Docs', url='https://example.com/')
>>> post2.tags = ['mongoengine', 'documentation']
>>> post2.save()

Expand All @@ -120,7 +120,7 @@ Some simple examples of what MongoEngine code looks like:
... print('Link:', post.url)
...

# Count all blog posts and its subtypes
# Count all blog posts and their subtypes
>>> BlogPost.objects.count()
2
>>> TextPost.objects.count()
Expand All @@ -136,8 +136,13 @@ Some simple examples of what MongoEngine code looks like:

Tests
=====
To run the test suite, ensure you are running a local instance of MongoDB on
the standard port and have ``pytest`` installed. Then, run ``pytest tests/``.
To run the test suite, ensure MongoDB is running on the standard port, then
install the package with its test dependencies and run pytest:

.. code-block:: console

$ python -m pip install -e ".[test]"
$ pytest tests/

To run the test suite on every supported Python and PyMongo version, you can
use ``tox``. You'll need to make sure you have each supported Python version
Expand All @@ -152,10 +157,10 @@ installed in your environment and then:

Community
=========
- `MongoEngine Users mailing list
<http://groups.google.com/group/mongoengine-users>`_
- `MongoEngine Developers mailing list
<http://groups.google.com/group/mongoengine-dev>`_
- Ask usage questions on `Stack Overflow
<https://stackoverflow.com/questions/tagged/mongoengine>`_.
- Report confirmed bugs on `GitHub Issues
<https://github.com/MongoEngine/mongoengine/issues>`_.

Contributing
============
Expand Down
17 changes: 8 additions & 9 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ Changelog
Development
===========
- (Fill this out as you fix issues and develop your features).
- Add a warning that ``mongoengine.org`` is no longer controlled by the MongoEngine
project and appears to be an expired domain takeover.
- Fix querying GenericReferenceField with __in operator #2886
- Fix Document.compare_indexes() not working correctly for text indexes on multiple fields #2612

Changes in 1.0.0
===========
- Add support for transaction through run_in_transaction (kudos to juannyG for this) #2569

Some considerations:

- make sure to read https://www.mongodb.com/docs/manual/core/transactions-in-applications/#callback-api-vs-core-api
- run_in_transaction context manager relies on Pymongo coreAPI, it will retry automatically in case of ``UnknownTransactionCommitResult`` but not ``TransientTransactionError`` exceptions
- Using .count() in a transaction will always use Collection.count_document (as estimated_document_count is not supported in transactions)
- Add a warning that ``mongoengine.org`` is no longer controlled by the MongoEngine
project and appears to be an expired domain takeover.
- Fix querying GenericReferenceField with __in operator #2886
- Fix Document.compare_indexes() not working correctly for text indexes on multiple fields #2612
- BREAKING CHANGE: wrap _document_registry (normally not used by end users) with _DocumentRegistry which acts as a singleton to access the registry
- Log a warning in case users creates multiple Document classes with the same name as it can lead to unexpected behavior #1778
- Fix use of $geoNear or $collStats in aggregate #2493
Expand All @@ -32,11 +35,7 @@ Development

Changes in 0.29.3
=================
- Remove mentions of mongoengine[dot]org has we no longer own it #2905

Changes in 0.29.2
=================
- fixes relted with
- Remove mentions of mongoengine[dot]org as we no longer own it #2905

Changes in 0.29.1
=================
Expand Down
21 changes: 16 additions & 5 deletions docs/guide/defining-documents.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ To define a schema for a document, create a class that inherits from
:class:`~mongoengine.Document`. Fields are specified by adding **field
objects** as class attributes to the document class::

from datetime import datetime, timezone

from mongoengine import *
import datetime

class Page(Document):
title = StringField(max_length=200, required=True)
date_modified = DateTimeField(default=datetime.datetime.utcnow)
date_modified = DateTimeField(
default=lambda: datetime.now(timezone.utc)
)

As BSON (the binary format for storing data in mongodb) is order dependent,
documents are serialized based on their field order.
Expand Down Expand Up @@ -274,7 +277,10 @@ store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate
user = ReferenceField(User)
answers = DictField()

survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
survey_response = SurveyResponse(
date=datetime.now(timezone.utc),
user=request.user,
)
response_form = ResponseForm(request.POST)
survey_response.answers = response_form.cleaned_data()
survey_response.save()
Expand Down Expand Up @@ -689,7 +695,9 @@ collection after a given period. See the official
documentation for more information. A common usecase might be session data::

class Session(Document):
created = DateTimeField(default=datetime.utcnow)
created = DateTimeField(
default=lambda: datetime.now(timezone.utc)
)
meta = {
'indexes': [
{'fields': ['created'], 'expireAfterSeconds': 3600}
Expand Down Expand Up @@ -812,7 +820,10 @@ the class name in every documents. When a document is loaded, MongoEngine checks
it's :attr:`_cls` attribute and use that class to construct the instance.::

Page(title='a funky title').save()
DatedPage(title='another title', date=datetime.utcnow()).save()
DatedPage(
title='another title',
date=datetime.now(timezone.utc),
).save()

print(Page.objects().count()) # 2
print(DatedPage.objects().count()) # 1
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/signals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ Attaching Events
After writing a handler function like the following::

import logging
from datetime import datetime
from datetime import datetime, timezone

from mongoengine import *
from mongoengine import signals

def update_modified(sender, document):
document.modified = datetime.utcnow()
document.modified = datetime.now(timezone.utc)

You attach the event handler to your :class:`~mongoengine.Document` or
:class:`~mongoengine.EmbeddedDocument` subclass::
Expand Down Expand Up @@ -139,7 +139,7 @@ cleaner looking while still allowing manual execution of the callback::

@handler(signals.pre_save)
def update_modified(sender, document):
document.modified = datetime.utcnow()
document.modified = datetime.now(timezone.utc)

@update_modified.apply
class Record(Document):
Expand Down
2 changes: 1 addition & 1 deletion mongoengine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)


VERSION = (0, 29, 3)
VERSION = (1, 0, 0)


def get_version():
Expand Down
13 changes: 11 additions & 2 deletions mongoengine/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,15 @@ class DateTimeField(BaseField):
installed you can utilise it to convert varying types of date formats into valid
python datetime objects.

Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
Note: To default the field to the current datetime, use
``DateTimeField(default=lambda: datetime.now(timezone.utc))``

.. warning::

PyMongo converts timezone-aware datetimes to UTC before storing them.
The original timezone and UTC offset are not preserved. By default,
datetimes are returned as naive UTC values; use ``tz_aware=True`` on
the connection to receive timezone-aware UTC values.

Note: Microseconds are rounded to the nearest millisecond.
Pre UTC microsecond support is effectively broken.
Expand Down Expand Up @@ -631,7 +639,8 @@ class ComplexDateTimeField(StringField):
The `,` as the separator can be easily modified by passing the `separator`
keyword when initializing the field.

Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
Note: To default the field to the current datetime, use
``DateTimeField(default=lambda: datetime.now(timezone.utc))``
"""

def __init__(self, separator=",", **kwargs):
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ max-complexity=47
# Limits the discovery to tests directory
# avoids that it runs for instance the benchmark
testpaths = tests
filterwarnings =
ignore:No uuidRepresentation is specified!:DeprecationWarning
ignore:Multiple Document classes named .* were registered:UserWarning

[isort]
known_first_party = mongoengine,tests
Expand Down
6 changes: 3 additions & 3 deletions tests/document/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import unittest
import uuid
import weakref
from datetime import datetime
from datetime import datetime, timezone
from unittest.mock import Mock

import bson
Expand Down Expand Up @@ -3540,7 +3540,7 @@ class Test(Document):

def test_default_values_dont_get_override_upon_save_when_only_is_used(self):
class Person(Document):
created_on = DateTimeField(default=lambda: datetime.utcnow())
created_on = DateTimeField(default=lambda: datetime.now(timezone.utc))
name = StringField()

p = Person(name="alon")
Expand All @@ -3554,7 +3554,7 @@ class Person(Document):
assert orig_created_on == p3.created_on

class Person(Document):
created_on = DateTimeField(default=lambda: datetime.utcnow())
created_on = DateTimeField(default=lambda: datetime.now(timezone.utc))
name = StringField()
height = IntField(default=189)

Expand Down
10 changes: 5 additions & 5 deletions tests/document/test_timeseries_collection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import time
import unittest
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

from mongoengine import (
DateTimeField,
Expand Down Expand Up @@ -84,7 +84,7 @@ def test_insert_document_into_timeseries_collection(self):
assert collection_name in self.db.list_collection_names()

# Insert a document and ensure it was inserted
self.SensorData(timestamp=datetime.utcnow(), temperature=23.4).save()
self.SensorData(timestamp=datetime.now(timezone.utc), temperature=23.4).save()
assert collection.count_documents({}) == 1

@requires_mongodb_gte_50
Expand All @@ -98,7 +98,7 @@ def test_timeseries_expiration(self):
assert options.get("timeseries", {}) is not None
assert options["expireAfterSeconds"] == 1

self.SensorData(timestamp=datetime.utcnow(), temperature=23.4).save()
self.SensorData(timestamp=datetime.now(timezone.utc), temperature=23.4).save()

assert collection.count_documents({}) == 1

Expand Down Expand Up @@ -144,7 +144,7 @@ def test_timeseries_data_insertion_order(self):
self.SensorData._get_collection()

# Insert documents out of order
now = datetime.utcnow()
now = datetime.now(timezone.utc)
self.SensorData(timestamp=now, temperature=23.4).save()
self.SensorData(timestamp=now - timedelta(seconds=5), temperature=22.0).save()
self.SensorData(timestamp=now + timedelta(seconds=5), temperature=24.0).save()
Expand All @@ -164,7 +164,7 @@ def test_timeseries_query_by_time_range(self):
self.SensorData._get_collection_name()
self.SensorData._get_collection()

now = datetime.utcnow()
now = datetime.now(timezone.utc)
self.SensorData(timestamp=now - timedelta(seconds=10), temperature=22.0).save()
self.SensorData(timestamp=now - timedelta(seconds=5), temperature=23.0).save()
self.SensorData(timestamp=now, temperature=24.0).save()
Expand Down
Loading