【database_operator.py】
import os, re
from importlib.machinery import SourceFileLoader
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.declarative.api import DeclarativeMeta
from .exceptions import DataBaseOperationException
from .utility import Utility
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.AL32UTF8'
def error_handle(func):
def wrapper(database_operator, *args, **kwargs):
try:
return func(database_operator, *args, **kwargs)
except SQLAlchemyError as e:
database_operator.session_provider.rollback()
raise e
finally:
database_operator.session_provider.close()
return wrapper
class DataOperatorSingleton(type):
def __init__(cls, name, bases, dict):
super(DataOperatorSingleton, cls).__init__(name, bases, dict)
cls.__instance = None
def __call__(cls, *args, **kwargs):
_key = str(args) + str(kwargs)
if cls.__instance is None:
cls.__instance = {
_key: super(DataOperatorSingleton, cls).__call__(*args, **kwargs)
}
elif _key not in cls.__instance:
cls.__instance.update({
_key: super(DataOperatorSingleton, cls).__call__(*args, **kwargs)
})
return cls.__instance.get(_key)
class DataBaseOperator(object, metaclass=DataOperatorSingleton):
def __init__(self, model_path_str, env='qa', split_tb_hook=None, split_db_hook=None):
self.__env = env
self.__table_cls = None
self.session_provider = None
self.__db_hook = None
self.__tb_hook = None
self.__is_select = False
self.utl = Utility()
db_model = re.search(r'(\w+)\.py$', model_path_str).group(1)
try:
load_path = self.utl.intersection_of_path(model_path_str)
self.__module = SourceFileLoader(db_model, load_path).load_module()
except ModuleNotFoundError:
raise ModuleNotFoundError('The "{0}" mapping classes has not yet been generated!').format(db_model)
self.__connect_info = self.__module.CONNECT_INFO
self.__database_type = self.__module.CONNECT_INFO['connect_tag']
if not self.__db_hook:
self._build_session_provider()
def __get_table_cls(self, filter_d):
for cls_name in self.__module.__dict__.keys():
if re.match(r'^(t_)?{0}(_view)?$'.format(self.__org_table_name), self.utl.camel_to_underline(cls_name)):
self.__table_cls = getattr(self.__module, cls_name)
break
else:
continue
if self.__table_cls is None:
raise DataBaseOperationException('The {} table class is not definition!'.format(self.__org_table_name))
def _build_session_provider(self):
if self.__database_type == 'mysql':
connect_string = 'mysql+pymysql://{usr}:{pwd}@{url}/{db}?charset=utf8'.format(**self.__connect_info[self.__env])
parameters = {"echo": False}
engine = create_engine(connect_string, **parameters)
session = sessionmaker(bind=engine)
self.session_provider = session()
@error_handle
def __get_table_row(self, order_by_column_name=None, result_mode='all', limit=10, **filters):
self.__is_select = True
self.__get_table_cls(filters)
self.session_provider.commit() #强制刷新缓存
sql_query = self.session_provider.query(self.__table_cls).filter_by(**filters)
if order_by_column_name:
order_by_mode, order_by_column = order_by_column_name.split('_', 1)
order_by_func = getattr(sqlalchemy, order_by_mode)
sql_query = sql_query.order_by(order_by_func(order_by_column))
return getattr(sql_query.limit(limit), result_mode)() if result_mode == 'all' else getattr(sql_query, result_mode)()
@error_handle
def __update_table_row(self, update_dict, **filters):
if not isinstance(update_dict, dict):
raise Exception('Update statement %s is wrong. ' % update_dict)
self.__is_select = False
self.__get_table_cls(filters)
self.session_provider.query(self.__table_cls).filter_by(**filters).update(update_dict)
self.session_provider.commit()
@error_handle
def __delete_table_row(self, **filters):
self.__is_select = False
self.__get_table_cls(filters)
if isinstance(self.__table_cls, sqlalchemy.Table):
sql_s = ''
_keys = list(filters.keys())
for i in range(len(_keys)):
if _keys[i] == _keys[-1]:
sql_s += '{}={}'.format(_keys[i], filters[_keys[i]])
else:
sql_s += '{}={} and '.format(_keys[i], filters[_keys[i]])
del_st = self.__table_cls.delete().where(sql_s)
self.session_provider.execute(del_st)
self.session_provider.commit()
else:
rows = self.session_provider.query(self.__table_cls).filter_by(**filters).all()
if rows is None:
raise SQLAlchemyError('Cannot be found in %s table conditions for %s' % (self.__table_cls.__tablename__, filters))
else:
for row in rows:
self.session_provider.delete(row)
self.session_provider.commit()
@error_handle
def __add_table_row(self, **insert_dict):
self.__is_select = False
self.__get_table_cls(insert_dict)
if not isinstance(insert_dict, dict):
raise Exception('Add statement %s is wrong.' % insert_dict)
if isinstance(self.__table_cls, sqlalchemy.Table):
ins = self.__table_cls.insert().values(**insert_dict)
self.session_provider.execute(ins)
else:
self.session_provider.add(self.__table_cls(**insert_dict))
self.session_provider.commit()
def __getattr__(self, item):
ghost_func = re.search(r'(^get_|^update_|^add_|^delete_)(\w+$)', item)
if ghost_func:
self.__org_table_name = ghost_func.group(2)
if 'get' in item:
return self.__get_table_row
elif 'update' in item:
return self.__update_table_row
elif 'add' in item:
return self.__add_table_row
elif 'delete' in item:
return self.__delete_table_row
else:
raise AttributeError(" '%s' object has no attribute '%s'" % (self.__class__.__name__, item))
【exceptions.py】
class DataPatternWarning(Warning):
pass
class VerifyWarning(Warning):
pass
class DataBaseOperationException(Exception):
pass
【utility.py】
import time, collections, inspect, os, imaplib, sys, hashlib, re
from faker import Factory
from functools import wraps
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.__instance = None
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls.__instance
class Utility(object, metaclass=Singleton):
def __init__(self):
self.fake_cn = Factory.create('zh_CN')
self.fake_en = Factory.create()
@staticmethod
def camel_to_underline(camel_format):
'''
驼峰格式转下划线格式
:param camel_format:
:return:
'''
underline_format = ''
if isinstance(camel_format, str):
for i in range(len(camel_format)):
underline_format += (camel_format[i].isupper() and i !=0) and '_' + camel_format[i].lower() or camel_format[i].lower()
return underline_format
@staticmethod
def intersection_of_path(file_path):
'''
拼接绝对路径的文件路径,方便文件的读取
:param file_path:
:return:
'''
relative_list = os.path.normpath(file_path).split(os.sep)
sys_path_now_list = os.path.dirname(__file__).split(os.sep)[1:]
for i in range(1, len(sys_path_now_list)):
list_path = ''.join(['/%s' % y for y in sys_path_now_list[:-i]])
sys_path_dirs = os.listdir(list_path)
if relative_list[0] in sys_path_dirs:
return os.path.join(list_path, file_path)
if __name__ == '__main__':
print ( '\n\.')
[account.py]
from sqlalchemy import String, Column, DECIMAL, Date, DateTime, TIMESTAMP, Table, Text, text
from sqlalchemy.dialects.mysql import BIGINT, INTEGER, SMALLINT, VARCHAR, DATETIME
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metedata = Base.metadata
CONNECT_INFO = {'connect_tag': 'mysql',
'qa': {'usr': 'root', 'pwd': '123', 'url': 'localhost:3306', 'db': 'account'}}
class FlaskerTbOne(Base):
__tablename__ = 'flasker_tb_one'
id = Column(INTEGER(4), primary_key=True)
title = Column(String(255), nullable=False)
text = Column(String(255), nullable=False)
[style.css]
body { font-family: sans-serif; backgroud: #eee; }
a, h1, h2 { color: #377BA8; }
h1, h2 { font-family: 'Georgia', serif; margin:0; }
h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl { font-weight: bold; }
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
margin-bottom: 1em; background: #fafafa; }
.flash { background: #CEE5F5; padding: 0.5em;
border: 1px solid #AACBE2; }
.error { background: #F0D6D6; padding: 0.5em; }
【layout.html】
<!DOCTYPE html>
<title>Flaskr</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<div class="page">
<h1>Flaskr</h1>
<div class="metanav">
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in </a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>
[login.html]
{% extends 'layout.html' %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class="error"><strong>Error:</strong>{{ error }}{% endif %}
<form action="{{ url_for('login') }}" method="post">
<dl>
<dt>Username:
<dd><input type="text" name="username">
<dt>Password:
<dd><input type="password" name="password">
<dd><input type="submit" value="Login">
</dl>
</form>
{% endblock %}
[show_entities.html]
{% extends 'layout.html' %}
{% block body %}
{% if session.logged_in %}
<form action="{{ url_for('add_entry') }}" method="post" class="add-entry">
<dl>
<dt>Title:
<dd><input type="text" size="30" name="title">
<dt>Text:
<dd><textarea name="text" rows="5" cols="40"></textarea>
<dd><input type="submit" value="Share">
</dl>
</form>
{% endif %}
<ul class="entries">
{% for entry in entries %}
<li><h2>{{ entry.title }}}</h2>{{ entry.text|safe }}
{% else %}
<li><em>Unbelievable. No entries here so far </em>
{% endfor %}
</ul>
{% endblock %}
[conftest.py]
import pytest
from flasker.kernel.database_operator import DataBaseOperator
DB_MODELS_PATH = 'untitled3/flasker/models/{}.py'
def pytest_addoption(parser):
parser.addoption('--test_env', action='store', default='qa', dest='test_env', help='my option: qa or dev')
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
setattr(item, 'rep_' + rep.when, rep)
@pytest.fixture(scope='session')
def environment(request):
return request.config.getoption('test_env')
@pytest.fixture(scope='session')
def account_data(environment):
return DataBaseOperator(DB_MODELS_PATH.format('account'), environment)
[flasker_one_test.py]
import os
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
import pytest
class TestFlasker(object):
@pytest.mark.basic_wxue_case
def test_one(self, account_data):
account_d = account_data.get_flasker_tb_one(result_mode='one', id=1)
print ('**************account.text=***************' + account_d.text)
assert 0
[flaskr.py]
import os
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
import pytest
from flasker.kernel.database_operator import DataBaseOperator
from flasker.models.account import FlaskerTbOne
DB_MODELS_PATH = 'untitled3/flasker/models/{}.py'
app = Flask(__name__)
def get_db():
db = getattr(g,'_database',None)
if db is None:
db = DataBaseOperator(DB_MODELS_PATH.format('account'))
g._database = db # 存入Flask.g对象中
return db
@app.before_request
def before_request():
get_db()
@app.route('/')
def show_entries():
#account_d = db.session_provider.query(FlaskerTbOne).all()
account_d = g._database.get_flasker_tb_one(result_mode='all')
entries = [dict(title=row.title, text=row.text) for row in account_d]
return render_template('show_entries.html', entries=entries)
@app.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
g._database.add_flasker_tb_one(title=request.form['title'], text=request.form['text'])
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in ')
return redirect(url_for('show_entries'))
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
if __name__ == '__main__':
app.debug = True
app.config.from_object('settings.FlaskSetting')
app.run()
[settings.py]
class FlaskSetting:
USERNAME = 'admin'
PASSWORD = '123'