From be808d1f77e650a7958aa43e4984bfe8247c8fa7 Mon Sep 17 00:00:00 2001 From: Weizhi Cui <53459042+StudentCWZ@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:57:40 +0800 Subject: [PATCH] Update project --- app.py | 19 +++++ application/__init__.py | 34 +++++++++ application/config/config.yaml | 9 +++ application/config/production_config.yaml | 2 + application/dao/__init__.py | 13 ++++ application/dao/user.py | 31 ++++++++ application/extensions/__init__.py | 31 ++++++++ application/extensions/init_apispec.py | 27 +++++++ application/extensions/init_bcrypt.py | 26 +++++++ application/extensions/init_config.py | 31 ++++++++ application/extensions/init_logger.py | 25 +++++++ application/extensions/init_marshmallow.py | 27 +++++++ application/extensions/init_migrate.py | 28 +++++++ application/extensions/init_sqlalchemy.py | 29 ++++++++ application/lib/__init__.py | 14 ++++ application/lib/config/__init__.py | 14 ++++ application/lib/config/consul.py | 29 ++++++++ application/lib/config/local.py | 29 ++++++++ application/lib/flask_loguru/__init__.py | 13 ++++ application/lib/flask_loguru/format.py | 31 ++++++++ application/lib/flask_loguru/logger.py | 58 +++++++++++++++ application/models/__init__.py | 13 ++++ application/models/user.py | 49 ++++++++++++ application/schemas/__init__.py | 14 ++++ application/schemas/user.py | 49 ++++++++++++ application/script/__init__.py | 20 +++++ application/script/cmdline.py | 28 +++++++ application/services/__init__.py | 13 ++++ application/services/user.py | 28 +++++++ application/utils/__init__.py | 13 ++++ application/utils/dsn/__init__.py | 13 ++++ application/utils/dsn/dsn.py | 27 +++++++ application/utils/loaders/__init__.py | 11 +++ application/utils/loaders/consul_loader.py | 86 ++++++++++++++++++++++ application/utils/loaders/yaml_loader.py | 74 +++++++++++++++++++ application/views/__init__.py | 19 +++++ application/views/user/__init__.py | 19 +++++ application/views/user/user.py | 47 ++++++++++++ requirements.txt | 36 +++++++++ 39 files changed, 1079 insertions(+) create mode 100644 app.py create mode 100644 application/__init__.py create mode 100644 application/config/config.yaml create mode 100644 application/config/production_config.yaml create mode 100644 application/dao/__init__.py create mode 100644 application/dao/user.py create mode 100644 application/extensions/__init__.py create mode 100644 application/extensions/init_apispec.py create mode 100644 application/extensions/init_bcrypt.py create mode 100644 application/extensions/init_config.py create mode 100644 application/extensions/init_logger.py create mode 100644 application/extensions/init_marshmallow.py create mode 100644 application/extensions/init_migrate.py create mode 100644 application/extensions/init_sqlalchemy.py create mode 100644 application/lib/__init__.py create mode 100644 application/lib/config/__init__.py create mode 100644 application/lib/config/consul.py create mode 100644 application/lib/config/local.py create mode 100644 application/lib/flask_loguru/__init__.py create mode 100644 application/lib/flask_loguru/format.py create mode 100644 application/lib/flask_loguru/logger.py create mode 100644 application/models/__init__.py create mode 100644 application/models/user.py create mode 100644 application/schemas/__init__.py create mode 100644 application/schemas/user.py create mode 100644 application/script/__init__.py create mode 100644 application/script/cmdline.py create mode 100644 application/services/__init__.py create mode 100644 application/services/user.py create mode 100644 application/utils/__init__.py create mode 100644 application/utils/dsn/__init__.py create mode 100644 application/utils/dsn/dsn.py create mode 100644 application/utils/loaders/__init__.py create mode 100644 application/utils/loaders/consul_loader.py create mode 100644 application/utils/loaders/yaml_loader.py create mode 100644 application/views/__init__.py create mode 100644 application/views/user/__init__.py create mode 100644 application/views/user/user.py create mode 100644 requirements.txt diff --git a/app.py b/app.py new file mode 100644 index 0000000..8295ef9 --- /dev/null +++ b/app.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 13:49 +# @File : app.py +# @Description : +""" + +from application import create_app + +app = create_app() + + +if __name__ == "__main__": + app.run() diff --git a/application/__init__.py b/application/__init__.py new file mode 100644 index 0000000..ea183bf --- /dev/null +++ b/application/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:11 +# @File : __init__.py +# @Description : +""" + +import os + +from flask import Flask + +from application.extensions import init_plugs +from application.views import init_views +from application.script import init_script + + +def create_app() -> Flask: + app = Flask(os.path.abspath(os.path.join(os.path.dirname(__file__), "."))) + + # 注册各种插件 + init_plugs(app) + + # 注册路由 + init_views(app) + + # 注册命令 + init_script(app) + + return app diff --git a/application/config/config.yaml b/application/config/config.yaml new file mode 100644 index 0000000..4831032 --- /dev/null +++ b/application/config/config.yaml @@ -0,0 +1,9 @@ +System: + Env: public + +MySQL: + USER: root + PASSWORD: localhost123 + HOST: localhost + PORT: 3306 + DB: elp diff --git a/application/config/production_config.yaml b/application/config/production_config.yaml new file mode 100644 index 0000000..485af78 --- /dev/null +++ b/application/config/production_config.yaml @@ -0,0 +1,2 @@ +System: + Env: public123 \ No newline at end of file diff --git a/application/dao/__init__.py b/application/dao/__init__.py new file mode 100644 index 0000000..7aa926b --- /dev/null +++ b/application/dao/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:31 +# @File : __init__.py +# @Description : +""" + +from .user import UserDao diff --git a/application/dao/user.py b/application/dao/user.py new file mode 100644 index 0000000..ed1571c --- /dev/null +++ b/application/dao/user.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 23:56 +# @File : user.py +# @Description : +""" + +from application.extensions.init_sqlalchemy import db +from application.models.user import User + + +class UserDao: + @classmethod + def get_all(cls): + return User.query.all() + + @classmethod + def get_by_id(cls, user_id: int): + return User.query.get(user_id) + + @classmethod + def create(cls, username: str, password: str, email: str): + new_user = User(username=username, email=email, password=password) + db.session.add(new_user) + db.session.commit() + return new_user diff --git a/application/extensions/__init__.py b/application/extensions/__init__.py new file mode 100644 index 0000000..f2d5b89 --- /dev/null +++ b/application/extensions/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:50 +# @File : __init__.py +# @Description : +""" + +from flask import Flask + +from .init_config import init_config +from .init_logger import init_logger +from .init_sqlalchemy import init_database +from .init_bcrypt import init_bcrypt +from .init_migrate import init_migrate +from .init_apispec import init_apispec +from .init_marshmallow import init_marshmallow + + +def init_plugs(app: Flask) -> None: + init_config(app) + init_logger(app) + init_database(app) + init_bcrypt(app) + init_migrate(app) + init_apispec(app) + init_marshmallow(app) diff --git a/application/extensions/init_apispec.py b/application/extensions/init_apispec.py new file mode 100644 index 0000000..d6bb6f8 --- /dev/null +++ b/application/extensions/init_apispec.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 12:01 +# @File : init_apispec.py +# @Description : +""" + +from flask import Flask +from flask_apispec import FlaskApiSpec + +# 初始化 flask-apispec +apispec = FlaskApiSpec() + + +def init_apispec(app: Flask) -> None: + """ + Initialize the ApiSpec extension + + :param app: flask.Flask application instance + :return: None + """ + apispec.init_app(app) diff --git a/application/extensions/init_bcrypt.py b/application/extensions/init_bcrypt.py new file mode 100644 index 0000000..34ade7c --- /dev/null +++ b/application/extensions/init_bcrypt.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:06 +# @File : init_bcrypt.py +# @Description : +""" + +from flask import Flask +from flask_bcrypt import Bcrypt + +bcrypt = Bcrypt() + + +def init_bcrypt(app: Flask) -> None: + """ + Initialize the bcrypt extension + + :param app: flask.Flask application instance + :return: None + """ + bcrypt.init_app(app) diff --git a/application/extensions/init_config.py b/application/extensions/init_config.py new file mode 100644 index 0000000..c3952dd --- /dev/null +++ b/application/extensions/init_config.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:53 +# @File : init_config.py +# @Description : flask-dynaconf extension +""" + +from flask import Flask + +from dynaconf import FlaskDynaconf + + +def init_config(app: Flask) -> None: + """ + Initialize the dynaconf extension + + :param app: flask.Flask application instance + :return: None + """ + FlaskDynaconf( + app=app, + loaders=[ + "application.utils.loaders.consul_loader", + "application.utils.loaders.yaml_loader", + ], + ) diff --git a/application/extensions/init_logger.py b/application/extensions/init_logger.py new file mode 100644 index 0000000..62e96a5 --- /dev/null +++ b/application/extensions/init_logger.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 18:16 +# @File : init_logger.py +# @Description : +""" + +from flask import Flask + +from application.lib import FlaskLoguru + + +def init_logger(app: Flask) -> None: + """ + Initialize the dynaconf extension + + :param app: flask.Flask application instance + :return: None + """ + FlaskLoguru(app) diff --git a/application/extensions/init_marshmallow.py b/application/extensions/init_marshmallow.py new file mode 100644 index 0000000..8923008 --- /dev/null +++ b/application/extensions/init_marshmallow.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 12:36 +# @File : init_marshmallow.py +# @Description : +""" + +from flask import Flask + +from flask_marshmallow import Marshmallow + +ma = Marshmallow() + + +def init_marshmallow(app: Flask) -> None: + """ + Initialize the database extension + + :param app: flask.Flask application instance + :return: None + """ + ma.init_app(app) \ No newline at end of file diff --git a/application/extensions/init_migrate.py b/application/extensions/init_migrate.py new file mode 100644 index 0000000..2f0457b --- /dev/null +++ b/application/extensions/init_migrate.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 01:19 +# @File : init_migrate.py +# @Description : +""" + +from flask import Flask +from flask_migrate import Migrate + +from application.extensions.init_sqlalchemy import db + +migrate = Migrate() + + +def init_migrate(app: Flask) -> None: + """ + Initialize the database extension + + :param app: flask.Flask application instance + :return: None + """ + migrate.init_app(app, db) diff --git a/application/extensions/init_sqlalchemy.py b/application/extensions/init_sqlalchemy.py new file mode 100644 index 0000000..1e47102 --- /dev/null +++ b/application/extensions/init_sqlalchemy.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:01 +# @File : init_sqlalchemy.py +# @Description : +""" + +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +from application.utils import dsn + +db = SQLAlchemy() + + +def init_database(app: Flask) -> None: + """ + Initialize the database extension + + :param app: flask.Flask application instance + :return: None + """ + app.config.setdefault('SQLALCHEMY_DATABASE_URI', dsn(app)) + db.init_app(app) diff --git a/application/lib/__init__.py b/application/lib/__init__.py new file mode 100644 index 0000000..47b6820 --- /dev/null +++ b/application/lib/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:19 +# @File : __init__.py +# @Description : +""" + +from .config import ConsulConfig, LocalConfig +from .flask_loguru import FlaskLoguru diff --git a/application/lib/config/__init__.py b/application/lib/config/__init__.py new file mode 100644 index 0000000..0df1d01 --- /dev/null +++ b/application/lib/config/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 15:06 +# @File : __init__.py.py +# @Description : +""" + +from .consul import ConsulConfig +from .local import LocalConfig diff --git a/application/lib/config/consul.py b/application/lib/config/consul.py new file mode 100644 index 0000000..3eee5c2 --- /dev/null +++ b/application/lib/config/consul.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 15:06 +# @File : consul.py +# @Description : +""" + +from typing import Any + +import consul +import yaml + + +class ConsulConfig: + def __init__(self, host='localhost', port=8500, token=None, dc='dc1'): + self.token = token + self.dc = dc + self.client = consul.Consul(host=host, port=port, token=token, dc=dc) + + def get(self, key: str) -> Any: + _, data = self.client.kv.get(key=key, token=self.token, dc=self.dc) + if data is None: + raise KeyError(f'Key {key} not found in Consul.') + return yaml.load(data['Value'], Loader=yaml.FullLoader) diff --git a/application/lib/config/local.py b/application/lib/config/local.py new file mode 100644 index 0000000..0ff1702 --- /dev/null +++ b/application/lib/config/local.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 15:07 +# @File : local.py +# @Description : +""" + +import os +from typing import Any + +import yaml + + +class LocalConfig: + def __init__(self, config_dir: str): + self.config_dir = config_dir + + def load(self, filename: str) -> Any: + filepath = os.path.join(self.config_dir, filename) + if not os.path.exists(filepath): + raise FileNotFoundError(f"No such file or directory: '{filepath}'") + with open(filepath, 'r') as file: + cfg = yaml.safe_load(file) + return cfg diff --git a/application/lib/flask_loguru/__init__.py b/application/lib/flask_loguru/__init__.py new file mode 100644 index 0000000..a382bc2 --- /dev/null +++ b/application/lib/flask_loguru/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 17:43 +# @File : __init__.py +# @Description : +""" + +from .logger import FlaskLoguru diff --git a/application/lib/flask_loguru/format.py b/application/lib/flask_loguru/format.py new file mode 100644 index 0000000..ca5fe95 --- /dev/null +++ b/application/lib/flask_loguru/format.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 18:21 +# @File : format.py +# @Description : +""" + +import json + + +def serialize(record: dict) -> str: + time_stamp = record["time"] + time_stamp = time_stamp.strftime("%Y-%m-%d %H:%M:%S") + subset = { + "time": time_stamp, + "message": record["message"], + "level": record["level"].name.lower(), + "tag": "{}:{}".format(record["file"].path, record["line"]), + "field": {"data": record["extra"].get("data", {})}, + } + return json.dumps(subset, ensure_ascii=False) + + +def patching(record: dict) -> str: + record["extra"]["serialized"] = serialize(record) + return "{extra[serialized]}\n" diff --git a/application/lib/flask_loguru/logger.py b/application/lib/flask_loguru/logger.py new file mode 100644 index 0000000..6b015d5 --- /dev/null +++ b/application/lib/flask_loguru/logger.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 17:44 +# @File : logger.py +# @Description : +""" + +import sys + +from flask import Flask, request, g +from loguru import logger + +from .format import patching + + +class FlaskLoguru: + def __init__(self, app=None): + if app is not None: + self.init_app(app) + + def init_app(self, app: Flask): + logger.remove() + logger.add(sys.stderr, format=patching) + if not hasattr(app, "extensions"): + app.extensions = {} + + app.extensions.setdefault("loguru", {}) + app.extensions["loguru"][self] = logger + + @app.before_request + def before_request(): + data = dict( + url=request.url, + method=request.method, + ip=request.remote_addr, + request_body=request.get_json(), + ) + g.logger = logger.bind(data=data) + g.logger.info('Request started') + + @app.after_request + def after_request(response): + g.logger.info('Request completed') + return response + + @app.teardown_request + def teardown_request(exception=None): + if exception: + data = dict( + exception=str(exception) + ) + g.logger = logger.bind(data=data) + g.logger.exception('An error occurred during the request') diff --git a/application/models/__init__.py b/application/models/__init__.py new file mode 100644 index 0000000..2f7985a --- /dev/null +++ b/application/models/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:12 +# @File : __init__.py.py +# @Description : +""" + +from .user import User diff --git a/application/models/user.py b/application/models/user.py new file mode 100644 index 0000000..0a84600 --- /dev/null +++ b/application/models/user.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:12 +# @File : user.py +# @Description : +""" + +from sqlalchemy.ext.hybrid import hybrid_property + +from application.extensions.init_bcrypt import bcrypt +from application.extensions.init_sqlalchemy import db + + +class User(db.Model): + """Basic user model""" + + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(80), unique=True, nullable=False) + _password = db.Column("password", db.String(255), nullable=False) + active = db.Column(db.Boolean, default=True) + + @hybrid_property + def password(self): + return self._password + + @password.setter + def password(self, password: str): + self._password = bcrypt.generate_password_hash(password).decode('utf-8') + + def check_password(self, password: str): + # 判断传过来的密码是否与数据库存的密码一致 + return bcrypt.check_password_hash(self._password, password) + + def to_dict(self) -> dict: + """object to dict""" + return { + 'id': self.id, + 'username': self.username, + 'email': self.email, + } + + def __repr__(self) -> str: + return f'' diff --git a/application/schemas/__init__.py b/application/schemas/__init__.py new file mode 100644 index 0000000..11aa313 --- /dev/null +++ b/application/schemas/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:31 +# @File : __init__.py.py +# @Description : +""" + +from .user import CreateUserItem +from .user import UserSchema diff --git a/application/schemas/user.py b/application/schemas/user.py new file mode 100644 index 0000000..1a13d2a --- /dev/null +++ b/application/schemas/user.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:07 +# @File : user.py +# @Description : +""" + +from pydantic import BaseModel, EmailStr, field_validator +from application.extensions.init_marshmallow import ma +from application.extensions.init_sqlalchemy import db +from application.models import User +from marshmallow import validates, ValidationError + + +class CreateUserItem(BaseModel): + username: str + password: str + email: EmailStr + + @classmethod + @field_validator("username") + def name_must_contain_only_characters(cls, value): + if not value.isalpha(): + raise ValueError("Username must contain only characters.") + return value + + +class UserSchema(ma.SQLAlchemyAutoSchema): + + id = ma.Int(dump_only=True) + username = ma.Str(required=True) + password = ma.String(load_only=True, required=True) + email = ma.Email(required=True) + + class Meta: + model = User + sqla_session = db.session + load_instance = True + exclude = ("_password",) + + @validates('username') + def validate_username(self, username): + if len(username) < 3: + raise ValidationError('Username must be at least 3 characters.') \ No newline at end of file diff --git a/application/script/__init__.py b/application/script/__init__.py new file mode 100644 index 0000000..fad2e9a --- /dev/null +++ b/application/script/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 11:21 +# @File : __init__.py.py +# @Description : +""" + + +from flask import Flask + +from .cmdline import init + + +def init_script(app: Flask) -> None: + app.cli.add_command(init) diff --git a/application/script/cmdline.py b/application/script/cmdline.py new file mode 100644 index 0000000..d80507c --- /dev/null +++ b/application/script/cmdline.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 11:21 +# @File : cmdline.py +# @Description : +""" + +import click +from flask.cli import with_appcontext + + +@click.command("init") +@with_appcontext +def init(): + """Create a new admin user""" + from application.extensions.init_sqlalchemy import db + from application.models import User + + click.echo("create user") + user = User(username="StudentCWZ", email="StudentCWZ@outlook.com", password="qwe!2345", active=True) + db.session.add(user) + db.session.commit() + click.echo("created user admin") diff --git a/application/services/__init__.py b/application/services/__init__.py new file mode 100644 index 0000000..0c75594 --- /dev/null +++ b/application/services/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:29 +# @File : __init__.py +# @Description : +""" + +from .user import UserService diff --git a/application/services/user.py b/application/services/user.py new file mode 100644 index 0000000..ce1983b --- /dev/null +++ b/application/services/user.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:03 +# @File : user.py +# @Description : +""" + +from application.dao import UserDao + + +class UserService: + + @classmethod + def get_all_users(cls): + return UserDao.get_all() + + @classmethod + def get_user_by_id(cls, user_id: int): + return UserDao.get_by_id(user_id) + + @classmethod + def create_user(cls, username: str, password: str, email: str): + return UserDao.create(username, password, email) diff --git a/application/utils/__init__.py b/application/utils/__init__.py new file mode 100644 index 0000000..9776109 --- /dev/null +++ b/application/utils/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:15 +# @File : __init__.py +# @Description : +""" + +from .dsn import dsn diff --git a/application/utils/dsn/__init__.py b/application/utils/dsn/__init__.py new file mode 100644 index 0000000..5711662 --- /dev/null +++ b/application/utils/dsn/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:36 +# @File : __init__.py +# @Description : +""" + +from .dsn import dsn diff --git a/application/utils/dsn/dsn.py b/application/utils/dsn/dsn.py new file mode 100644 index 0000000..0f7b544 --- /dev/null +++ b/application/utils/dsn/dsn.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:36 +# @File : dsn.py +# @Description : +""" + +from flask import Flask + + +def dsn(app: Flask) -> str: + """ + Initialize the MySQL dsn extension + + :param app: flask.Flask application instance + :return: dsn + """ + mysql_cfg = app.config.get('MySQL') + if mysql_cfg is None: + raise KeyError('Key MySQL error') + return (f'mysql+pymysql://{app.config.MySQL.USER}:' + f'{app.config.MySQL.PASSWORD}@{app.config.MySQL.HOST}:{app.config.MySQL.PORT}/{app.config.MySQL.DB}') diff --git a/application/utils/loaders/__init__.py b/application/utils/loaders/__init__.py new file mode 100644 index 0000000..c80b422 --- /dev/null +++ b/application/utils/loaders/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:16 +# @File : __init__.py +# @Description : +""" \ No newline at end of file diff --git a/application/utils/loaders/consul_loader.py b/application/utils/loaders/consul_loader.py new file mode 100644 index 0000000..58297fc --- /dev/null +++ b/application/utils/loaders/consul_loader.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:16 +# @File : consul_loader.py +# @Description : +""" + +import os +from typing import NamedTuple, Union + +import requests +from dynaconf.base import LazySettings +from dynaconf.utils.parse_conf import parse_conf_data + +from application.lib import ConsulConfig + +IDENTIFIER = "consul_loader" + + +class SourceMetadata(NamedTuple): + loader: str + identifier: str + env: str + merged: bool = False + + +def load( + obj: LazySettings, + env: str = None, + silent: bool = True, + key: str = None, + validate=False, +) -> Union[bool, None]: + consul_host = os.environ.get('CONSUL_HOST', 'localhost') + consul_port = os.environ.get('CONSUL_PORT', 8500) + consul_dc = os.environ.get('CONSUL_DC', 'dc1') + consul_token = os.getenv('CONSUL_TOKEN') + consul_key = os.getenv('CONSUL_KEY') + + # 没有对应环境变量,会进入下一个加载器 + if consul_key is None: + return + + # 实例 ConsulConfig + client = ConsulConfig(host=consul_host, port=consul_port, token=consul_token, dc=consul_dc) + # 捕获异常 + try: + data = client.get(key=consul_key) + except requests.exceptions.ConnectionError: + print(2) + # 连接错误后,则会进入下一个加载器 + return + except Exception as e: + # 发生未知错误,才会抛出异常 + raise RuntimeError(f'Unknown error: {e}') + + # 基于 key 获取所需配置 + if key is not None: + data = data[key] + + if env is None: + return + + try: + # 获取 consul 注册中心的配置信息 + result = { + key: parse_conf_data(value, tomlfy=True, box_settings=obj) + for key, value in data.items() + } + except Exception as e: + if silent: + return False + raise e + else: + result['Consul'] = True + # 将 result 配置写入 dynaconf 内置配置中 + obj.update( + result, + loader_identifier=IDENTIFIER, + validate=validate, + ) diff --git a/application/utils/loaders/yaml_loader.py b/application/utils/loaders/yaml_loader.py new file mode 100644 index 0000000..7234afd --- /dev/null +++ b/application/utils/loaders/yaml_loader.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 13:01 +# @File : yaml_loader.py +# @Description : +""" + +import os +from pathlib import Path +from typing import Union + +from dynaconf.base import LazySettings +from dynaconf.utils.parse_conf import parse_conf_data + +from application.lib import LocalConfig + +IDENTIFIER = "yaml_loader" + + +def load( + obj: LazySettings, + env: str = None, + silent: bool = True, + key: str = None, + validate=False, +) -> Union[bool, None]: + # 判断是否已经加载 consul 配置,如果加载,则不再加载本地配置 + if obj.get('Consul', False): + return + # 获取本地配置文件目录 + config_dir = os.path.join(str(Path(__file__).parent.parent.parent), 'config') + # 实例化 LocalConfig 对象 + local_cfg = LocalConfig(config_dir) + + # 判断当前是否为生产环境,基于不同环境读取本地不同配置文件 + if env == 'PRODUCTION': + filename = f'{env.lower()}_config.yaml' + else: + filename = 'config.yaml' + + # 基于 key 获取所需配置 + if key is None: + cfg = local_cfg.load(filename) + else: + cfg = local_cfg.load(filename)[key] + + try: + # 将 cfg 配置写入 dynaconf 内置配置中 + if cfg.get(env) is None: + result = { + key: parse_conf_data(value, tomlfy=True, box_settings=obj) + for key, value in cfg.items() + } + else: + result = { + key: parse_conf_data(value, tomlfy=True, box_settings=obj) + for key, value in cfg[env].items() + } + except Exception as e: + if silent: + return False + raise e + else: + if result: + obj.update( + result, + loader_identifier=IDENTIFIER, + validate=validate, + ) diff --git a/application/views/__init__.py b/application/views/__init__.py new file mode 100644 index 0000000..327d917 --- /dev/null +++ b/application/views/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:29 +# @File : __init__.py.py +# @Description : +""" + +from flask import Flask + +from .user import register_user_views + + +def init_views(app: Flask) -> None: + register_user_views(app) diff --git a/application/views/user/__init__.py b/application/views/user/__init__.py new file mode 100644 index 0000000..17d3c96 --- /dev/null +++ b/application/views/user/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:18 +# @File : __init__.py.py +# @Description : +""" + +from flask import Flask + +from .user import user_api + + +def register_user_views(app: Flask): + app.register_blueprint(user_api) diff --git a/application/views/user/user.py b/application/views/user/user.py new file mode 100644 index 0000000..fd75459 --- /dev/null +++ b/application/views/user/user.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:08 +# @File : user.py +# @Description : +""" + +from flask import Blueprint, request, jsonify +from application.services import UserService +from marshmallow import ValidationError + +from application.schemas import UserSchema + +user_api = Blueprint('user_api', __name__) + + +@user_api.route('/users', methods=['GET']) +def get_users(): + users = UserService.get_all_users() + return jsonify([user.to_dict() for user in users]) + + +@user_api.route('/users/', methods=['GET']) +def get_user(user_id): + user = UserService.get_user_by_id(user_id) + if user is None: + return jsonify({'error': 'User not found'}), 404 + return jsonify(user.to_dict()) + + +@user_api.route('/users', methods=['POST']) +def create_user(): + user_schema = UserSchema() + try: + data = user_schema.load(request.json) + except ValidationError as e: + return jsonify(e.messages), 400 + username = data.username + password = data.password + email = data.email + user = UserService.create_user(username, password, email) + return jsonify(user_schema.dump(user)), 201 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ecab631 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,36 @@ +alembic==1.12.1 +annotated-types==0.6.0 +apispec==6.3.0 +bcrypt==4.0.1 +blinker==1.6.3 +certifi==2023.7.22 +charset-normalizer==3.3.1 +click==8.1.7 +dnspython==2.4.2 +dynaconf==3.2.3 +email-validator==2.1.0.post1 +Flask==3.0.0 +flask-apispec==0.11.4 +Flask-Bcrypt==1.0.1 +Flask-Migrate==4.0.5 +Flask-SQLAlchemy==3.1.1 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +loguru==0.7.2 +Mako==1.2.4 +MarkupSafe==2.1.3 +marshmallow==3.20.1 +packaging==23.2 +pydantic==2.4.2 +pydantic_core==2.10.1 +PyMySQL==1.1.0 +python-consul==1.1.0 +PyYAML==6.0.1 +requests==2.31.0 +six==1.16.0 +SQLAlchemy==2.0.22 +typing_extensions==4.8.0 +urllib3==2.0.7 +webargs==8.3.0 +Werkzeug==3.0.1 -- GitLab