Files
jogobot-red/lib/mysqlred.py
Jonathan Golder bf8e47f916 Improve new status API
Make sure state changes are only detected as such by sqlalchemy if they
are real changes
2017-03-09 00:10:50 +01:00

334 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# mysqlred.py
#
# Copyright 2015 GOLDERWEB Jonathan Golder <jonathan@golderweb.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
"""
Provides interface classes for communication of redundances bot with mysql-db
"""
# Prefere using oursql then MySQLdb
try:
import oursql as mysqldb
except ImportError:
import MySQLdb as mysqldb
import atexit
import pywikibot
from pywikibot import config
import jogobot
from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL
url = URL( "mysql+oursql",
username=config.db_username,
password=config.db_password,
host=config.db_hostname,
port=config.db_port,
database=config.db_username + jogobot.config['db_suffix'] )
engine = create_engine(url, echo=True)
from sqlalchemy.ext.declarative import (
declarative_base, declared_attr, has_inherited_table )
Base = declarative_base()
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship, composite
from sqlalchemy.ext.mutable import MutableComposite, MutableSet
from sqlalchemy.orm.collections import attribute_mapped_collection
import sqlalchemy.types as types
Session = sessionmaker(bind=engine)
session = Session()
family = "dewpbeta"
class Mysql(object):
session = session
@declared_attr
def _tableprefix(cls):
return family + "_"
@declared_attr
def _tablesuffix(cls):
return "s"
@declared_attr
def __tablename__(cls):
if has_inherited_table(cls):
return None
prefix = family + "_"
name = cls.__name__[len("Mysql"):].lower()
suffix = "s"
return cls._tableprefix + name + cls._tablesuffix
def changedp(self):
return self.session.is_modified(self)
class MutableSet(MutableSet):
"""
Extended version of the mutable set for our states
"""
def has(self, item):
"""
Check if item is in set
@param item Item to check
"""
return item in self
def add(self, item):
"""
Extended add method, which only result in changed object if there is
really an item added.
@param item Item to add
"""
if not item in self:
super().add(item)
def discard(self, item):
"""
Wrapper for extended remove below
@param item Item to discard
"""
self.remove(item)
def remove(self, item, weak=True ):
"""
Extended remove method, which only results in changed object if there
is really an item removed. Additionally, combine remove and discard!
@param item Item to remove/discard
@param weak Set to false to use remove, else discard behavior
"""
if item in self:
if weak:
super().discard(item)
else:
super().remove(item)
class ColumnList( list, MutableComposite ):
"""
Combines multiple Colums into a list like object
"""
def __init__( self, *columns ):
"""
Wrapper to the list constructor deciding whether we have initialization
with individual params per article or with an iterable.
"""
# Individual params per article (from db), first one is a str
if isinstance( columns[0], str ) or \
isinstance( columns[0], MutableSet ) or columns[0] is None:
super().__init__( columns )
# Iterable articles list
else:
super().__init__( columns[0] )
def __setitem__(self, key, value):
"""
The MutableComposite class needs to be noticed about changes in our
component. So we tweak the setitem process.
"""
# set the item
super().__setitem__( key, value)
# alert all parents to the change
self.changed()
def __composite_values__(self):
"""
The Composite method needs to have this method to get the items for db.
"""
return self
class Status( types.TypeDecorator ):
impl = types.String
def process_bind_param(self, value, dialect):
"""
Returns status as commaseparated string (to save in DB)
@returns Raw status string
@rtype str
"""
if isinstance(value, MutableSet):
return ",".join( value )
elif isinstance(value, String ) or value is None:
return value
else:
raise ProgrammingError
def process_result_value(self, value, dialect):
"""
Sets status based on comma separated list
@param raw_status Commaseparated string of stati (from DB)
@type raw_status str
"""
if value:
return MutableSet( value.strip().split(","))
else:
return MutableSet([])
def copy(self, **kw):
return Status(self.impl.length)
class MysqlRedFam( Mysql, Base ):
famhash = Column( String(64), primary_key=True, unique=True )
__article0 = Column('article0', String(255), nullable=False )
__article1 = Column('article1', String(255), nullable=False )
__article2 = Column('article2', String(255), nullable=True )
__article3 = Column('article3', String(255), nullable=True )
__article4 = Column('article4', String(255), nullable=True )
__article5 = Column('article5', String(255), nullable=True )
__article6 = Column('article6', String(255), nullable=True )
__article7 = Column('article7', String(255), nullable=True )
__articlesList = composite(
ColumnList, __article0, __article1, __article2, __article3,
__article4, __article5, __article6, __article7 )
heading = Column( Text, nullable=False )
redpageid = Column(
Integer, ForeignKey( "dewpbeta_redpages.pageid" ), nullable=False )
beginning = Column( DateTime, nullable=False )
ending = Column( DateTime, nullable=True )
__status = Column( 'status', MutableSet.as_mutable(Status(255)), nullable=True )
__article0_status = Column(
'article0_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article1_status = Column(
'article1_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article2_status = Column(
'article2_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article3_status = Column(
'article3_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article4_status = Column(
'article4_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article5_status = Column(
'article5_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article6_status = Column(
'article6_status', MutableSet.as_mutable(Status(64)), nullable=True )
__article7_status = Column(
'article7_status', MutableSet.as_mutable(Status(64)), nullable=True )
__articlesStatus = composite(
ColumnList, __article0_status, __article1_status, __article2_status,
__article3_status, __article4_status, __article5_status,
__article6_status, __article7_status )
redpage = relationship( "RedPage", back_populates="redfams" )
@property
def articlesList(self):
"""
List of articles belonging to the redfam
"""
return self.__articlesList
@articlesList.setter
def articlesList(self, articlesList):
# Make sure to always have full length for complete overwrites
while( len(articlesList) < 8 ):
articlesList.append(None)
self.__articlesList = ColumnList(articlesList)
@property
def status( self ):
"""
Current fam status
"""
return self.__status
@status.setter
def status( self, status ):
if status:
self.__status = MutableSet( status )
else:
self.__status = MutableSet()
@property
def articlesStatus(self):
"""
List of status strings/sets for the articles of the redfam
"""
return self.__articlesStatus
@articlesStatus.setter
def articlesStatus(self, articlesStatus):
self.__articlesStatus = ColumnList(articlesStatus)
class MysqlRedPage( Mysql, Base ):
pageid = Column( Integer, unique=True, primary_key=True )
revid = Column( Integer, unique=True, nullable=False )
pagetitle = Column( String(255), nullable=False )
__status = Column( 'status', MutableSet.as_mutable(Status(255)), nullable=True )
redfams = relationship(
"MysqlRedFam", order_by=MysqlRedFam.famhash, back_populates="redpage",
collection_class=attribute_mapped_collection("famhash"))
@property
def status( self ):
"""
Current fam status
"""
return self.__status
@status.setter
def status( self, status ):
if status:
self.__status = MutableSet( status )
else:
self.__status = MutableSet()
Base.metadata.create_all(engine)
class MysqlRedError(Exception):
"""
Basic Exception class for this module
"""
pass
class MysqlRedConnectionError(MysqlRedError):
"""
Raised if there are Errors with Mysql-Connections
"""
pass