# -*- coding: utf-8 -*-
# Time-stamp: < dao.py (2016-02-14 02:22) >
# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Provides *DAO* support.
Base :py:class:`Dao` class provides access to virtual
:py:attr:`Dao.session` attribute. Inside any *DAO* object subclassed from
:py:class:`Dao` you can access current (thread-bound) *SQLAlchemy* session
simply by accessing :py:attr:`Dao.session`.
Much more interesting is :py:class:`EntityDao` class which is inteded to be
used as a base *DAO* class for your :py:class:`summer.domain.Entity`.
"""
import logging
from sqlalchemy.orm import Query
from sqlalchemy.orm.exc import NoResultFound
from .domain import Entity, Filter
from .ex import ApplicationException
from .sf import SessionFactory
from .txaop import transactional
logger = logging.getLogger(__name__)
[docs]class Dao(object):
"""Base *DAO* class.
Provides safe access to thread bound session through :py:attr:`session`
attribute.
"""
[docs] def __init__(self, session_factory: SessionFactory):
"""Creates :py:class:`Dao` instance.
Args:
session_factory (SessionFactory): session factory to be used
"""
self.session_factory = session_factory
[docs] def __getattribute__(self, attrname: str):
"""Access to virtual :py:attr:`session` attribute.
"""
if attrname == "session":
attrval = self.session_factory.get_sqlalchemy_session()
logger.debug("Dao.session access by '%s'", self)
else:
attrval = object.__getattribute__(self, attrname)
return attrval
[docs]class EntityDao(Dao):
"""Base *DAO* class for persistent classes subclassed from
:py:class:`summer.domain.Entity`.
Provides basic persistent operations.
Defines another virtual attribute -- :py:attr:`query` -- access to
generic *SQLAlchemy* query over :py:attr:`clazz`.
"""
[docs] def __init__(self, session_factory: SessionFactory, clazz: type):
"""Creates :py:class:`EntityDao` instance.
Args:
session_factory (SessionFactory): session factory intance to be
passed to superclass
(:py:class:`Dao`)
clazz (type): reference to class type
"""
Dao.__init__(self, session_factory)
self.clazz = clazz
[docs] def __getattribute__(self, attrname: str) -> object:
"""Access to virtual :py:attr:`query` attribute.
"""
if attrname == "query":
logger.debug("EntityDao.query access by '%s'", self)
attrval = self.session_factory.get_sqlalchemy_session().query(
self.clazz)
else:
attrval = Dao.__getattribute__(self, attrname)
return attrval
[docs] def _check_entity(self, entity: Entity):
"""Check if entity is correct.
Args:
entity (Entity): entity instance to be checked
"""
if entity == None:
raise DaoException("entity == None")
elif not isinstance(entity, self.clazz):
msg = "entity is not instance of %s" % self.clazz
raise DaoException(msg, entity_class=entity.__class__)
[docs] def _get_result_list(self, query: Query, query_filter: Filter) -> list:
"""Get list of entities with filter offset applied. Useful for paging.
Args:
query (Query): query to be executed
query_filter (Filter): filter to be used for paging
Returns:
list: list of entities using query and paging supplied
"""
logger.debug("query %s", query)
logger.debug("query_filter %s", query_filter)
# apply query_filter limits
query = query.offset(query_filter.get_offset())
if query_filter.get_max_results() > 0:
query = query.limit(query_filter.get_max_results())
# query
return query.all()
@transactional
[docs] def get(self, ident: object) -> Entity:
"""Get entity by :py:attr:`Entity.id` attribute.
Args:
ident (object): primary key for :py:class:`Entity`
Returns:
Entity: entity instance or raise :py:class:`NoResultFound` if
none is found
"""
return self.query.populate_existing().get(ident)
@transactional
[docs] def save(self, entity: Entity) -> Entity:
"""Save an entity.
Args:
entity (Entity): entity to be persisted
Returns:
Entity: persisted instance
"""
self._check_entity(entity)
self.session.add(entity)
return entity
@transactional
[docs] def delete(self, entity_or_id: object) -> Entity:
"""Delete an entity.
Args:
entity_or_id (object): either :py:class:`Entity` or its primary
key
Returns:
Entity: just deleted entity instance
"""
if isinstance(entity_or_id, self.clazz):
entity = entity_or_id
else:
entity = self.get(entity_or_id)
self._check_entity(entity)
self.session.delete(entity)
return entity
@transactional
[docs] def find(self, query_filter: Filter) -> list:
"""Find collection of entities.
Args:
query_filter (Filter): filter with at least paging set
Returns:
list: list of entities using query and paging supplied
"""
return self._get_result_list(self.query, query_filter)
@transactional
[docs] def get_by_uniq(self, col_name: str, col_value: object) -> Entity:
"""Get entity by its unique attribute value.
Args:
col_name (str): attribute column name
col_value (object): attribute value
Returns:
Entity: entity instance or raise :py:class:`NoResultFound` if
none is found.
"""
assert col_name != None, "col_name == None"
assert col_value != None, "col_value == None"
kwargs = {col_name: col_value}
return self.query.filter_by(**kwargs).one()
[docs] def get_by_uniq_or_none(self, col_name: str, col_value: object) -> Entity:
"""Get entity by its unique attribute value.
Args:
col_name (str): attribute column name
col_value (object): attribute value
Returns:
Entity: entity instance or ``None`` if none is found.
"""
try:
tmp = self.get_by_uniq(col_name, col_value)
return tmp
except NoResultFound as e:
logger.exception("no result found for %s having %s == %s",
self.clazz, col_name, col_value)
return None
[docs]class CodeEntityDao(EntityDao):
"""Base *DAO* class for persistent classes subclassed from
:py:class:`summer.domain.CodeEntity`.
"""
[docs] def __init__(self, session_factory: SessionFactory, clazz: type):
"""Creates :py:class:`CodeEntityDao` instance.
Args:
session_factory (SessionFactory): session factory intance to be
passed to superclass
(:py:class:`Dao`)
clazz (type): reference to class type
"""
EntityDao.__init__(self, session_factory, clazz)
@transactional
[docs] def find_map(self, query_filter: Filter) -> dict:
"""Loads the objects into a map by :py:attr:`CodeEntity.code` attribute
used as a map key.
query_filter (Filter): filter with at least paging set
Returns:
dict: dictionary of entities using query and paging supplied
"""
hash_map = dict()
col = self.find(query_filter)
for i in col:
hash_map[i.code] = i
return hash_map
class DaoException(ApplicationException):
def __init__(self, message: str=None, **kwargs):
ApplicationException.__init__(self, message, **kwargs)