Source code for flask_open_directory.query
# -*- coding: utf-8 -*-
from typing import Union
from functools import wraps
import collections
from .query_abc import QueryABC
from .base_query import BaseQuery
__all__ = ('QueryABC', 'Query', 'BaseQuery')
def chainable_method(fn):
"""Marks a method to always return self/class regardless of wrapped
method's output.
"""
@wraps(fn)
def decorator(self, *args, **kwargs):
fn(self, *args, **kwargs)
return self
return decorator
[docs]class Query(BaseQuery):
"""Extends the :class:`BaseQuery` with some helper methods. The helpers
typically return the query when called for method chaining.
This allows a query to be mostly setup by another object, and the caller
provide the criteria for the search.
Example::
>>> query = Query(open_directory=OpenDirectory())
>>> query(User).filter(username='testuser').first()
User(...)
>>> query.all() # reuse the same criteria, only get all the users
[User(...),
...]
>>> q = query(Group) # change the ``model`` on a query
>>> q == query
True
>>> q.filter(group_name='testgroup').first()
Group(...)
"""
def _parse_filter_kwargs(self, kwargs):
"""Helper to yield '(key=value)' strings from kwargs. This method also
parses the key with the ``model`` attribute of an instance to get the
correct key to use in the query.
"""
if len(kwargs) > 0 and isinstance(kwargs, collections.Mapping):
for key, value in kwargs.items():
if self.model is not None:
mkey = self.model.attribute_for_key(key).ldap_key
if mkey is not None:
key = mkey
yield '({}={})'.format(key, value)
def _filter_string_from_kwargs(self, kwargs) -> Union[str, None]:
"""Helper to create the filter string from kwargs.
"""
parsed = tuple(self._parse_filter_kwargs(kwargs))
if len(parsed) > 1:
return '(&' + ''.join(parsed) + ')'
elif len(parsed) == 1:
return parsed[0]
@chainable_method
[docs] def filter(self, *args, **kwargs) -> 'Query':
"""Set's the search filter for an instance, returning the query for
method chaining.
This method will accept a string or keyword arguments as parameters.
If there is a string passed in it is set as the filter on the instance.
There is no parsing or validation done on a string if it's passed in,
which can cause errors when performing the query if it is an invalid
filter string.
If there is no string passed in then keyword arguments are parsed with
the current ``model`` set on an instance. This means that you can build
a query using either the model's attribute name or the ldap entry key.
:Example:
>>> from open_directory_utils.model import User
>>> from open_directory_utils.query import Query
>>> q = Query(search_base='dc=example,dc=com', model=User)
>>> q.filter(username='testuser')
Query(...)
>>> print(q.search_filter)
'(uid=testuser)'
>>> q.filter(uid='anotheruser')
Query(...)
>>> print(q.search_filter)
'(uid=anotheruser)'
>>> q.filter('(cn='Test User')')
>>> print(q.search_filter)
'(cn=Test User)'
"""
string = next((s for s in iter(args) if isinstance(s, str)), None)
if string is None and len(kwargs) > 0:
string = self._filter_string_from_kwargs(kwargs)
self.search_filter = string
@chainable_method
def __call__(self, model) -> 'Query':
"""Set/change the model for an instance.
"""
self.model = model