import os
import secrets
import subprocess
import sys
from typing import Optional

import typer
from configobj import ConfigObj
from jinja2 import Template

file_data = {'': {'alembic.ini': ['# A generic, single database configuration.\n', '\n', '[alembic]\n', '# path to migration scripts\n', 'script_location = alembic\n', '\n', '# template used to generate migration files\n', '# file_template = %%(rev)s_%%(slug)s\n', '\n', '# timezone to use when rendering the date\n', '# within the migration file as well as the filename.\n', '# string value is passed to dateutil.tz.gettz()\n', '# leave blank for localtime\n', '# timezone =\n', '\n', '# max length of characters to apply to the\n', '# "slug" field\n', '# truncate_slug_length = 40\n', '\n', "# set to 'true' to run the environment during\n", "# the 'revision' command, regardless of autogenerate\n", '# revision_environment = false\n', '\n', "# set to 'true' to allow .pyc and .pyo files without\n", '# a source .py file to be detected as revisions in the\n', '# versions/ directory\n', '# sourceless = false\n', '\n', '# version location specification; this defaults\n', '# to alembic/versions.  When using multiple version\n', '# directories, initial revisions must be specified with --version-path\n', '# version_locations = %(here)s/bar %(here)s/bat alembic/versions\n', '\n', '# the output encoding used when revision files\n', '# are written from script.py.mako\n', '# output_encoding = utf-8\n', '\n', 'sqlalchemy.url =\n', '\n', '\n', '[post_write_hooks]\n', '# post_write_hooks defines scripts or Python functions that are run\n', '# on newly generated revision scripts.  See the documentation for further\n', '# detail and examples\n', '\n', '# format using "black" - use the console_scripts runner, against the "black" entrypoint\n', '# hooks=black\n', '# black.type=console_scripts\n', '# black.entrypoint=black\n', '# black.options=-l 79\n', '\n', '# Logging configuration\n', '[loggers]\n', 'keys = root, sqlalchemy, alembic\n', '\n', '[handlers]\n', 'keys = console\n', '\n', '[formatters]\n', 'keys = generic\n', '\n', '[logger_root]\n', 'level = WARN\n', 'handlers = console\n', 'qualname = \n', '\n', '[logger_sqlalchemy]\n', 'level = WARN\n', 'handlers = \n', 'qualname = sqlalchemy.engine\n', '\n', '[logger_alembic]\n', 'level = INFO\n', 'handlers = \n', 'qualname = alembic\n', '\n', '[handler_console]\n', 'class = StreamHandler\n', 'args = (sys.stderr, )\n', 'level = NOTSET\n', 'formatter = generic\n', '\n', '[formatter_generic]\n', 'format = %(levelname)-5.5s [%(name)s] %(message)s\n', 'datefmt = %H:%M:%S\n'], 'main.py-tpl': ['from loguru import logger\n', 'from fastapi import FastAPI\n', 'from starlette.middleware.cors import CORSMiddleware\n', '\n', 'from core.config import settings\n', 'from api.api_v1.api import api_router\n', 'from middleware.auto_db_session import DBSessionMiddleware\n', 'from middleware.authentication import BearerAuthenticationMiddleware\n', '\n', 'app = FastAPI(\n', '    title=settings.PROJECT_NAME,\n', "    openapi_url=f'{settings.API_V1_STR}/openapi.json',\n", "    docs_url='/docs',\n", "    redoc_url='/redoc',\n", ')\n', '\n', '# middleware\n', 'app.add_middleware(BearerAuthenticationMiddleware)  # auth middleware\n', 'app.add_middleware(DBSessionMiddleware)  # auto db session manage middleware\n', 'if settings.BACKEND_CORS_ORIGINS:\n', '    app.add_middleware(\n', '        CORSMiddleware,\n', '        allow_origins=settings.BACKEND_CORS_ORIGINS,\n', '        allow_credentials=True,\n', '        allow_methods=["*"],\n', '        allow_headers=["*"],\n', '    )\n', '\n', '# log config\n', '# logger.remove(handler_id=None)\n', "# logger.add(sink=f'logs/{settings.PROJECT_NAME}-{{time:YYYY-MM-DD}}.log',\n", '#            format="{time:YYYY-MM-DD HH:mm:ss}-{level}-{name}:{function}:{line}-{level}-{message}",\n', '#            level=settings.LOG_LEVEL,\n', '#            enqueue=True,\n', '#            diagnose=True,\n', '#            retention="10 days",\n', '#            rotation="24h",\n', "#            encoding='utf-8',\n", "#            # compression='zip'\n", '#            )\n', '\n', '\n', '# app.include_router(api_router)\n', '\n', '# V1\n', 'app.include_router(api_router, prefix=settings.API_V1_STR)\n', '\n'], 'manage.py-tpl': ['import os\n', 'import sys\n', '\n', '\n', 'def main():\n', "    command = 'fastapi-manage '\n", "    command += ' '.join(sys.argv[1:])\n", '    os.chdir(sys.path[0])\n', '    os.system(command)\n', '\n', '\n', "if __name__ == '__main__':\n", '    main()\n', '\n'], 'README.md': [], 'start-celery.sh': ['#!/bin/bash\n', '\n', 'celery -A tasks worker -l info\n'], 'start-gunicorn.sh': ['#!/bin/bash\n', 'read -p "dev_mode: " dev_mode\n', 'read -p "server_port: " server_port\n', 'gunicorn -b 0.0.0.0:$server_port -w 1 -k uvicorn.workers.UvicornWorker -e DEV_MODE=$dev_mode --preload main:app\n'], '__init__.py-tpl': ['\n']}, 'alembic': {'env.py-tpl': ['import sys\n', 'import os\n', 'from logging.config import fileConfig\n', '\n', 'from sqlalchemy import engine_from_config\n', 'from sqlalchemy import pool\n', '\n', 'from alembic import context\n', '\n', 'sys.path.insert(0, os.getcwd())\n', '\n', 'from db import base\n', '\n', '# this is the Alembic Config object, which provides\n', '# access to the values within the .ini file in use.\n', 'config = context.config\n', '\n', '# Interpret the config file for Python logging.\n', '# This line sets up loggers basically.\n', 'fileConfig(config.config_file_name)\n', '\n', "# add your model's MetaData object here\n", "# for 'autogenerate' support\n", '# from myapp import mymodel\n', '# target_metadata = mymodel.Base.metadata\n', 'target_metadata = base.Base.metadata\n', '\n', '# other values from the config, defined by the needs of env.py,\n', '# can be acquired:\n', '# my_important_option = config.get_main_option("my_important_option")\n', '# ... etc.\n', '\n', '\n', 'def run_migrations_offline():\n', '    """Run migrations in \'offline\' mode.\n', '\n', '    This configures the context with just a URL\n', '    and not an Engine, though an Engine is acceptable\n', '    here as well.  By skipping the Engine creation\n', "    we don't even need a DBAPI to be available.\n", '\n', '    Calls to context.execute() here emit the given string to the\n', '    script output.\n', '\n', '    """\n', '    url = config.get_main_option("sqlalchemy.url")\n', '    context.configure(\n', '        url=url,\n', '        target_metadata=target_metadata,\n', '        literal_binds=True,\n', '        dialect_opts={"paramstyle": "named"},\n', '    )\n', '\n', '    with context.begin_transaction():\n', '        context.run_migrations()\n', '\n', '\n', 'def run_migrations_online():\n', '    """Run migrations in \'online\' mode.\n', '\n', '    In this scenario we need to create an Engine\n', '    and associate a connection with the context.\n', '\n', '    """\n', '    connectable = engine_from_config(\n', '        config.get_section(config.config_ini_section),\n', '        prefix="sqlalchemy.",\n', '        poolclass=pool.NullPool,\n', '    )\n', '\n', '    with connectable.connect() as connection:\n', '        context.configure(\n', '            connection=connection, target_metadata=target_metadata\n', '        )\n', '\n', '        with context.begin_transaction():\n', '            context.run_migrations()\n', '\n', '\n', 'if context.is_offline_mode():\n', '    run_migrations_offline()\n', 'else:\n', '    run_migrations_online()\n', '\n'], 'script.py.mako': ['"""${message}\n', '\n', 'Revision ID: ${up_revision}\n', 'Revises: ${down_revision | comma,n}\n', 'Create Date: ${create_date}\n', '\n', '"""\n', 'from alembic import op\n', 'import sqlalchemy as sa\n', '${imports if imports else ""}\n', '\n', '# revision identifiers, used by Alembic.\n', 'revision = ${repr(up_revision)}\n', 'down_revision = ${repr(down_revision)}\n', 'branch_labels = ${repr(branch_labels)}\n', 'depends_on = ${repr(depends_on)}\n', '\n', '\n', 'def upgrade():\n', '    ${upgrades if upgrades else "pass"}\n', '\n', '\n', 'def downgrade():\n', '    ${downgrades if downgrades else "pass"}\n']}, 'alembic/versions': {}, 'api': {'common.py-tpl': ['from typing import Generator\n', '\n', 'from db.session import SessionFactory\n', '\n', '\n', 'def get_session() -> Generator:\n', '    """\n', '    get database session\n', '    """\n', '    db = SessionFactory()\n', '    try:\n', '        yield db\n', '    finally:\n', '        db.close()\n', '\n'], '__init__.py-tpl': ['\n']}, 'api/api_v1': {'api.py-tpl': ['from fastapi import APIRouter\n', '\n', 'from api.api_v1.endpoints import user\n', '\n', 'api_router = APIRouter()\n', '# user\n', "api_router.include_router(user.router, prefix='/user', tags=['user'])\n", '\n'], '__init__.py-tpl': ['\n']}, 'api/api_v1/endpoints': {'user.py-tpl': ['from typing import Any, Optional\n', '\n', 'from fastapi import APIRouter, Depends, HTTPException\n', '\n', 'import models\n', 'import schemas\n', 'from libs.dependencies import Utils, UtilsObject\n', '\n', 'router = APIRouter()\n', '\n'], '__init__.py-tpl': ['\n']}, 'core': {'config.py-tpl': ['from typing import List\n', 'from functools import lru_cache\n', '\n', 'from loguru import logger\n', 'from pydantic import BaseSettings\n', '\n', '\n', 'class Settings(BaseSettings):\n', '    PROJECT_NAME: str = "{{ conf.project_name }}"\n', '\n', '    LOG_LEVEL: str = "DEBUG"  # TRACE, INFO, SUCCESS, WARNING, ERROR, CRITICAL ...\n', '\n', '    API_V1_STR: str = "/api/v1"\n', '    API_LOGIN_URL: str = "/api/v1/login"\n', '\n', '    SECRET_KEY: str = "{{ conf.secret_key }}"\n', '    SALT_ROUNDS: int = 4\n', '\n', '    # JWT expiration time\n', '    ACCESS_TOKEN_EXPIRES_MINUTES: int = 60 * 24\n', '\n', '    # timezone\n', '    TIMEZONE: str = "Asia/Shanghai"\n', '\n', '    # Cross-domain request configuration\n', '    BACKEND_CORS_ORIGINS: List = ["*"]\n', '\n', '    # Database configuration\n', '    DATABASE_USER: str = "{{ conf.db_user }}"\n', '    DATABASE_PASS: str = "{{ conf.db_pass }}"\n', '    DATABASE_HOST: str = "{{ conf.db_host }}"\n', '    DATABASE_DB: str = "{{ conf.db_db }}"\n', '    DATABASE_PORT: str = "{{ conf.db_port }}"\n', '    SQLALCHEMY_DATABASE_URI: str = f"mysql+pymysql://" \\\n', '                                   f"{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_DB}"\n', '\n', '    # Redis store address\n', '    REDIS_STORAGE_HOST: str = "{{ conf.redis_host }}"\n', '    REDIS_STORAGE_PORT: str = "{{ conf.redis_port }}"\n', '    REDIS_STORAGE_PASS: str = "{{ conf.redis_password }}"\n', '\n', '    # RateLimitBackend\n', '    RATE_LIMIT_REDIS_BACKEND_HOST: str = "localhost"\n', '    RATE_LIMIT_REDIS_BACKEND_PORT: str = "6379"\n', '    RATE_LIMIT_REDIS_BACKEND_DB: str = "0"\n', '    RATE_LIMIT_REDIS_BACKEND_PASS: str = ""\n', '\n', '    # Celery broker & backend\n', '    CELERY_BROKER: str = ""\n', '    CELERY_BACKEND: str = ""\n', '\n', '    class Config:\n', '        case_sensitive = True\n', '\n', '\n', '@lru_cache(1)\n', 'def get_config():\n', "    devops_server_host = 'http://'\n", "    api_key = ''\n", "    app = ''\n", '    from dotenv import load_dotenv, find_dotenv\n', '    if find_dotenv():\n', "        logger.debug('Locate .env file and configure the project with.env')\n", "        load_dotenv(encoding='utf8')\n", '        return Settings()\n', '    else:\n', '        import json\n', '        import os\n', '        import requests\n', '\n', "        dev_mode = os.getenv('DEV_MODE', 'dev')\n", "        assert dev_mode in ['test', 'stable', 'dev']\n", "        if dev_mode == 'stable':\n", "            logger.debug('识别生产模式, 自动请求生产环境配置')\n", "        elif dev_mode == 'test':\n", "            logger.debug('识别测试模式, 自动请求测试环境配置')\n", '        else:\n', "            logger.debug('识别开发者模式')\n", '\n', "        if dev_mode != 'dev':\n", "            devops_api = f'{devops_server_host}/api/apis/config/?apiKey={api_key}&app={app}&env={dev_mode}' \\\n", "                         f'&format=json&noPrefix=1'\n", '            r = requests.get(devops_api)\n', '            if r.status_code != 200:\n', "                raise RuntimeError('请求运维服务器错误')\n", '            env = json.loads(r.text)\n', '\n', '            for key, value in env.items():\n', '                os.environ[key] = value\n', "        logger.debug('项目配置加载完成')\n", '        return Settings()\n', '\n', '\n', 'settings = get_config()\n', '\n'], '__init__.py-tpl': ['\n']}, 'db': {'base.py-tpl': ['from db.base_class import Base\n', 'import models\n', '\n'], 'base_class.py-tpl': ['import copy\n', 'from typing import List, Union\n', '\n', 'from sqlalchemy import Column, String, TIMESTAMP, Boolean, func\n', 'from sqlalchemy.dialects.mysql import INTEGER\n', 'from sqlalchemy.ext.declarative import as_declarative, declared_attr\n', '\n', '\n', '@as_declarative()\n', 'class Base:\n', '    id = Column(INTEGER(unsigned=True), primary_key=True, index=True, autoincrement=True)\n', "    created_at = Column(TIMESTAMP, server_default=func.now(), comment='create time of the record')\n", '    updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now(),\n', "                        comment='update time of the record')\n", "    deleted = Column(Boolean, default=False, comment='delete flag')\n", '    __name__: str\n', '\n', '    # Automatically assigns a table name that is lowercase for the current class name\n', '    @declared_attr\n', '    def __tablename__(cls) -> str:\n', '        table_name = []\n', '        class_name = cls.__name__\n', '        for index, char in enumerate(class_name):\n', '            if char.isupper() and index != 0:\n', '                table_name.append("_")\n', '            table_name.append(char)\n', "        return ''.join(table_name).lower()\n", '\n', '    # set engine\n', '    @declared_attr\n', '    def __table_args__(self) -> dict:\n', "        return {'mysql_engine': 'InnoDB'}\n", '\n', '\n', 'class UserBase(Base):\n', '    __abstract__ = True\n', '\n', "    _groups = Column(String(255), default='', comment='user groups')\n", '\n', '    @property\n', '    def groups(self) -> List[str]:\n', "        return [group for group in self._groups.split(',')] if self._groups else []\n", '\n', '    @groups.setter\n', '    def groups(self, value: List[str] = None) -> None:\n', "        self._groups = ','.join([groups.replace(' ', '') for groups in value]) if value else ''\n", '\n', '    def group_add(self, value: Union[str, List[str]]) -> None:\n', '        """ Add groups for users """\n', '        values = value if isinstance(value, list) else [value]\n', '        tmp_groups = copy.deepcopy(self.groups)\n', '        for value in values:\n', '            if value not in tmp_groups:\n', '                tmp_groups.append(value)\n', '        self.groups = tmp_groups\n', '\n', '    def group_remove(self, value: Union[str]) -> None:\n', '        """ Delete groups for users """\n', '        values = value if isinstance(value, list) else [value]\n', '        groups = copy.deepcopy(self.groups)\n', '        for value in values:\n', '            if value in groups:\n', '                groups.remove(value)\n', '        self.groups = groups\n', '\n'], 'init_db.py-tpl': ['from db.base_class import Base\n', 'from db.session import engine\n', '\n', '\n', "if __name__ == '__main__':\n", '    Base.metadata.create_all(bind=engine)\n', '\n'], 'session.py-tpl': ['from functools import lru_cache\n', 'from typing import Union\n', '\n', 'from aredis import StrictRedis\n', 'from sqlalchemy import create_engine\n', 'from sqlalchemy.orm import sessionmaker\n', 'from aredis.client import (\n', '    ClusterCommandMixin, ConnectionCommandMixin, ExtraCommandMixin,\n', '    GeoCommandMixin, HashCommandMixin, HyperLogCommandMixin,\n', '    KeysCommandMixin, ListsCommandMixin, PubSubCommandMixin,\n', '    ScriptingCommandMixin, SentinelCommandMixin, ServerCommandMixin,\n', '    SetsCommandMixin, SortedSetCommandMixin, StringsCommandMixin,\n', '    TransactionCommandMixin, StreamsCommandMixin\n', ')\n', '\n', 'from core.config import settings\n', '\n', 'engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True, pool_size=8)\n', 'SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n', '\n', '\n', '@lru_cache()\n', 'def redis_session(db: int = 0) -> Union[\n', '    ClusterCommandMixin, ConnectionCommandMixin, ExtraCommandMixin,\n', '    GeoCommandMixin, HashCommandMixin, HyperLogCommandMixin,\n', '    KeysCommandMixin, ListsCommandMixin, PubSubCommandMixin,\n', '    ScriptingCommandMixin, SentinelCommandMixin, ServerCommandMixin,\n', '    SetsCommandMixin, SortedSetCommandMixin, StringsCommandMixin,\n', '    TransactionCommandMixin, StreamsCommandMixin\n', ']:\n', '    return StrictRedis(host=settings.REDIS_STORAGE_HOST, port=settings.REDIS_STORAGE_PORT,\n', '                       password=settings.REDIS_STORAGE_PASS or None, db=db, decode_responses=True)\n', '\n'], '__init__.py-tpl': ['\n']}, 'libs': {'dependencies.py-tpl': ['from typing import Any, List, MutableMapping, Sequence, Union\n', '\n', 'from loguru import logger\n', 'from starlette.middleware.sessions import SessionMiddleware\n', 'from starlette.authentication import AuthCredentials, UnauthenticatedUser\n', 'from starlette.datastructures import Address, Headers, QueryParams, State, URL\n', 'from starlette.requests import HTTPConnection\n', 'from fastapi.security import OAuth2PasswordBearer\n', 'from fastapi import Depends, FastAPI, HTTPException, Request\n', '\n', 'from core.config import settings\n', 'from middleware.auto_db_session import DBSession, DBSessionBase\n', 'from middleware.authentication import AuthUser, BearerAuthenticationMiddleware\n', '\n', 'oauth2 = OAuth2PasswordBearer(tokenUrl=settings.API_LOGIN_URL)\n', '\n', '\n', 'class UtilsObject(object):\n', '    def __init__(self, request: Request, db: Union[DBSessionBase, DBSession]):\n', '        if SessionMiddleware in [mw.cls for mw in request.app.user_middleware]:\n', '            self.session = request.session\n', '        self.db: Union[DBSession, DBSessionBase] = db\n', '        self.app: FastAPI = request.app\n', '        self.auth: AuthCredentials = request.auth\n', '        self.base_url: URL = request.base_url\n', '        self.client: Address = request.client\n', '        self.cookies: dict = request.cookies\n', '        self.headers: Headers = request.headers\n', '        self.method: str = request.method\n', '        self.path_params: dict = request.path_params\n', '        self.query_params: QueryParams = request.query_params\n', '        self.scope: MutableMapping[str, Any] = request.scope\n', '        self.state: State = request.state\n', '        self.url: URL = request.url\n', '        self.user: Union[UnauthenticatedUser, AuthUser] = request.user\n', '\n', '\n', 'class BaseDepends:\n', '    def __init__(self):\n', '        pass\n', '\n', '    def __call__(self, request: Request):\n', '        self.request = request\n', '\n', '\n', 'class UtilsBase(BaseDepends):\n', '    def __init__(self, auth: bool = False, scopes: Union[List[str], str] = None):\n', '        super().__init__()\n', '        self._auth = auth\n', '        self._scopes = scopes\n', '\n', '    def __call__(self, request: Request) -> UtilsObject:\n', '        super().__call__(request)\n', '        if self._auth:\n', '            if not request.user.is_authenticated:\n', "                raise HTTPException(detail='permission denied', status_code=403)\n", '            if self._scopes:\n', '                scopes_list = [self._scopes] if isinstance(self._scopes, str) else list(self._scopes)\n', '                if not self.has_required_scope(request, scopes_list):\n', "                    raise HTTPException(detail='permission denied', status_code=403)\n", "        if not hasattr(request.state, 'db'):\n", '            request.state.db = DBSessionBase()\n', '        return UtilsObject(request, request.state.db)\n', '\n', '    @staticmethod\n', '    def has_required_scope(conn: HTTPConnection, scopes: Sequence[str]) -> bool:\n', '        """ Group authentication is passed as long as the user belongs to one of the groups """\n', '        if not len(conn.auth.scopes):\n', '            return False\n', '        for scope in scopes:\n', '            if scope in conn.auth.scopes:\n', '                return True\n', '        return False\n', '\n', '\n', 'class Utils(UtilsBase):\n', '    """\n', '    expansion of the request obj\n', '    """\n', '    middleware_check_flag = False\n', '\n', '    def __call__(self, request: Request, token: str = Depends(oauth2)) -> UtilsObject:\n', '        if not self.middleware_check_flag:\n', "            logger.debug('Dependency checking')\n", '            if BearerAuthenticationMiddleware not in [mw.cls for mw in request.app.user_middleware]:\n', "                raise NameError('auth dependent AuthenticationMiddleware, But not added')\n", '            else:\n', '                Utils.middleware_check_flag = True\n', '        return super().__call__(request)\n', '\n', '    def __new__(cls, auth: bool = False, *args, **kwargs):\n', '        return object.__new__(cls) if auth else UtilsBase(auth, *args, **kwargs)\n', '\n'], 'pagination.py-tpl': ['from typing import Optional\n', '\n', 'from fastapi import HTTPException, Query\n', 'from sqlalchemy.orm import Query as QueryType\n', '\n', '\n', 'class Pagination:\n', "    page_query_param = 'page'\n", "    page_size_query_param = 'page_size'\n", '\n', '    def __init__(self, max_page_size: int = 400):\n', '        self._queryset: Optional[QueryType] = None\n', '        self.max_page_size = max_page_size\n', '\n', '    def __call__(self, page: int = Query(1, alias=page_query_param, ge=1,\n', "                                         description='Client can control the page using this query parameter.'),\n", '                 page_size: int = Query(10, alias=page_size_query_param, ge=1,\n', "                                        description='Client can control the page size using this query parameter.')):\n", '        if page_size > self.max_page_size:\n', "            raise HTTPException(detail=f'The maximum number of page_size cannot exceed {str(self.max_page_size)}',\n", '                                status_code=400)\n', '        self.page = page\n', '        self.page_size = page_size\n', '        return self\n', '\n', '    @property\n', '    def queryset(self):\n', '        return self._queryset\n', '\n', '    @queryset.setter\n', '    def queryset(self, queryset: QueryType):\n', '        if not isinstance(queryset, QueryType):\n', "            raise ValueError('queryset must be of QueryType')\n", '        self._queryset = queryset\n', '\n', '    def get_page(self, num_pages: int = None):\n', '        """\n', '        Returns a valid query set. If no page argument is available, the query parameter passed in is used.\n', '        :param num_pages: page number\n', '        :return: queryset\n', '        """\n', '        if not isinstance(self.queryset, QueryType):\n', "            raise NameError('queryset must be set before it can be used')\n", '        if not num_pages:\n', '            num_pages = self.page\n', '        if num_pages < 1:\n', "            raise ValueError('Incorrect page number')\n", '        if not self.queryset:\n', "            raise UnboundLocalError('QuerySet is uninitialized')\n", '        return self._get_page(num_pages)\n', '\n', '    def _get_page(self, num_pages: int):\n', '        skip = (num_pages - 1) * self.page_size\n', '        return self.queryset.offset(skip).limit(self.page_size).all()\n', '\n', '    def count(self):\n', '        """ Return the total number of pages. This method is inefficient """\n', '        # session.query(func.count(MODELS.id)).filter_by(xxx).all()\n', '        if not isinstance(self.queryset, QueryType):\n', "            raise NameError('queryset must be set before it can be used')\n", '        return self.queryset.count()\n', '\n'], 'security.py-tpl': ['from datetime import datetime, timedelta\n', 'from typing import Any, List, Optional, Union\n', '\n', 'import bcrypt\n', 'from jose import jwt\n', 'from pydantic import BaseModel\n', '\n', 'from core.config import settings\n', '\n', '\n', "ALGORITHM = 'HS256'\n", '\n', '\n', 'class TokenPayload(BaseModel):\n', '    exp: int\n', '    sub: int = None\n', '    group: Optional[List[str]] = None\n', '\n', '\n', 'class Token(BaseModel):\n', '    access_token: str\n', '    token_type: str\n', '\n', '\n', '# create token\n', 'def create_access_token(\n', '    subject: Union[str, Any], group: Optional[List[str]] = None, expires_delta: timedelta = None\n', ') -> Token:\n', '    if expires_delta:\n', '        expire = datetime.utcnow() + expires_delta\n', '    else:\n', '        expire = datetime.utcnow() + timedelta(\n', '            minutes=settings.ACCESS_TOKEN_EXPIRES_MINUTES\n', '        )\n', '    to_encode = {"exp": expire, "sub": str(subject), \'group\': group}\n', '    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)\n', "    return Token(access_token=encoded_jwt, token_type='bearer')\n", '\n', '\n', '# verify password\n', 'def verify_password(plain_password: str, hashed_password: str) -> bool:\n', '    hashed_password = hashed_password.encode()\n', '    result = bcrypt.hashpw(plain_password.encode(), hashed_password)\n', '    return result == hashed_password\n', '\n', '\n', '# password hash\n', 'def get_password_hash(password: str) -> str:\n', '    return bcrypt.hashpw(password.encode(), bcrypt.gensalt(settings.SALT_ROUNDS)).decode()\n', '\n']}, 'middleware': {'authentication.py-tpl': ['import warnings\n', 'from abc import ABC\n', '\n', 'import jose\n', 'from jose import jwt\n', 'from pydantic import ValidationError\n', 'from fastapi.security.utils import get_authorization_scheme_param\n', 'from starlette.middleware.authentication import AuthenticationMiddleware\n', 'from starlette.authentication import (\n', '    AuthenticationBackend, AuthenticationError, BaseUser, UnauthenticatedUser, AuthCredentials\n', ')\n', 'from starlette.types import ASGIApp\n', 'from starlette.requests import HTTPConnection\n', 'from starlette.responses import JSONResponse, PlainTextResponse, Response\n', '\n', 'from libs import security\n', 'from core.config import settings\n', '\n', '\n', 'class BearerAuthenticationMiddleware(AuthenticationMiddleware):\n', '    def __init__(\n', '            self,\n', '            app: ASGIApp,\n', '            on_error=None,\n', '    ) -> None:\n', '        backend = BearerAuthBackend()\n', '        super().__init__(app, backend, on_error)\n', '\n', '    @staticmethod\n', '    def default_on_error(conn: HTTPConnection, exc: Exception) -> Response:\n', '        if len(exc.args) > 1 and isinstance(exc.args[1], int):\n', '            return JSONResponse(exc.args[0], status_code=exc.args[1])\n', '        return PlainTextResponse(str(exc), status_code=400)\n', '\n', '\n', 'class BearerAuthBackend(AuthenticationBackend):\n', '    headers = "Authorization"\n', "    auth_type = 'bearer'\n", '    token_payload = security.TokenPayload\n', "    user_id_flag = 'sub'\n", "    user_group_flag = 'group'\n", '\n', '    async def authenticate(self, request):\n', '        if self.headers.lower() not in request.headers:\n', '            return AuthCredentials(), UnauthenticatedUser()\n', '\n', '        auth = request.headers[self.headers]\n', '        scheme, token = get_authorization_scheme_param(auth)\n', '        if scheme.lower() != self.auth_type:\n', '            return AuthCredentials(), UnauthenticatedUser()\n', '\n', '        return self.verify_token(token)\n', '\n', '    def verify_token(self, token):\n', '        try:\n', '            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[security.ALGORITHM])\n', '            token_data = self.token_payload(**payload).dict(exclude_unset=True)\n', '        except jose.ExpiredSignatureError:\n', "            raise AuthenticationError({'detail': 'Signature has expired.'},  401)\n", '        except(jose.JWTError, ValidationError):\n', "            raise AuthenticationError({'detail': 'Invalid bearer auth credentials.'}, 400)\n", '\n', '        scopes = token_data.get(self.user_group_flag)\n', '        if self.user_group_flag not in token_data:\n', '            msg = f"AuthenticationMiddleware lacks a required property \'{self.user_group_flag}\' in jwt"\n', '            warnings.warn(msg)\n', '        return AuthCredentials(scopes), AuthUser(str(token_data.get(self.user_id_flag)))\n', '\n', '\n', 'class AuthUser(BaseUser, ABC):\n', '    """\n', '    user object\n', '    """\n', '    def __init__(self, userid: str, username: str = None) -> None:\n', '        self.userid = userid\n', '        self.username = username\n', '\n', '    @property\n', '    def is_authenticated(self) -> bool:\n', '        return True\n', '\n', '    @property\n', '    def display_name(self) -> str:\n', '        return self.username\n', '\n', '    @property\n', '    def obj(self):\n', '        """\n', '        TODO: return user object\n', '        :return: User object in db\n', '        """\n', '        return None\n', '\n'], 'auto_db_session.py-tpl': ['from loguru import logger\n', 'from sqlalchemy.orm import Session\n', 'from starlette.middleware.base import BaseHTTPMiddleware\n', '\n', 'from db.session import SessionFactory\n', '\n', '\n', 'class DBSessionMiddleware(BaseHTTPMiddleware):\n', '    """\n', '    DB session automatic management\n', '    """\n', '    def __init__(self, app):\n', '        super().__init__(app)\n', '\n', '    async def dispatch(self, request, call_next):\n', '        request.state.db = DBSession()\n', '        response = await call_next(request)\n', '        request.state.db.__del__()\n', '        return response\n', '\n', '\n', 'class DBSessionBase:\n', '    @property\n', '    def session(self):\n', "        logger.error('DBSessionMiddleware must be installed to access session')\n", "        raise NotImplementedError('DBSessionMiddleware must be installed to access session')\n", '\n', '\n', 'class DBSession(DBSessionBase):\n', '    def __init__(self):\n', '        self._db = None\n', '\n', '    def __del__(self):\n', '        if self._db:\n', '            self._db.close()\n', "            logger.debug('close db session')\n", '\n', '    @property\n', '    def session(self) -> Session:\n', '        if not self._db:\n', '            self._db = SessionFactory()\n', "            logger.debug('create db session')\n", '        return self._db\n', '\n'], 'rate_limit.py-tpl': ['from ipaddress import ip_address\n', 'from typing import Dict, Sequence, Tuple, Callable, Awaitable\n', '\n', 'from jose import jwt\n', 'from loguru import logger\n', 'from starlette.types import ASGIApp\n', 'from fastapi.security.utils import get_authorization_scheme_param\n', 'from ratelimit.types import Scope\n', 'from ratelimit.core import default_429\n', 'from ratelimit.backends import BaseBackend\n', 'from ratelimit import RateLimitMiddleware as RateLimitMixin, Rule\n', 'from ratelimit.backends.redis import RedisBackend as redisBackendMixin\n', '\n', 'from libs import security\n', 'from core.config import settings\n', 'from libs.security import TokenPayload\n', '\n', '\n', 'async def auth_func(scope: Scope) -> Tuple[str, str]:\n', '    """\n', "    Resolve the user's unique identifier and the user's group from ASGI SCOPE.\n", '\n', '    If there is no user information, it should raise `EmptyInformation`.\n', '    If there is no group information, it should return "default".\n', '    """\n', '    # FIXME\n', '    # You must write the logic of this function yourself,\n', '    # or use the function in the following document directly.\n', '    # return USER_UNIQUE_ID, GROUP_NAME\n', '\n', "    # 1. authenticated, take jwt as the unique identity, group: jwt.group or 'default'\n", '    auth_headers = "authorization"\n', "    auth_type = 'bearer'\n", "    headers = {hd[0]: hd[1] for hd in scope.get('headers')}\n", '    if auth_headers.encode() in headers:\n', '        scheme, token = get_authorization_scheme_param(headers[auth_headers.encode()].decode())\n', '        if scheme.lower() == auth_type:\n', '            try:\n', '                payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[security.ALGORITHM])\n', '                token_data = TokenPayload(**payload)\n', "                return str(token_data.sub), token_data.group or 'default'  # return USER_UNIQUE_ID, GROUP_NAME\n", '            except Exception as e:\n', '                logger.warning(e)\n', '\n', '    # 2. unauthorized, Take IP as the unique identity, group: default\n', '    real_ip = ""\n', '    if scope["client"]:\n', '        real_ip, port = tuple(scope["client"])\n', '        for name, value in scope["headers"]:  # type: bytes, bytes\n', '            if name == b"x-real-ip":\n', '                ip = value.decode("utf8")\n', '                if not real_ip and ip_address(ip).is_global:\n', '                    real_ip = ip\n', '    return real_ip, "default"  # return USER_UNIQUE_ID, GROUP_NAME\n', '\n', '\n', 'class RedisBackend(redisBackendMixin):\n', '    def __init__(\n', '            self,\n', '            host: str = "localhost",\n', '            port: int = 6379,\n', '            db: int = 0,\n', '            password: str = None,\n', '    ) -> None:\n', "        host = settings.RATE_LIMIT_REDIS_BACKEND_HOST if hasattr(settings, 'RATE_LIMIT_REDIS_BACKEND_HOST') else host\n", "        port = settings.RATE_LIMIT_REDIS_BACKEND_PORT if hasattr(settings, 'RATE_LIMIT_REDIS_BACKEND_PORT') else port\n", "        db = settings.RATE_LIMIT_REDIS_BACKEND_DB if hasattr(settings, 'RATE_LIMIT_REDIS_BACKEND_DB') else db\n", "        if hasattr(settings, 'RATE_LIMIT_REDIS_BACKEND_PASS') and settings.RATE_LIMIT_REDIS_BACKEND_PASS:\n", '            password = settings.RATE_LIMIT_REDIS_BACKEND_PASS\n', '        super().__init__(host, port, db, password)\n', '\n', '\n', 'class RateLimitMiddleware(RateLimitMixin):\n', '    def __init__(\n', '            self,\n', '            *,\n', '            app: ASGIApp,\n', '            config: Dict[str, Sequence[Rule]],\n', '            authenticate: Callable[[Scope], Awaitable[Tuple[str, str]]] = auth_func,\n', '            backend: BaseBackend = RedisBackend(),\n', '            on_blocked: ASGIApp = default_429,\n', '    ) -> None:\n', '        super().__init__(app, authenticate, backend, config, on_blocked=on_blocked)\n', '\n']}, 'models': {'user.py-tpl': ['from sqlalchemy import Boolean, Column, String, SmallInteger, DATETIME, text\n', 'from sqlalchemy.dialects.mysql import INTEGER\n', '\n', 'from db.base_class import Base, UserBase\n', '\n', '\n', 'class User(UserBase):\n', "    __tablename__ = 'user'\n", '\n', '    userid = Column(String(10), unique=True, index=True)\n', "    username = Column(String(20), default='')\n", '    hashed_password = Column(String(255), nullable=True)\n', "    department = Column(String(50), default='')\n", '    role = Column(SmallInteger, nullable=True)\n', "    email = Column(String(50), default='')\n", '\n', '    last_login = Column(DATETIME, nullable=True)\n', "    date_joined = Column(DATETIME, nullable=False, server_default=text('NOW()'))\n", '    is_active = Column(Boolean, default=True)\n', '    is_staff = Column(Boolean, default=False)\n', '    is_superuser = Column(Boolean, default=False)\n', '\n'], '__init__.py-tpl': ['from .user import User\n', '\n']}, 'schemas': {'user.py-tpl': ['import time\n', 'from datetime import datetime\n', 'from typing import Optional\n', '\n', 'from pydantic import BaseModel, validator\n', '\n', '\n', 'class UserBase(BaseModel):\n', '    userid: str\n', "    username: Optional[str] = ''\n", "    department: Optional[str] = ''\n", '    role: Optional[int] = None\n', "    email: Optional[str] = ''\n", '    last_login: Optional[datetime] = None\n', '    date_joined: Optional[datetime] = None\n', '    is_active: Optional[bool] = True\n', '    is_staff: Optional[bool] = False\n', '    is_superuser: Optional[bool] = False\n', '\n', '\n', 'class UserCreate(BaseModel):\n', '    userid: str\n', '    role: int\n', "    username: Optional[str] = ''\n", "    department: Optional[str] = ''\n", '    password: str = None\n', '    is_staff: Optional[bool] = False\n', '\n', '\n', 'class UserUpdate(UserCreate):\n', '    userid: str = None\n', '    role: str = None\n', '    password: Optional[str] = None\n', '\n', '\n', 'class UserInDBBase(UserBase):\n', '    id: Optional[int] = None\n', '\n', '    class Config:\n', '        orm_mode = True\n', '\n', '\n', 'class User(UserInDBBase):\n', '\n', "    @validator('last_login')\n", '    def last_login(cls, value):\n', "        return int(value.timestamp()) if value else ''\n", '\n', "    @validator('date_joined')\n", '    def date_joined(cls, value):\n', '        print(value)\n', "        return int(value.timestamp()) if value else ''\n", '\n', '\n', 'class UserInDB(UserInDBBase):\n', '    hashed_password: str\n', '\n'], '__init__.py-tpl': ['from .user import User, UserUpdate, UserCreate, UserInDB\n', '\n']}, 'tasks': {'config.py-tpl': ['from core.config import settings\n', '\n', '# Broker settings.\n', 'broker_url = settings.CELERY_BROKER\n', '\n', '# Using the database to store task state and results.\n', 'result_backend = settings.CELERY_BACKEND\n', '\n', '# The name of the default queue used by .apply_async if the message has no route or no custom queue has been specified.\n', "task_default_queue = 'celery'\n", '\n', '# A white-list of content-types/serializers to allow.\n', "accept_content = {'json'}\n", '\n', '# A white-list of content-types/serializers to allow for the result backend.\n', "result_accept_content = {'json'}\n", '\n', '# Configure Celery to use a custom time zone. The timezone value can be any time zone supported by the pytz library.\n', 'timezone = settings.TIMEZONE\n', '\n', '# Exact same semantics as imports, but can be used as a means to have different import categories.\n', "include = ['tasks.tasks']\n", '\n'], 'tasks.py-tpl': ['\n', 'from . import app\n', '\n'], '__init__.py-tpl': ['from celery import Celery\n', '\n', 'from core.config import settings\n', 'from . import config\n', '\n', 'app = Celery(settings.PROJECT_NAME)\n', 'app.config_from_object(config)\n', '\n']}, 'test': {'__init__.py-tpl': ['\n']}}
project_dir = ['alembic/versions', 'api/api_v1/endpoints', 'core', 'db', 'libs', 'middleware', 'models', 'schemas', 'tasks', 'test']


