# Copyright 2016 MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import inspect
from pymodm.common import _import
from pymodm.compat import PY3
from pymodm.queryset import QuerySet
[docs]class BaseManager(object):
"""Abstract base class for all Managers.
`BaseManager` has no underlying :class:`~pymodm.queryset.QuerySet`
implementation. To extend this class into a concrete class, a `QuerySet`
implementation must be provided by calling :meth:`~from_queryset`::
class MyQuerySet(QuerySet):
...
MyManager = BaseManager.from_queryset(MyQuerySet)
Extending this class by calling `from_queryset` creates a new Manager class
that wraps only the methods from the given `QuerySet` type (and not from the
default `QuerySet` implementation).
.. seealso:: The default :class:`~pymodm.manager.Manager`.
"""
# Creation counter to keep track of order within a Model.
__creation_counter = 0
def __init__(self):
self.__counter = BaseManager.__creation_counter
BaseManager.__creation_counter += 1
def __get__(self, instance, cls):
"""Only let Manager be accessible from Model classes."""
MongoModel = _import('pymodm.base.models.MongoModel')
if isinstance(instance, MongoModel):
raise AttributeError(
"Manager isn't accessible via %s instances." % (cls.__name__,))
return self
[docs] def get_queryset(self):
"""Get a QuerySet instance."""
return self._queryset_class(self.model)
@property
def creation_order(self):
"""The order in which this Manager instance was created."""
return self.__counter
@classmethod
def _get_queryset_methods(cls, queryset_class):
def create_method(name, queryset_method):
@functools.wraps(queryset_method)
def manager_method(self, *args, **kwargs):
return getattr(self.get_queryset(), name)(*args, **kwargs)
return manager_method
predicate = inspect.isfunction if PY3 else inspect.ismethod
queryset_methods = inspect.getmembers(queryset_class, predicate)
method_dict = {
name: create_method(name, method)
for name, method in queryset_methods
# Don't shadow existing Manager methods.
if not hasattr(cls, name)}
return method_dict
@classmethod
[docs] def from_queryset(cls, queryset_class, class_name=None):
"""Create a Manager that delegates methods to the given
:class:`~pymodm.queryset.QuerySet` class.
The Manager class returned is a subclass of this Manager class.
:parameters:
- `queryset_class`: The QuerySet class to be instantiated by the
Manager.
- `class_name`: The name of the Manager class. If one is not provided,
the name of the Manager will be `XXXFromYYY`, where `XXX`
is the name of this Manager class, and YYY is the name of the
QuerySet class.
"""
if class_name is None:
class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
class_dict = dict(
cls._get_queryset_methods(queryset_class),
_queryset_class=queryset_class)
return type(class_name, (cls,), class_dict)
[docs] def contribute_to_class(self, cls, name):
self.model = cls
setattr(cls, name, self)
[docs]class Manager(BaseManager.from_queryset(QuerySet)):
"""The default manager used for :class:`~pymodm.MongoModel` instances.
This implementation of :class:`~pymodm.manager.BaseManager` uses
:class:`~pymodm.queryset.QuerySet` as its QuerySet class.
This Manager class (accessed via the ``objects`` attribute on a
:class:`~pymodm.MongoModel`) is used by default for all MongoModel classes,
unless another Manager instance is supplied as an attribute within the
MongoModel definition.
Managers have two primary functions:
1. Construct :class:`~pymodm.queryset.QuerySet` instances for use when
querying or working with :class:`~pymodm.MongoModel` instances in bulk.
2. Define collection-level functionality that can be reused across different
MongoModel types.
If you created a custom QuerySet that makes certain queries easier, for
example, you will need to create a custom Manager type that returns this
queryset using the :meth:`~pymodm.manager.BaseManager.from_queryset`
method::
class UserQuerySet(QuerySet):
def active(self):
'''Return only active users.'''
return self.raw({"active": True})
class User(MongoModel):
active = fields.BooleanField()
# Add our custom Manager.
users = Manager.from_queryset(UserQuerySet)
In the above example, we added a `users` attribute on `User` so that we can
use the `active` method on our new QuerySet type::
active_users = User.users.active()
If we wanted every method on the QuerySet to examine active users *only*, we
can do that by customizing the Manager itself::
class UserManager(Manager):
def get_queryset(self):
# Override get_queryset, so that every QuerySet created will
# have this filter applied.
return super(UserManager, self).get_queryset().raw(
{"active": True})
class User(MongoModel):
active = fields.BooleanField()
users = UserManager()
active_users = User.users.all()
"""
pass