- 新增图像生成接口,支持试用、积分和自定义API Key模式 - 实现生成图片结果异步上传至MinIO存储,带重试机制 - 优化积分预扣除和异常退还逻辑,保障用户积分准确 - 添加获取生成历史记录接口,支持时间范围和分页 - 提供本地字典配置接口,支持模型、比例、提示模板和尺寸 - 实现图片批量上传接口,支持S3兼容对象存储 feat(admin): 增加管理员角色管理与权限分配接口 - 实现角色列表查询、角色创建、更新及删除功能 - 增加权限列表查询接口 - 实现用户角色分配接口,便于统一管理用户权限 - 增加系统字典增删查改接口,支持分类过滤和排序 - 权限控制全面覆盖管理接口,保证安全访问 feat(auth): 完善用户登录注册及权限相关接口与页面 - 实现手机号验证码发送及校验功能,保障注册安全 - 支持手机号注册、登录及退出接口,集成日志记录 - 增加修改密码功能,验证原密码后更新 - 提供动态导航菜单接口,基于权限展示不同菜单 - 实现管理界面路由及日志、角色、字典管理页面访问权限控制 - 添加系统日志查询接口,支持关键词和等级筛选 feat(app): 初始化Flask应用并配置蓝图与数据库 - 创建应用程序工厂,加载配置,初始化数据库和Redis客户端 - 注册认证、API及管理员蓝图,整合路由 - 根路由渲染主页模板 - 应用上下文中自动创建数据库表,保证运行环境准备完毕 feat(database): 提供数据库创建与迁移支持脚本 - 新增数据库创建脚本,支持自动检测是否已存在 - 添加数据库表初始化脚本,支持创建和删除所有表 - 实现RBAC权限初始化,包含基础权限和角色创建 - 新增字段手动修复脚本,添加用户API Key和积分字段 - 强制迁移脚本支持清理连接和修复表结构,初始化默认数据及角色分配 feat(config): 新增系统配置参数 - 配置数据库、Redis、Session和MinIO相关参数 - 添加AI接口地址及试用Key配置 - 集成阿里云短信服务配置及开发模式相关参数 feat(extensions): 初始化数据库、Redis和MinIO客户端 - 创建全局SQLAlchemy数据库实例和Redis客户端 - 配置基于boto3的MinIO兼容S3客户端 chore(logs): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
281 lines
9.5 KiB
Python
281 lines
9.5 KiB
Python
import logging
|
|
import os
|
|
import subprocess
|
|
from optparse import Values
|
|
from typing import Any, List, Optional
|
|
|
|
from pip._internal.cli.base_command import Command
|
|
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
|
from pip._internal.configuration import (
|
|
Configuration,
|
|
Kind,
|
|
get_configuration_files,
|
|
kinds,
|
|
)
|
|
from pip._internal.exceptions import PipError
|
|
from pip._internal.utils.logging import indent_log
|
|
from pip._internal.utils.misc import get_prog, write_output
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ConfigurationCommand(Command):
|
|
"""
|
|
Manage local and global configuration.
|
|
|
|
Subcommands:
|
|
|
|
- list: List the active configuration (or from the file specified)
|
|
- edit: Edit the configuration file in an editor
|
|
- get: Get the value associated with command.option
|
|
- set: Set the command.option=value
|
|
- unset: Unset the value associated with command.option
|
|
- debug: List the configuration files and values defined under them
|
|
|
|
Configuration keys should be dot separated command and option name,
|
|
with the special prefix "global" affecting any command. For example,
|
|
"pip config set global.index-url https://example.org/" would configure
|
|
the index url for all commands, but "pip config set download.timeout 10"
|
|
would configure a 10 second timeout only for "pip download" commands.
|
|
|
|
If none of --user, --global and --site are passed, a virtual
|
|
environment configuration file is used if one is active and the file
|
|
exists. Otherwise, all modifications happen to the user file by
|
|
default.
|
|
"""
|
|
|
|
ignore_require_venv = True
|
|
usage = """
|
|
%prog [<file-option>] list
|
|
%prog [<file-option>] [--editor <editor-path>] edit
|
|
|
|
%prog [<file-option>] get command.option
|
|
%prog [<file-option>] set command.option value
|
|
%prog [<file-option>] unset command.option
|
|
%prog [<file-option>] debug
|
|
"""
|
|
|
|
def add_options(self) -> None:
|
|
self.cmd_opts.add_option(
|
|
"--editor",
|
|
dest="editor",
|
|
action="store",
|
|
default=None,
|
|
help=(
|
|
"Editor to use to edit the file. Uses VISUAL or EDITOR "
|
|
"environment variables if not provided."
|
|
),
|
|
)
|
|
|
|
self.cmd_opts.add_option(
|
|
"--global",
|
|
dest="global_file",
|
|
action="store_true",
|
|
default=False,
|
|
help="Use the system-wide configuration file only",
|
|
)
|
|
|
|
self.cmd_opts.add_option(
|
|
"--user",
|
|
dest="user_file",
|
|
action="store_true",
|
|
default=False,
|
|
help="Use the user configuration file only",
|
|
)
|
|
|
|
self.cmd_opts.add_option(
|
|
"--site",
|
|
dest="site_file",
|
|
action="store_true",
|
|
default=False,
|
|
help="Use the current environment configuration file only",
|
|
)
|
|
|
|
self.parser.insert_option_group(0, self.cmd_opts)
|
|
|
|
def run(self, options: Values, args: List[str]) -> int:
|
|
handlers = {
|
|
"list": self.list_values,
|
|
"edit": self.open_in_editor,
|
|
"get": self.get_name,
|
|
"set": self.set_name_value,
|
|
"unset": self.unset_name,
|
|
"debug": self.list_config_values,
|
|
}
|
|
|
|
# Determine action
|
|
if not args or args[0] not in handlers:
|
|
logger.error(
|
|
"Need an action (%s) to perform.",
|
|
", ".join(sorted(handlers)),
|
|
)
|
|
return ERROR
|
|
|
|
action = args[0]
|
|
|
|
# Determine which configuration files are to be loaded
|
|
# Depends on whether the command is modifying.
|
|
try:
|
|
load_only = self._determine_file(
|
|
options, need_value=(action in ["get", "set", "unset", "edit"])
|
|
)
|
|
except PipError as e:
|
|
logger.error(e.args[0])
|
|
return ERROR
|
|
|
|
# Load a new configuration
|
|
self.configuration = Configuration(
|
|
isolated=options.isolated_mode, load_only=load_only
|
|
)
|
|
self.configuration.load()
|
|
|
|
# Error handling happens here, not in the action-handlers.
|
|
try:
|
|
handlers[action](options, args[1:])
|
|
except PipError as e:
|
|
logger.error(e.args[0])
|
|
return ERROR
|
|
|
|
return SUCCESS
|
|
|
|
def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
|
|
file_options = [
|
|
key
|
|
for key, value in (
|
|
(kinds.USER, options.user_file),
|
|
(kinds.GLOBAL, options.global_file),
|
|
(kinds.SITE, options.site_file),
|
|
)
|
|
if value
|
|
]
|
|
|
|
if not file_options:
|
|
if not need_value:
|
|
return None
|
|
# Default to user, unless there's a site file.
|
|
elif any(
|
|
os.path.exists(site_config_file)
|
|
for site_config_file in get_configuration_files()[kinds.SITE]
|
|
):
|
|
return kinds.SITE
|
|
else:
|
|
return kinds.USER
|
|
elif len(file_options) == 1:
|
|
return file_options[0]
|
|
|
|
raise PipError(
|
|
"Need exactly one file to operate upon "
|
|
"(--user, --site, --global) to perform."
|
|
)
|
|
|
|
def list_values(self, options: Values, args: List[str]) -> None:
|
|
self._get_n_args(args, "list", n=0)
|
|
|
|
for key, value in sorted(self.configuration.items()):
|
|
write_output("%s=%r", key, value)
|
|
|
|
def get_name(self, options: Values, args: List[str]) -> None:
|
|
key = self._get_n_args(args, "get [name]", n=1)
|
|
value = self.configuration.get_value(key)
|
|
|
|
write_output("%s", value)
|
|
|
|
def set_name_value(self, options: Values, args: List[str]) -> None:
|
|
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
|
self.configuration.set_value(key, value)
|
|
|
|
self._save_configuration()
|
|
|
|
def unset_name(self, options: Values, args: List[str]) -> None:
|
|
key = self._get_n_args(args, "unset [name]", n=1)
|
|
self.configuration.unset_value(key)
|
|
|
|
self._save_configuration()
|
|
|
|
def list_config_values(self, options: Values, args: List[str]) -> None:
|
|
"""List config key-value pairs across different config files"""
|
|
self._get_n_args(args, "debug", n=0)
|
|
|
|
self.print_env_var_values()
|
|
# Iterate over config files and print if they exist, and the
|
|
# key-value pairs present in them if they do
|
|
for variant, files in sorted(self.configuration.iter_config_files()):
|
|
write_output("%s:", variant)
|
|
for fname in files:
|
|
with indent_log():
|
|
file_exists = os.path.exists(fname)
|
|
write_output("%s, exists: %r", fname, file_exists)
|
|
if file_exists:
|
|
self.print_config_file_values(variant)
|
|
|
|
def print_config_file_values(self, variant: Kind) -> None:
|
|
"""Get key-value pairs from the file of a variant"""
|
|
for name, value in self.configuration.get_values_in_config(variant).items():
|
|
with indent_log():
|
|
write_output("%s: %s", name, value)
|
|
|
|
def print_env_var_values(self) -> None:
|
|
"""Get key-values pairs present as environment variables"""
|
|
write_output("%s:", "env_var")
|
|
with indent_log():
|
|
for key, value in sorted(self.configuration.get_environ_vars()):
|
|
env_var = f"PIP_{key.upper()}"
|
|
write_output("%s=%r", env_var, value)
|
|
|
|
def open_in_editor(self, options: Values, args: List[str]) -> None:
|
|
editor = self._determine_editor(options)
|
|
|
|
fname = self.configuration.get_file_to_edit()
|
|
if fname is None:
|
|
raise PipError("Could not determine appropriate file.")
|
|
elif '"' in fname:
|
|
# This shouldn't happen, unless we see a username like that.
|
|
# If that happens, we'd appreciate a pull request fixing this.
|
|
raise PipError(
|
|
f'Can not open an editor for a file name containing "\n{fname}'
|
|
)
|
|
|
|
try:
|
|
subprocess.check_call(f'{editor} "{fname}"', shell=True)
|
|
except FileNotFoundError as e:
|
|
if not e.filename:
|
|
e.filename = editor
|
|
raise
|
|
except subprocess.CalledProcessError as e:
|
|
raise PipError(f"Editor Subprocess exited with exit code {e.returncode}")
|
|
|
|
def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
|
|
"""Helper to make sure the command got the right number of arguments"""
|
|
if len(args) != n:
|
|
msg = (
|
|
f"Got unexpected number of arguments, expected {n}. "
|
|
f'(example: "{get_prog()} config {example}")'
|
|
)
|
|
raise PipError(msg)
|
|
|
|
if n == 1:
|
|
return args[0]
|
|
else:
|
|
return args
|
|
|
|
def _save_configuration(self) -> None:
|
|
# We successfully ran a modifying command. Need to save the
|
|
# configuration.
|
|
try:
|
|
self.configuration.save()
|
|
except Exception:
|
|
logger.exception(
|
|
"Unable to save configuration. Please report this as a bug."
|
|
)
|
|
raise PipError("Internal Error.")
|
|
|
|
def _determine_editor(self, options: Values) -> str:
|
|
if options.editor is not None:
|
|
return options.editor
|
|
elif "VISUAL" in os.environ:
|
|
return os.environ["VISUAL"]
|
|
elif "EDITOR" in os.environ:
|
|
return os.environ["EDITOR"]
|
|
else:
|
|
raise PipError("Could not determine editor to use.")
|