app = typer.Typer()


class Configure:
    templates_dir: str = 'templates'
    bin_file: str = 'bin_files'

    project_file_dir: str = 'project_file_dir.py'
    project_file_data: str = 'project_file_data.py'

    project_name: str = ""
    secret_key: str = ""

    db_user: str = ""
    db_pass: str = ""
    db_host: str = ""
    db_db: str = ""
    db_port: str = "3306"

    redis_host: str = "127.0.0.1"
    redis_port: str = "6379"
    redis_password: str = ""


def startproject_menu(conf_: Configure):
    try:
        # print('\033[32m input your database information')
        # conf_.mysql_user = input('\033[0m user:')
        # conf_.mysql_pass = input(' password:')
        # conf_.mysql_host = input(' host:')
        # conf_.mysql_port = input(' port:')
        # conf_.mysql_db = input(' db:')
        return conf_
    except KeyboardInterrupt:
        print('exit')
        sys.exit(0)


@app.command()
def startproject(project_name: str):
    """
    Create a FastAPI project with the project root folder name [project name]
    """
    # enter project information
    config = Configure()
    config.project_name = project_name
    conf = startproject_menu(config)

    # set the secret key
    conf.secret_key = secrets.token_urlsafe(32)

    # configure project file and template file locations
    base_dir = conf.project_name

    # create the project root directory
    os.mkdir(base_dir)

    # create a file template
    create_project_serializer(base_dir, config)


