├── __init__.py ├── config ├── __init__.py ├── logging.conf └── settings.py ├── src ├── misc │ ├── __init__.py │ ├── exts.py │ ├── parse.py │ ├── render.py │ └── decorator.py ├── views │ ├── __init__.py │ └── v1 │ │ ├── __init__.py │ │ └── project.py ├── validations │ └── __init__.py ├── models │ ├── __init__.py │ ├── project.py │ ├── users.py │ └── basemodels.py ├── business │ ├── __init__.py │ └── project.py └── __init__.py ├── README.md ├── test.py ├── LICENSE ├── run.py ├── requirements.txt ├── manage.py └── Pipfile /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/misc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/validations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/models/__init__.py: -------------------------------------------------------------------------------- 1 | from src.models.project import Project 2 | -------------------------------------------------------------------------------- /src/views/v1/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /src/business/__init__.py: -------------------------------------------------------------------------------- 1 | from src.business.project import ProjectBusiness 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyComs 2 | 一个基于Flask 1.0 的后端服务框架,安装好必要的包后可直接进入后段服务开发,是一个项目级纯净框架。 3 | -------------------------------------------------------------------------------- /src/misc/exts.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | from flask_marshmallow import Marshmallow 3 | 4 | db = SQLAlchemy() 5 | ma = Marshmallow() -------------------------------------------------------------------------------- /src/models/project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from src.models.basemodels import EntityWithNameModel 5 | from src.misc.exts import db 6 | 7 | 8 | class Project(EntityWithNameModel): 9 | ACTIVE = 1 10 | DISABLE = 0 11 | 12 | description = db.Column(db.String(1000), nullable=True) 13 | status = db.Column(db.Integer, default=ACTIVE) 14 | weight = db.Column(db.Integer, default=1) 15 | logo = db.Column(db.String(2048), comment="Project Logo") 16 | ext = db.Column(db.Text()) 17 | -------------------------------------------------------------------------------- /src/views/v1/project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import Blueprint, request 5 | from src.misc.parse import parse_list_args, parse_json_form 6 | from src.misc.render import json_list_render, json_detail_render 7 | 8 | from src.business import ProjectBusiness 9 | 10 | project = Blueprint('project', __name__) 11 | 12 | 13 | @project.route('/', methods=['GET']) 14 | def project_query_handler(): 15 | limit, offset = parse_list_args() 16 | data = ProjectBusiness.query_all(limit, offset) 17 | return json_list_render(0, data, limit, offset) 18 | -------------------------------------------------------------------------------- /src/models/users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from src.misc.exts import db 5 | from src.models.basemodels import EntityWithNameModel 6 | 7 | 8 | class User(EntityWithNameModel): 9 | ACTIVE = 1 10 | DISABLE = 0 11 | 12 | nickname = db.Column(db.String(100), nullable=True) 13 | password = db.Column(db.String(100), nullable=False) 14 | email = db.Column(db.String(100)) 15 | telephone = db.Column(db.String(30)) 16 | weight = db.Column(db.Integer, default=1) 17 | status = db.Column(db.Integer, default=ACTIVE) 18 | ext = db.Column(db.Text()) 19 | -------------------------------------------------------------------------------- /config/logging.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root, pycoms 3 | 4 | [handlers] 5 | keys=consoleHandler, rotateFileHandler 6 | 7 | [formatters] 8 | keys=simpleFormatter 9 | 10 | [formatter_simpleFormatter] 11 | format=[%(asctime)s][%(levelname)s][%(name)s][%(funcName)s]%(message)s 12 | 13 | [logger_root] 14 | level=DEBUG 15 | handlers=consoleHandler, rotateFileHandler 16 | 17 | [logger_pycoms] 18 | level=DEBUG 19 | handlers=consoleHandler, rotateFileHandler 20 | qualname=pycoms 21 | propagate=0 22 | 23 | [handler_consoleHandler] 24 | class=StreamHandler 25 | level=DEBUG 26 | formatter=simpleFormatter 27 | args=(sys.stdout,) 28 | 29 | [handler_rotateFileHandler] 30 | class=handlers.RotatingFileHandler 31 | level=DEBUG 32 | formatter=simpleFormatter 33 | args=('log/app.log', 'a', 1024*1024, 9) -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | class ABC(object): 5 | 6 | def foo1(self): 7 | print("foo1", self) 8 | 9 | def foo2(cls): 10 | print("foo2", cls) 11 | 12 | @classmethod 13 | def foo3(cls): 14 | print("foo3", cls) 15 | 16 | @staticmethod 17 | def foo4(self): 18 | print('foo4', self) 19 | 20 | @staticmethod 21 | def foo5(cls): 22 | print('foo5', cls) 23 | 24 | 25 | if __name__ == "__main__": 26 | # abc = ABC() 27 | # abc.foo1() 28 | # ABC.foo1(abc) 29 | # print('=========') 30 | # abc.foo2() 31 | # print('=========') 32 | # abc.foo3() 33 | # abc.foo4(abc) 34 | # abc.foo5(abc) 35 | ABC.foo4("xxxx") 36 | ABC.foo1() 37 | ABC.foo3() 38 | -------------------------------------------------------------------------------- /src/models/basemodels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from src.misc.exts import db 5 | from datetime import datetime 6 | 7 | 8 | class EntityModel(db.Model): 9 | __abstract__ = True 10 | 11 | __table_args__ = ( 12 | dict( 13 | mysql_engine='InnoDB', 14 | mysql_charset='utf8' 15 | ) 16 | ) 17 | 18 | id = db.Column(db.Integer, primary_key=True) 19 | 20 | creation_time = db.Column(db.DateTime, default=datetime.now()) 21 | modified_time = db.Column(db.TIMESTAMP, nullable=False, default=db.func.current_timestamp()) 22 | 23 | @classmethod 24 | def gets(cls, ids): 25 | return cls.query.filter(cls.id.in_(ids)).all() 26 | 27 | 28 | class EntityWithNameModel(EntityModel): 29 | __abstract__ = True 30 | name = db.Column(db.String(100), nullable=False) 31 | 32 | def __repr__(self): 33 | return self.name.encode('utf-8') 34 | 35 | def __unicode__(self): 36 | return self.name 37 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from config import settings 5 | from src.views.v1.project import project 6 | from src.misc.exts import db, ma 7 | from flask import Flask 8 | 9 | DEFAULT_APP_NAME = settings.PROJECT_NAME 10 | DEFAULT_MODULES = ( 11 | (project, '/project'), 12 | ) 13 | 14 | 15 | def create_app(app_name=None, modules=None): 16 | if app_name is None: 17 | app_name = DEFAULT_APP_NAME 18 | if modules is None: 19 | modules = DEFAULT_MODULES 20 | 21 | app = Flask(app_name) 22 | configure_conf(app) 23 | configure_exts(app) 24 | configure_modules(app, modules) 25 | return app 26 | 27 | 28 | def configure_conf(app): 29 | app.config.from_pyfile('config/settings.py') 30 | 31 | 32 | def configure_modules(app, modules): 33 | for module, url_prefix in modules: 34 | app.register_blueprint(module, url_prefix='/v1{}'.format(url_prefix)) 35 | 36 | 37 | def configure_exts(app): 38 | db.init_app(app) 39 | ma.init_app(app) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 上海钧漫元泷网络科技有限公司 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import multiprocessing 4 | 5 | from gunicorn.app.base import BaseApplication 6 | from six import iteritems 7 | from src import create_app 8 | from flask import Flask 9 | 10 | 11 | 12 | def number_of_workers(): 13 | return multiprocessing.cpu_count() * 2 + 1 14 | 15 | 16 | options = { 17 | 'bind': '{}:{}'.format('0.0.0.0', '15942'), 18 | 'workers': number_of_workers(), 19 | 'worker_class': 'gevent', 20 | 'timeout': '1800', 21 | 'loglevel': 'debug', 22 | 'debug': True, 23 | } 24 | 25 | 26 | class Application(BaseApplication): 27 | 28 | def __init__(self, app, options=None): 29 | self.options = options or {} 30 | self.application = app 31 | super(Application, self).__init__() 32 | 33 | def load_config(self): 34 | config = dict([(key, value) for key, value in iteritems(self.options) 35 | if key in self.cfg.settings and value is not None]) 36 | for key, value in iteritems(config): 37 | self.cfg.set(key.lower(), value) 38 | 39 | def load(self): 40 | return self.application 41 | 42 | def main(): 43 | Application(create_app(), options).run() 44 | 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==0.24.0 2 | backports.ssl-match-hostname==3.5.0.1 3 | bcrypt==3.1.7 4 | cached-property==1.5.1 5 | certifi==2017.7.27.1 6 | cffi==1.12.3 7 | chardet==3.0.4 8 | Cheetah==2.4.4 9 | Click==7.0 10 | cloud-init==0.7.6 11 | configobj==4.7.2 12 | cryptography==2.7 13 | decorator==3.4.0 14 | docker==3.7.3 15 | docker-pycreds==0.4.0 16 | dockerpty==0.4.1 17 | docopt==0.6.2 18 | enum34==1.1.6 19 | Flask==1.0.2 20 | functools32==3.2.3.post2 21 | gevent==1.3.7 22 | greenlet==0.4.15 23 | idna==2.6 24 | iniparse==0.4 25 | ipaddress==1.0.16 26 | IPy==0.75 27 | itsdangerous==1.1.0 28 | Jinja2==2.10 29 | jsonpatch==1.16 30 | jsonpointer==1.12 31 | jsonschema==2.6.0 32 | kitchen==1.1.1 33 | locustio==0.9.0 34 | Markdown==2.6.9 35 | MarkupSafe==1.0 36 | meld3==0.6.10 37 | msgpack==0.5.6 38 | MySQL-python==1.2.5 39 | oauth==1.0.1 40 | paramiko==2.6.0 41 | perf==0.1 42 | pipenv==2018.11.26 43 | policycoreutils-default-encoding==0.1 44 | prettytable==0.7.2 45 | pycparser==2.19 46 | pycurl==7.19.0 47 | pygobject==3.22.0 48 | pygpgme==0.3 49 | pyliblzma==0.5.3 50 | PyNaCl==1.3.0 51 | python-linux-procfs==0.4.9 52 | pyudev==0.15 53 | pyxattr==0.5.1 54 | PyYAML==3.12 55 | pyzmq==17.1.2 56 | requests==2.18.4 57 | schedutils==0.4 58 | seobject==0.1 59 | sepolicy==1.1 60 | six==1.11.0 61 | slip==0.4.0 62 | slip.dbus==0.4.0 63 | supervisor==3.1.4 64 | texttable==0.9.1 65 | typing==3.6.6 66 | urlgrabber==3.10 67 | urllib3==1.22 68 | virtualenv==16.1.0 69 | virtualenv-clone==0.4.0 70 | websocket-client==0.56.0 71 | Werkzeug==0.14.1 72 | yum-metadata-parser==1.1.4 -------------------------------------------------------------------------------- /src/misc/parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import request 5 | from hashlib import md5 6 | 7 | from config.settings import SALT, YML_JSON, logger 8 | 9 | parse_pwd = lambda pwd: md5(SALT + pwd).hexdigest() 10 | 11 | 12 | def parse_list_args(): 13 | req_args = request.args 14 | try: 15 | limit = int(req_args.get('limit', 99999)) 16 | except Exception: 17 | limit = 10 18 | try: 19 | offset = int(req_args.get('offset', 0)) 20 | except Exception: 21 | offset = 0 22 | # q = req_args.get('q', None) 23 | return limit, offset 24 | 25 | 26 | def parse_list_args2(): 27 | req_args = request.args 28 | try: 29 | page_size = req_args.get('page_size') 30 | if page_size: 31 | page_size = int(page_size) 32 | except Exception: 33 | page_size = 10 34 | 35 | try: 36 | page_index = req_args.get('page_index') 37 | if page_index: 38 | page_index = int(page_index) 39 | except Exception: 40 | page_index = 1 41 | return page_size, page_index 42 | 43 | 44 | def parse_json_form(name): 45 | form_json = YML_JSON.get(name) 46 | returnvalue = form_json.get('returnvalue') 47 | logger.info(request.json) 48 | return [request.json.get(x) for x in returnvalue] 49 | 50 | 51 | def parse_query_string(name): 52 | form_json = YML_JSON.get(name) 53 | returnvalue = form_json.get('returnvalue') 54 | logger.info(request.args) 55 | return [request.args.get(x) for x in returnvalue] 56 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from flask import current_app 6 | from src import create_app 7 | from src.misc.exts import db 8 | from src.models import Project 9 | from flask_script import Manager, Server, Shell 10 | 11 | manager = Manager(create_app) 12 | server = Server(host='0.0.0.0', port=15942, use_debugger=True) 13 | 14 | 15 | def make_shell_context(): 16 | return dict( 17 | app=current_app, 18 | db=db, 19 | Project=Project, 20 | ) 21 | 22 | 23 | manager.add_command('shell', Shell(make_context=make_shell_context)) 24 | manager.add_command('runserver', server) 25 | 26 | 27 | def dadd(inputs): 28 | for i in inputs: 29 | db.session.add(i) 30 | db.session.commit() 31 | 32 | @manager.command 33 | def createdb(): 34 | print ('开始创建数据库') 35 | db.create_all() 36 | print('创建数据库成功') 37 | print('开始初始化数据') 38 | 39 | # 固有配置 40 | 41 | project1 = Project( 42 | name='萌推', description='电商项目', logo='http://tcloud-static.ywopt.com/static/468e0de0-8c92-4843-b456-2751208a44e4.png' 43 | ) 44 | project2 = Project( 45 | name='实惠喵', description='返利网',logo='http://tcloud-static.ywopt.com/static/6d2600e1-4928-4168-893d-00548460dd3f.png' 46 | ) 47 | dadd([project1,project2]) 48 | print('数据初始化完成') 49 | 50 | @manager.command 51 | def dropdb(): 52 | print('开始删除数据库') 53 | db.drop_all() 54 | print('数据库删除完成') 55 | 56 | @manager.command 57 | def initdb(): 58 | dropdb() 59 | createdb() 60 | 61 | if __name__ == '__main__': 62 | manager.run() 63 | -------------------------------------------------------------------------------- /src/misc/render.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | 6 | from flask import jsonify 7 | from flask import make_response 8 | from config.settings import MSG_MAP 9 | 10 | 11 | def _render(resp): 12 | response = make_response(jsonify(resp)) 13 | # response.headers["Access-Control-Allow-Origin"] = "*" 14 | return response 15 | 16 | 17 | def json_list_render(code, data, limit, offset, message=None): 18 | if message is None: 19 | message = MSG_MAP.get(code) 20 | resp = dict( 21 | code=code, limit=limit, offset=offset, message=message, data=data) 22 | return _render(resp) 23 | 24 | 25 | def json_list_render2(code, data, page_size, page_index, total, message=None): 26 | if message is None: 27 | message = MSG_MAP.get(code) 28 | resp = dict( 29 | code=code, page_size=page_size, page_index=page_index, total=total, message=message, data=data) 30 | return _render(resp) 31 | 32 | 33 | def json_detail_render(code, data=[], message=None): 34 | if message is None: 35 | message = MSG_MAP.get(code) 36 | resp = dict(code=code, message=message, data=data) 37 | return _render(resp) 38 | 39 | 40 | def json_token_render(code, token, message=None): 41 | if message is None: 42 | message = MSG_MAP.get(code) 43 | resp = dict(code=code, token=token, message=message) 44 | return _render(resp) 45 | 46 | 47 | def json_detail_render_sse(code, data=[], message=None): 48 | if message is None: 49 | message = MSG_MAP.get(code) 50 | resp = dict(code=code, message=message, data=data) 51 | return json.dumps(resp) 52 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from os import listdir 5 | from os.path import dirname, join, abspath, splitext 6 | 7 | import yaml 8 | import logging.config 9 | 10 | SECRET = '*>_<*' 11 | ALGORITHM = 'HS256' 12 | SESSION_TIME = 60 * 60 13 | 14 | PROJECT_NAME = 'pycoms' 15 | 16 | SQLALCHEMY_DATABASE_URI = '' 17 | 18 | SQLALCHEMY_TRACK_MODIFICATIONS = True 19 | SCHEDULER_API_ENABLED = True 20 | 21 | PROJECT_PATH = dirname(dirname(abspath(__file__))) 22 | 23 | SALT = "pycoms" 24 | VALIDATE_YML_PATH = join(PROJECT_PATH, 'src', 'validations') 25 | 26 | YML_JSON = {} 27 | for fi in listdir(VALIDATE_YML_PATH): 28 | if splitext(fi)[-1] !='.yml': 29 | continue 30 | with open(join(VALIDATE_YML_PATH, fi),'rb') as f: 31 | YML_JSON.update(yaml.load(f.read())) 32 | 33 | logging.config.fileConfig(join(PROJECT_PATH,'config', 'logging.conf')) 34 | logger = logging.getLogger('pycoms') 35 | 36 | MSG_MAP = { 37 | 0: 'ok', 38 | 39 | 101: 'can not find object', 40 | 102: 'save object error', 41 | 103: 'duplicate data', 42 | 104: 'can not create object', 43 | 105: 'remove failed', 44 | 106: 'operate failed', 45 | 108: 'permission denied', 46 | 109: 'project permission denied', 47 | 48 | 201: 'field required', 49 | 202: 'field length error', 50 | 51 | 301: 'password wrong', 52 | 303: 'username or password wrong', 53 | 54 | 403: 'not allowed', 55 | 410: 'auth expired', 56 | 411: 'auth error', 57 | 412: 'not login', 58 | 413: 'username is not exist or password error', 59 | 414: 'invalid data', 60 | } 61 | 62 | try: 63 | from config.local_settings import * 64 | except Exception as e: 65 | pass 66 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | asn1crypto = "==0.24.0" 10 | bcrypt = "==3.1.7" 11 | cached-property = "==1.5.1" 12 | certifi = "==2017.7.27.1" 13 | cffi = "==1.12.3" 14 | chardet = "==3.0.4" 15 | click = "==7.0" 16 | cloud-init = "==0.7.6" 17 | configobj = "==4.7.2" 18 | cryptography = "==2.7" 19 | decorator = "==3.4.0" 20 | docker = "==3.7.3" 21 | docker-pycreds = "==0.4.0" 22 | dockerpty = "==0.4.1" 23 | docopt = "==0.6.2" 24 | enum34 = "==1.1.6" 25 | functools32 = "==3.2.3.post2" 26 | gevent = "==1.3.7" 27 | greenlet = "==0.4.15" 28 | idna = "==2.6" 29 | iniparse = "==0.4" 30 | ipaddress = "==1.0.16" 31 | itsdangerous = "==1.1.0" 32 | jsonpatch = "==1.16" 33 | jsonpointer = "==1.12" 34 | jsonschema = "==2.6.0" 35 | kitchen = "==1.1.1" 36 | locustio = "==0.9.0" 37 | meld3 = "==0.6.10" 38 | msgpack = "==0.5.6" 39 | oauth = "==1.0.1" 40 | paramiko = "==2.6.0" 41 | perf = "==0.1" 42 | pipenv = "==2018.11.26" 43 | policycoreutils-default-encoding = "==0.1" 44 | pycparser = "==2.19" 45 | pycurl = "==7.19.0" 46 | pygpgme = "==0.3" 47 | pyliblzma = "==0.5.3" 48 | python-linux-procfs = "==0.4.9" 49 | pyudev = "==0.15" 50 | pyxattr = "==0.5.1" 51 | pyzmq = "==17.1.2" 52 | requests = "==2.18.4" 53 | schedutils = "==0.4" 54 | seobject = "==0.1" 55 | sepolicy = "==1.1" 56 | six = "==1.11.0" 57 | slip-dbus = "==0.4.0" 58 | supervisor = "==3.1.4" 59 | texttable = "==0.9.1" 60 | typing = "==3.6.6" 61 | urlgrabber = "==3.10" 62 | urllib3 = "==1.22" 63 | virtualenv = "==16.1.0" 64 | virtualenv-clone = "==0.4.0" 65 | yum-metadata-parser = "==1.1.4" 66 | "backports.ssl_match_hostname" = "==3.5.0.1" 67 | Cheetah = "==2.4.4" 68 | Flask = "==1.0.2" 69 | IPy = "==0.75" 70 | Jinja2 = "==2.10" 71 | Markdown = "==2.6.9" 72 | MarkupSafe = "==1.0" 73 | MySQL-python = "==1.2.5" 74 | PrettyTable = "==0.7.2" 75 | PyGObject = "==3.22.0" 76 | PyNaCl = "==1.3.0" 77 | PyYAML = "==3.12" 78 | SLIP = "==0.4.0" 79 | websocket_client = "==0.56.0" 80 | Werkzeug = "==0.14.1" 81 | 82 | [requires] 83 | python_version = "3.6" 84 | -------------------------------------------------------------------------------- /src/business/project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from src.models import Project 5 | from src.misc.decorator import transfer2jsonwithoutset , slicejson 6 | from sqlalchemy import desc 7 | from src.misc.exts import db 8 | from flask import request 9 | 10 | 11 | class ProjectBusiness(object): 12 | @classmethod 13 | def _query(cls): 14 | return Project.query \ 15 | .add_columns( 16 | Project.id.label('id'), 17 | Project.name.label('name'), 18 | Project.description.label('description'), 19 | Project.status.label('status'), 20 | Project.logo.label('logo'), 21 | Project.weight.label('weight'), 22 | ) 23 | 24 | @classmethod 25 | @transfer2jsonwithoutset('?id|!name|!description|!status|!weight|!logo') 26 | def query_all(cls, limit, offset): 27 | return cls._query().filter(Project.status == Project.ACTIVE).order_by(desc(Project.weight)).order_by( 28 | Project.id).all() 29 | 30 | @classmethod 31 | @transfer2jsonwithoutset('?id|!name|!description|!status|!weight|!logo') 32 | def query_by_id(cls, id): 33 | return cls._query().filter(Project.id == id, Project.status == Project.ACTIVE).order_by( 34 | desc(Project.weight)).all() 35 | 36 | @classmethod 37 | def add(cls, name, description, logo): 38 | try: 39 | p = Project( 40 | name=name, 41 | description=description, 42 | logo=logo, 43 | ) 44 | db.session.add(p) 45 | db.session.commit() 46 | return 0, None 47 | except Exception as e: 48 | return 102, str(e) 49 | 50 | @classmethod 51 | def update(cls, id, name, description, weight, logo): 52 | project = Project.query.get(id) 53 | if project.status == Project.ACTIVE: 54 | try: 55 | project.name = name 56 | project.description = description 57 | project.weight = weight 58 | project.logo = logo 59 | except Exception as e: 60 | db.session.rollback() 61 | return 102, str(e) 62 | 63 | @classmethod 64 | def delete(cls, id): 65 | project = Project.query.get(id) 66 | project.status = Project.DISABLE 67 | db.session.add(project) 68 | db.session.commit() 69 | return 0 70 | 71 | -------------------------------------------------------------------------------- /src/misc/decorator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from copy import deepcopy 5 | from functools import wraps 6 | from flask import request 7 | from src.misc.render import json_detail_render 8 | from config.settings import YML_JSON, logger 9 | import datetime,json 10 | 11 | def transfer(column): 12 | def dec(func): 13 | @wraps(func) 14 | def _(*args, **kwargs): 15 | tmap = { 16 | '?': "", 17 | '!': "", 18 | '@': [], 19 | '#': {}, 20 | '$': False, 21 | } 22 | result = func(*args, **kwargs) 23 | if not isinstance(result, list): 24 | raise('should be a list') 25 | 26 | cols = [i.strip() for i in column.split('|')] 27 | pure_cols = map(lambda x : x[1:], cols) 28 | template = {col[1:]: tmap.get(col[0]) for col in cols} 29 | key_col = filter(lambda x: '?' in x, cols)[0][1:] 30 | tdata = [{item: getattr(res, item) for item in pure_cols} for res in result] 31 | 32 | data = [] 33 | 34 | for d in tdata: 35 | tpl = deepcopy(template) 36 | for k, v in d.iteritems(): 37 | if isinstance(tpl[k], basestring) and v: 38 | tpl[k] = v 39 | elif isinstance(tpl[k], list) and v: 40 | tlist = deepcopy(tpl[k]) 41 | tlist.append(v) 42 | tpl[k] = tlist 43 | elif isinstance(tpl[k], dict) and v: 44 | tdict = deepcopy(tpl[k]) 45 | tdict.update(v) 46 | tpl[k] = tdict 47 | elif isinstance(tpl[k], bool): 48 | t = deepcopy(tpl[k]) 49 | t = bool(v) 50 | tpl[k] = t 51 | 52 | data.append(tpl) 53 | return data 54 | return _ 55 | return dec 56 | 57 | 58 | def transfer2json(column): 59 | """ 60 | ? : key 61 | ! : string 62 | @ : list 63 | # : dict 64 | $ : bool 65 | & : tuple 66 | """ 67 | 68 | def dec(func): 69 | @wraps(func) 70 | def _(*args, **kwargs): 71 | tmap = { 72 | '?': "", 73 | '!': "", 74 | '@': [], 75 | '#': {}, 76 | '$': False, 77 | '&': (), 78 | '~': ['~'], 79 | 80 | } 81 | result = func(*args, **kwargs) 82 | if not isinstance(result, list): 83 | raise('should be a list') 84 | cols = [i.strip() for i in column.split('|')] 85 | # key的list形式数据 86 | pure_cols = map(lambda x : x[1:], cols) 87 | # 键值对中给value赋值tmap 88 | template = {col[1:]: tmap.get(col[0]) for col in cols} 89 | key_col = filter(lambda x: '?' in x, cols)[0][1:] 90 | # 键值对中给value赋值数据库 91 | tdata = [{item: getattr(res, item) for item in pure_cols} for res in result] 92 | data = [] 93 | for d in tdata: 94 | fu = [i for i in data if i.get(key_col) == d.get(key_col)] 95 | if len(fu) == 0: 96 | tpl = deepcopy(template) 97 | for k,v in d.iteritems(): 98 | if isinstance(tpl[k], basestring) and v!=None: 99 | tpl[k] = v 100 | elif tpl[k]==['~']: 101 | tjlist = json.loads(v) if v else [] 102 | tpl[k] = tjlist 103 | elif isinstance(tpl[k], list) and v: 104 | tlist = deepcopy(tpl[k]) 105 | tlist.append(v) 106 | tpl[k] = tlist 107 | elif isinstance(tpl[k], dict) and v: 108 | tdict = deepcopy(tpl[k]) 109 | tdict.update(v) 110 | tpl[k] = tdict 111 | elif isinstance(tpl[k], bool): 112 | t = deepcopy(tpl[k]) 113 | t = bool(v) 114 | tpl[k] = t 115 | elif isinstance(tpl[k], tuple) and v: 116 | tlist = deepcopy(tpl[k]) 117 | tmp = [] 118 | tmp.append(v) 119 | tlist += tuple(tmp) 120 | tpl[k] = tlist 121 | 122 | data.append(tpl) 123 | else: 124 | fu = fu[0] 125 | for k,v in d.iteritems(): 126 | if isinstance(fu[k], basestring) and v: 127 | fu[k] = v 128 | elif isinstance(fu[k], list) and v: 129 | tlist = deepcopy(fu[k]) 130 | tlist.append(v) 131 | fu[k] = list(set(tlist)) 132 | fu[k].sort(key=tlist.index) 133 | elif isinstance(fu[k], dict) and v: 134 | tdict = deepcopy(fu[k]) 135 | tdict.update(v) 136 | fu[k] = tdict 137 | elif isinstance(tpl[k], bool): 138 | t = deepcopy(tpl[k]) 139 | t = bool(v) 140 | tpl[k] = t 141 | elif isinstance(fu[k], tuple) and v: 142 | tlist = deepcopy(fu[k]) 143 | tmp = [] 144 | tmp.append(v) 145 | tlist += tuple(tmp) 146 | fu[k] = tuple(tlist) 147 | return data 148 | return _ 149 | return dec 150 | 151 | def transfer2jsonwithoutset(column, ispagination=False): 152 | """ 153 | ? : key 154 | ! : string 155 | @ : list 156 | # : dict 157 | $ : bool 158 | & : tuple 159 | """ 160 | 161 | def dec(func): 162 | @wraps(func) 163 | def _(*args, **kwargs): 164 | t_map = { 165 | '?': "key", 166 | '!': "", 167 | '@': [], 168 | '#': {}, 169 | '$': False, 170 | '&': (), 171 | '~': ['~'], 172 | 173 | } 174 | count = 0 175 | if ispagination: 176 | results, count = func(*args, **kwargs) 177 | else: 178 | results = func(*args, **kwargs) 179 | if not isinstance(results, list): 180 | raise TypeError('should be a list') 181 | 182 | # 原始 键值 183 | origin_keys = [key.strip() for key in column.split('|')] 184 | # 键值和类型对应的 字典 185 | result_map = { 186 | key.strip()[1:]: t_map.get(key.strip()[0]) 187 | for key in column.split('|') 188 | } 189 | # 找到 ? 为前缀的键值作为 key 190 | key_of_data = list(filter(lambda x: '?' in x, origin_keys))[0][1:] 191 | # result dict 192 | results_dict = [{item: getattr(result, item) for item in result_map.keys()} for result in results] 193 | 194 | data = {} 195 | for result in results_dict: 196 | if result.get(key_of_data) in data.keys(): 197 | temp = data.get(result.get(key_of_data)) 198 | for key, value in result_map.items(): 199 | data_value = result.get(key) 200 | if isinstance(value, str) and data_value: 201 | temp[key] = data_value 202 | elif isinstance(value, list) and data_value: 203 | t_list = deepcopy(temp[key]) 204 | t_list.append(data_value) 205 | temp[key] = list(t_list) 206 | elif isinstance(value, dict) and data_value: 207 | tdict = deepcopy(temp[key]) 208 | tdict.update(data_value) 209 | temp[key] = tdict 210 | elif isinstance(value, bool): 211 | t = bool(data_value) 212 | temp[key] = t 213 | elif isinstance(value, tuple) and data_value: 214 | t_list = deepcopy(temp[key]) 215 | tmp = [data_value] 216 | t_list += tuple(tmp) 217 | temp[key] = tuple(t_list) 218 | else: 219 | temp = deepcopy(result_map) 220 | for key, value in result_map.items(): 221 | data_value = result.get(key) 222 | if isinstance(value, str) and data_value is not None: 223 | temp[key] = data_value 224 | elif value == ['~']: 225 | tjlist = json.loads(data_value) if data_value else [] 226 | temp[key] = tjlist 227 | elif isinstance(value, list) and data_value: 228 | temp[key] = [data_value] 229 | elif isinstance(value, dict) and data_value: 230 | temp[key] = {data_value} 231 | elif isinstance(value, bool): 232 | temp[key] = bool(data_value) 233 | elif isinstance(value, tuple) and data_value: 234 | temp[key] = tuple([data_value]) 235 | 236 | data.update({result.get(key_of_data): temp}) 237 | if ispagination: 238 | return list(data.values()), count 239 | else: 240 | return list(data.values()) 241 | 242 | return _ 243 | 244 | return dec 245 | 246 | def slicejson(settings): 247 | 248 | def _slicejson(ret): 249 | config = [setting.split('|') for setting in settings] 250 | for conf in config: 251 | na = [[dict(i) for i in map(lambda x: zip((conf[1],conf[2]), x), zip(r.get(conf[3]),r.get(conf[4])))] for r in ret] 252 | for index, item in enumerate(ret): 253 | for k in [conf[3], conf[4]]: 254 | del item[k] 255 | item[conf[0]] = na[index] 256 | return ret 257 | 258 | def wrapper(func): 259 | @wraps(func) 260 | def _(*args, **kwargs): 261 | ret = func(*args, **kwargs) 262 | return _slicejson(ret) 263 | return _ 264 | return wrapper 265 | 266 | 267 | def validation(validate_name = None): 268 | 269 | def validate_required(key, value): 270 | request_value = request.json.get(key) 271 | expect_value = value 272 | if request_value is None: 273 | return False, json_detail_render(201, [], "{} is required".format(key)) 274 | return True, 1 275 | 276 | 277 | def validate_min_length(key, value): 278 | request_value = request.json.get(key) 279 | expect_value = value 280 | if request_value is not None and len(request_value) < expect_value: 281 | return False, json_detail_render(202, [], "{} min length is {}".format(key, value)) 282 | return True, 1 283 | 284 | 285 | def validate_max_length(key, value): 286 | request_value = request.json.get(key) 287 | expect_value = value 288 | if request_value is not None and len(request_value) > expect_value: 289 | return False, json_detail_render(202, [], "{} max length is {}".format(key, value)) 290 | return True, 1 291 | 292 | def validate_type(key, value): 293 | ttype_dict = { 294 | 'list': list, 295 | 'basestring': basestring, 296 | 'dict': dict, 297 | 'int': int, 298 | 'bool': bool, 299 | } 300 | request_value = request.json.get(key) 301 | expect_value = value 302 | if request_value is not None and not isinstance(request_value, ttype_dict.get(value)): 303 | return False, json_detail_render(203, [], "{} should be a {}".format(key, value)) 304 | return True, 1 305 | 306 | KEY_FUNC_MAP = { 307 | 'required': validate_required, 308 | 'min_length': validate_min_length, 309 | 'max_length': validate_max_length, 310 | 'type': validate_type, 311 | } 312 | 313 | def wrapper(func): 314 | @wraps(func) 315 | def _(*args, **kwargs): 316 | protocol, vname = validate_name.split(':') 317 | if request.method == protocol: 318 | all_json = YML_JSON 319 | validate_json = deepcopy(all_json.get(vname)) 320 | del validate_json['returnvalue'] 321 | for item, settings in validate_json.items(): 322 | for key, value in settings.items(): 323 | f = KEY_FUNC_MAP.get(key) 324 | ret = f(item, value) 325 | if not ret[0]: 326 | return ret[1] 327 | return func(*args, **kwargs) 328 | return _ 329 | return wrapper 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | --------------------------------------------------------------------------------