forked from gergely/calendar-social
		
	Add the AppState model
This allows setting application state during run time
This commit is contained in:
		
							
								
								
									
										60
									
								
								calsocial/app_state.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								calsocial/app_state.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
# Calendar.social
 | 
			
		||||
# Copyright (C) 2018  Gergely Polonkai
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""Metaclass for storing and accessing app state
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
def get_state_base(self, key):
 | 
			
		||||
    """Method to get a key from the state store
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    return self.__get_state__(key)
 | 
			
		||||
 | 
			
		||||
def set_state_base(self, key, value):
 | 
			
		||||
    """Method to set a key/value in the state store
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    self.__set_state__(key, str(value))
 | 
			
		||||
 | 
			
		||||
def set_default_base(self, key, value):
 | 
			
		||||
    """Method to set the default value of a key in the state store
 | 
			
		||||
 | 
			
		||||
    If key is already in the state store, this method is a no-op.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    self.__set_state_default__(key, str(value))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def app_state_base(klass):
 | 
			
		||||
    """Base class creator for AppStateMeta types
 | 
			
		||||
 | 
			
		||||
    :param klass: the class to extend
 | 
			
		||||
    :type klass: type
 | 
			
		||||
    :returns: a new class extending ``klass``
 | 
			
		||||
    :rtype: type
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Construct the meta class based on the metaclass of ``klass``
 | 
			
		||||
    metaclass = type(
 | 
			
		||||
        klass.__name__ + 'BaseMeta',
 | 
			
		||||
        (type(klass),),
 | 
			
		||||
        {
 | 
			
		||||
            '__getitem__': get_state_base,
 | 
			
		||||
            '__setitem__': set_state_base,
 | 
			
		||||
            'setdefault': set_default_base,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    return metaclass(klass.__name__ + 'Base', (klass,), {'__abstract__': True})
 | 
			
		||||
@@ -21,12 +21,14 @@ from datetime import datetime
 | 
			
		||||
from enum import Enum
 | 
			
		||||
from warnings import warn
 | 
			
		||||
 | 
			
		||||
from flask import current_app
 | 
			
		||||
from flask_babelex import lazy_gettext
 | 
			
		||||
from flask_security import UserMixin, RoleMixin
 | 
			
		||||
from flask_sqlalchemy import SQLAlchemy
 | 
			
		||||
from sqlalchemy.orm.exc import NoResultFound
 | 
			
		||||
from sqlalchemy_utils.types.choice import ChoiceType
 | 
			
		||||
 | 
			
		||||
from .app_state import app_state_base
 | 
			
		||||
from .cache import cache
 | 
			
		||||
from .utils import force_locale
 | 
			
		||||
 | 
			
		||||
@@ -206,7 +208,6 @@ class User(db.Model, UserMixin):
 | 
			
		||||
        If the user didn’t set a time zone yet, the application default is used.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        from flask import current_app
 | 
			
		||||
        from pytz import timezone
 | 
			
		||||
        from pytz.exceptions import UnknownTimeZoneError
 | 
			
		||||
 | 
			
		||||
@@ -789,3 +790,64 @@ class Response(db.Model):  # pylint: disable=too-few-public-methods
 | 
			
		||||
 | 
			
		||||
    #: The response itself
 | 
			
		||||
    response = db.Column(db.Enum(ResponseType), nullable=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppState(app_state_base(db.Model)):  # pylint: disable=too-few-public-methods
 | 
			
		||||
    """Database model for application state values
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __tablename__ = 'app_state'
 | 
			
		||||
 | 
			
		||||
    #: The environment that set this key
 | 
			
		||||
    env = db.Column(db.String(length=40), nullable=False, primary_key=True)
 | 
			
		||||
 | 
			
		||||
    #: The key
 | 
			
		||||
    key = db.Column(db.String(length=80), nullable=False, primary_key=True)
 | 
			
		||||
 | 
			
		||||
    #: The value of the key
 | 
			
		||||
    value = db.Column(db.Unicode(length=200), nullable=True)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def __get_state__(cls, key):
 | 
			
		||||
        try:
 | 
			
		||||
            record = cls.query \
 | 
			
		||||
                        .filter(cls.env == current_app.env) \
 | 
			
		||||
                        .filter(cls.key == key) \
 | 
			
		||||
                        .one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        return record.value
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def __set_state__(cls, key, value):
 | 
			
		||||
        try:
 | 
			
		||||
            record = cls.query \
 | 
			
		||||
                        .filter(cls.env == current_app.env) \
 | 
			
		||||
                        .filter(cls.key == key) \
 | 
			
		||||
                        .one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            record = cls(env=current_app.env, key=key)
 | 
			
		||||
 | 
			
		||||
        record.value = value
 | 
			
		||||
        db.session.add(record)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def __set_state_default__(cls, key, value):
 | 
			
		||||
        try:
 | 
			
		||||
            record = cls.query \
 | 
			
		||||
                        .filter(cls.env == current_app.env) \
 | 
			
		||||
                        .filter(cls.key == key) \
 | 
			
		||||
                        .one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        record = cls(env=current_app.env, key=key, value=value)
 | 
			
		||||
        db.session.add(record)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return f'<AppState {self.env}:{self.key}="{self.value}"'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								tests/test_app_state.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								tests/test_app_state.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
# Calendar.social
 | 
			
		||||
# Copyright (C) 2018  Gergely Polonkai
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
def test_app_state_set(database):
 | 
			
		||||
    from calsocial.models import db, AppState
 | 
			
		||||
 | 
			
		||||
    AppState['test'] = 'value'
 | 
			
		||||
 | 
			
		||||
    state = AppState.query \
 | 
			
		||||
                    .filter(AppState.env == 'testing') \
 | 
			
		||||
                    .filter(AppState.key == 'test') \
 | 
			
		||||
                    .one()
 | 
			
		||||
 | 
			
		||||
    assert state.value == 'value'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_app_state_get(database):
 | 
			
		||||
    from calsocial.models import db, AppState
 | 
			
		||||
 | 
			
		||||
    state = AppState(env='testing', key='test', value='value')
 | 
			
		||||
    db.session.add(state)
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
 | 
			
		||||
    assert AppState['test'] == 'value'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_app_state_setdefault(database):
 | 
			
		||||
    from calsocial.models import AppState
 | 
			
		||||
 | 
			
		||||
    AppState['test'] = 'value'
 | 
			
		||||
    AppState.setdefault('test', 'new value')
 | 
			
		||||
 | 
			
		||||
    assert AppState['test'] == 'value'
 | 
			
		||||
 | 
			
		||||
    AppState.setdefault('other_test', 'value')
 | 
			
		||||
    assert AppState['other_test'] == 'value'
 | 
			
		||||
		Reference in New Issue
	
	Block a user