@app.command()
def makemigrations(
        commit_msg: Optional[str] = typer.Option('auto commit', '-m', help='commit message.')
):
    """
    Perform the migration
    """
    # import the config package
    try:
        sys.path.insert(-1, os.path.join(os.path.abspath('.')))
        conf = __import__('core.config').config
        project = conf.settings
    except ModuleNotFoundError:
        print('error: not in the project directory.')
        sys.exit()

    # determine database configuration
    if not project.DATABASE_USER or not project.DATABASE_PASS \
            or not project.DATABASE_HOST or not project.DATABASE_DB or not project.DATABASE_PORT:
        print('error: database configuration exception. Check the database configuration in the config.py-tpl')
        sys.exit()

    # modify ini file
    alembic_path = 'alembic.ini'
    ini_config = ConfigObj(alembic_path, encoding='UTF8', write_empty_values=True)
    ini_config['alembic']['sqlalchemy.url'] = project.SQLALCHEMY_DATABASE_URI
    ini_config.write()

    subprocess.call(f'alembic revision --autogenerate -m "{commit_msg}"', shell=True)


@app.command()
def migrate():
    """
    Apply to the database
    """
    subprocess.call('alembic upgrade head', shell=True)


@app.command()
def runserver(
        host: Optional[str] = typer.Option('127.0.0.1', '--host', '-h'),
        port: Optional[int] = typer.Option(8000, '--port', '-p'),
        workers: int = typer.Option(1, '--workers', '-w', help='the number of threads'),
        reload: bool = typer.Option(False, help='Code changes detected automatically reload'),
):
    """
    Run the service
    """
    # joint
    command = 'uvicorn main:app'
    if host:
        command += f' --host={host}'
    if port:
        command += f' --port={port}'
    if workers:
        command += f' --workers={workers}'
    if reload is None:
        command += f' --reload'

    # execute the command
    os.system(command)


@app.command()
def help():
    """
    Help information
    """
    print('\033[32mHelp information：')
    print('\033[0m\tstartproject        Create a FastAPI project with the project root folder name [project name]')
    print('\033[0m\tmakemigrations      Perform the migration')
    print('\033[0m\tmigrate             Apply to the database')
    print('\033[0m\trunserver           Run the service --host [IP] server address '
          ' -p --port [int] port number -w -workers [int] process number -- whether reload is automatically loaded')


def create_project_serializer(base_dir, config: Configure):
    """
    Create the project from the template
    """
    # parse the directory in the file
    for directory in project_dir:
        os.makedirs(os.path.join(base_dir, directory))

    for directory, value in file_data.items():
        for file_name, data in value.items():
            # Template file
            if file_name.endswith('.py-tpl'):
                template = Template(''.join(data), variable_start_string='{{ ', variable_end_string=' }}')
                data = template.render(conf=config)
                ext = file_name.split('.')
                source_ext = ext[-1].split('-')[0]
                file_name = ext[0] + '.' + source_ext
            # common file
            with open(os.path.join(base_dir, directory, file_name), 'w', encoding="utf-8") as f:
                f.writelines(data)
