- 新增图像生成接口,支持试用、积分和自定义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): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
420 lines
13 KiB
Python
420 lines
13 KiB
Python
from six import PY2
|
|
|
|
from functools import wraps
|
|
|
|
from datetime import datetime, timedelta, tzinfo
|
|
|
|
|
|
ZERO = timedelta(0)
|
|
|
|
__all__ = ['tzname_in_python2', 'enfold']
|
|
|
|
|
|
def tzname_in_python2(namefunc):
|
|
"""Change unicode output into bytestrings in Python 2
|
|
|
|
tzname() API changed in Python 3. It used to return bytes, but was changed
|
|
to unicode strings
|
|
"""
|
|
if PY2:
|
|
@wraps(namefunc)
|
|
def adjust_encoding(*args, **kwargs):
|
|
name = namefunc(*args, **kwargs)
|
|
if name is not None:
|
|
name = name.encode()
|
|
|
|
return name
|
|
|
|
return adjust_encoding
|
|
else:
|
|
return namefunc
|
|
|
|
|
|
# The following is adapted from Alexander Belopolsky's tz library
|
|
# https://github.com/abalkin/tz
|
|
if hasattr(datetime, 'fold'):
|
|
# This is the pre-python 3.6 fold situation
|
|
def enfold(dt, fold=1):
|
|
"""
|
|
Provides a unified interface for assigning the ``fold`` attribute to
|
|
datetimes both before and after the implementation of PEP-495.
|
|
|
|
:param fold:
|
|
The value for the ``fold`` attribute in the returned datetime. This
|
|
should be either 0 or 1.
|
|
|
|
:return:
|
|
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
|
``fold`` for all versions of Python. In versions prior to
|
|
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
|
subclass of :py:class:`datetime.datetime` with the ``fold``
|
|
attribute added, if ``fold`` is 1.
|
|
|
|
.. versionadded:: 2.6.0
|
|
"""
|
|
return dt.replace(fold=fold)
|
|
|
|
else:
|
|
class _DatetimeWithFold(datetime):
|
|
"""
|
|
This is a class designed to provide a PEP 495-compliant interface for
|
|
Python versions before 3.6. It is used only for dates in a fold, so
|
|
the ``fold`` attribute is fixed at ``1``.
|
|
|
|
.. versionadded:: 2.6.0
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def replace(self, *args, **kwargs):
|
|
"""
|
|
Return a datetime with the same attributes, except for those
|
|
attributes given new values by whichever keyword arguments are
|
|
specified. Note that tzinfo=None can be specified to create a naive
|
|
datetime from an aware datetime with no conversion of date and time
|
|
data.
|
|
|
|
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
|
|
return a ``datetime.datetime`` even if ``fold`` is unchanged.
|
|
"""
|
|
argnames = (
|
|
'year', 'month', 'day', 'hour', 'minute', 'second',
|
|
'microsecond', 'tzinfo'
|
|
)
|
|
|
|
for arg, argname in zip(args, argnames):
|
|
if argname in kwargs:
|
|
raise TypeError('Duplicate argument: {}'.format(argname))
|
|
|
|
kwargs[argname] = arg
|
|
|
|
for argname in argnames:
|
|
if argname not in kwargs:
|
|
kwargs[argname] = getattr(self, argname)
|
|
|
|
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
|
|
|
|
return dt_class(**kwargs)
|
|
|
|
@property
|
|
def fold(self):
|
|
return 1
|
|
|
|
def enfold(dt, fold=1):
|
|
"""
|
|
Provides a unified interface for assigning the ``fold`` attribute to
|
|
datetimes both before and after the implementation of PEP-495.
|
|
|
|
:param fold:
|
|
The value for the ``fold`` attribute in the returned datetime. This
|
|
should be either 0 or 1.
|
|
|
|
:return:
|
|
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
|
``fold`` for all versions of Python. In versions prior to
|
|
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
|
subclass of :py:class:`datetime.datetime` with the ``fold``
|
|
attribute added, if ``fold`` is 1.
|
|
|
|
.. versionadded:: 2.6.0
|
|
"""
|
|
if getattr(dt, 'fold', 0) == fold:
|
|
return dt
|
|
|
|
args = dt.timetuple()[:6]
|
|
args += (dt.microsecond, dt.tzinfo)
|
|
|
|
if fold:
|
|
return _DatetimeWithFold(*args)
|
|
else:
|
|
return datetime(*args)
|
|
|
|
|
|
def _validate_fromutc_inputs(f):
|
|
"""
|
|
The CPython version of ``fromutc`` checks that the input is a ``datetime``
|
|
object and that ``self`` is attached as its ``tzinfo``.
|
|
"""
|
|
@wraps(f)
|
|
def fromutc(self, dt):
|
|
if not isinstance(dt, datetime):
|
|
raise TypeError("fromutc() requires a datetime argument")
|
|
if dt.tzinfo is not self:
|
|
raise ValueError("dt.tzinfo is not self")
|
|
|
|
return f(self, dt)
|
|
|
|
return fromutc
|
|
|
|
|
|
class _tzinfo(tzinfo):
|
|
"""
|
|
Base class for all ``dateutil`` ``tzinfo`` objects.
|
|
"""
|
|
|
|
def is_ambiguous(self, dt):
|
|
"""
|
|
Whether or not the "wall time" of a given datetime is ambiguous in this
|
|
zone.
|
|
|
|
:param dt:
|
|
A :py:class:`datetime.datetime`, naive or time zone aware.
|
|
|
|
|
|
:return:
|
|
Returns ``True`` if ambiguous, ``False`` otherwise.
|
|
|
|
.. versionadded:: 2.6.0
|
|
"""
|
|
|
|
dt = dt.replace(tzinfo=self)
|
|
|
|
wall_0 = enfold(dt, fold=0)
|
|
wall_1 = enfold(dt, fold=1)
|
|
|
|
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
|
|
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
|
|
|
|
return same_dt and not same_offset
|
|
|
|
def _fold_status(self, dt_utc, dt_wall):
|
|
"""
|
|
Determine the fold status of a "wall" datetime, given a representation
|
|
of the same datetime as a (naive) UTC datetime. This is calculated based
|
|
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
|
|
datetimes, and that this offset is the actual number of hours separating
|
|
``dt_utc`` and ``dt_wall``.
|
|
|
|
:param dt_utc:
|
|
Representation of the datetime as UTC
|
|
|
|
:param dt_wall:
|
|
Representation of the datetime as "wall time". This parameter must
|
|
either have a `fold` attribute or have a fold-naive
|
|
:class:`datetime.tzinfo` attached, otherwise the calculation may
|
|
fail.
|
|
"""
|
|
if self.is_ambiguous(dt_wall):
|
|
delta_wall = dt_wall - dt_utc
|
|
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
|
|
else:
|
|
_fold = 0
|
|
|
|
return _fold
|
|
|
|
def _fold(self, dt):
|
|
return getattr(dt, 'fold', 0)
|
|
|
|
def _fromutc(self, dt):
|
|
"""
|
|
Given a timezone-aware datetime in a given timezone, calculates a
|
|
timezone-aware datetime in a new timezone.
|
|
|
|
Since this is the one time that we *know* we have an unambiguous
|
|
datetime object, we take this opportunity to determine whether the
|
|
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
|
occurrence, chronologically, of the ambiguous datetime).
|
|
|
|
:param dt:
|
|
A timezone-aware :class:`datetime.datetime` object.
|
|
"""
|
|
|
|
# Re-implement the algorithm from Python's datetime.py
|
|
dtoff = dt.utcoffset()
|
|
if dtoff is None:
|
|
raise ValueError("fromutc() requires a non-None utcoffset() "
|
|
"result")
|
|
|
|
# The original datetime.py code assumes that `dst()` defaults to
|
|
# zero during ambiguous times. PEP 495 inverts this presumption, so
|
|
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
|
|
dtdst = dt.dst()
|
|
if dtdst is None:
|
|
raise ValueError("fromutc() requires a non-None dst() result")
|
|
delta = dtoff - dtdst
|
|
|
|
dt += delta
|
|
# Set fold=1 so we can default to being in the fold for
|
|
# ambiguous dates.
|
|
dtdst = enfold(dt, fold=1).dst()
|
|
if dtdst is None:
|
|
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
|
"results; cannot convert")
|
|
return dt + dtdst
|
|
|
|
@_validate_fromutc_inputs
|
|
def fromutc(self, dt):
|
|
"""
|
|
Given a timezone-aware datetime in a given timezone, calculates a
|
|
timezone-aware datetime in a new timezone.
|
|
|
|
Since this is the one time that we *know* we have an unambiguous
|
|
datetime object, we take this opportunity to determine whether the
|
|
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
|
occurrence, chronologically, of the ambiguous datetime).
|
|
|
|
:param dt:
|
|
A timezone-aware :class:`datetime.datetime` object.
|
|
"""
|
|
dt_wall = self._fromutc(dt)
|
|
|
|
# Calculate the fold status given the two datetimes.
|
|
_fold = self._fold_status(dt, dt_wall)
|
|
|
|
# Set the default fold value for ambiguous dates
|
|
return enfold(dt_wall, fold=_fold)
|
|
|
|
|
|
class tzrangebase(_tzinfo):
|
|
"""
|
|
This is an abstract base class for time zones represented by an annual
|
|
transition into and out of DST. Child classes should implement the following
|
|
methods:
|
|
|
|
* ``__init__(self, *args, **kwargs)``
|
|
* ``transitions(self, year)`` - this is expected to return a tuple of
|
|
datetimes representing the DST on and off transitions in standard
|
|
time.
|
|
|
|
A fully initialized ``tzrangebase`` subclass should also provide the
|
|
following attributes:
|
|
* ``hasdst``: Boolean whether or not the zone uses DST.
|
|
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
|
|
representing the respective UTC offsets.
|
|
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
|
|
abbreviations in DST and STD, respectively.
|
|
* ``_hasdst``: Whether or not the zone has DST.
|
|
|
|
.. versionadded:: 2.6.0
|
|
"""
|
|
def __init__(self):
|
|
raise NotImplementedError('tzrangebase is an abstract base class')
|
|
|
|
def utcoffset(self, dt):
|
|
isdst = self._isdst(dt)
|
|
|
|
if isdst is None:
|
|
return None
|
|
elif isdst:
|
|
return self._dst_offset
|
|
else:
|
|
return self._std_offset
|
|
|
|
def dst(self, dt):
|
|
isdst = self._isdst(dt)
|
|
|
|
if isdst is None:
|
|
return None
|
|
elif isdst:
|
|
return self._dst_base_offset
|
|
else:
|
|
return ZERO
|
|
|
|
@tzname_in_python2
|
|
def tzname(self, dt):
|
|
if self._isdst(dt):
|
|
return self._dst_abbr
|
|
else:
|
|
return self._std_abbr
|
|
|
|
def fromutc(self, dt):
|
|
""" Given a datetime in UTC, return local time """
|
|
if not isinstance(dt, datetime):
|
|
raise TypeError("fromutc() requires a datetime argument")
|
|
|
|
if dt.tzinfo is not self:
|
|
raise ValueError("dt.tzinfo is not self")
|
|
|
|
# Get transitions - if there are none, fixed offset
|
|
transitions = self.transitions(dt.year)
|
|
if transitions is None:
|
|
return dt + self.utcoffset(dt)
|
|
|
|
# Get the transition times in UTC
|
|
dston, dstoff = transitions
|
|
|
|
dston -= self._std_offset
|
|
dstoff -= self._std_offset
|
|
|
|
utc_transitions = (dston, dstoff)
|
|
dt_utc = dt.replace(tzinfo=None)
|
|
|
|
isdst = self._naive_isdst(dt_utc, utc_transitions)
|
|
|
|
if isdst:
|
|
dt_wall = dt + self._dst_offset
|
|
else:
|
|
dt_wall = dt + self._std_offset
|
|
|
|
_fold = int(not isdst and self.is_ambiguous(dt_wall))
|
|
|
|
return enfold(dt_wall, fold=_fold)
|
|
|
|
def is_ambiguous(self, dt):
|
|
"""
|
|
Whether or not the "wall time" of a given datetime is ambiguous in this
|
|
zone.
|
|
|
|
:param dt:
|
|
A :py:class:`datetime.datetime`, naive or time zone aware.
|
|
|
|
|
|
:return:
|
|
Returns ``True`` if ambiguous, ``False`` otherwise.
|
|
|
|
.. versionadded:: 2.6.0
|
|
"""
|
|
if not self.hasdst:
|
|
return False
|
|
|
|
start, end = self.transitions(dt.year)
|
|
|
|
dt = dt.replace(tzinfo=None)
|
|
return (end <= dt < end + self._dst_base_offset)
|
|
|
|
def _isdst(self, dt):
|
|
if not self.hasdst:
|
|
return False
|
|
elif dt is None:
|
|
return None
|
|
|
|
transitions = self.transitions(dt.year)
|
|
|
|
if transitions is None:
|
|
return False
|
|
|
|
dt = dt.replace(tzinfo=None)
|
|
|
|
isdst = self._naive_isdst(dt, transitions)
|
|
|
|
# Handle ambiguous dates
|
|
if not isdst and self.is_ambiguous(dt):
|
|
return not self._fold(dt)
|
|
else:
|
|
return isdst
|
|
|
|
def _naive_isdst(self, dt, transitions):
|
|
dston, dstoff = transitions
|
|
|
|
dt = dt.replace(tzinfo=None)
|
|
|
|
if dston < dstoff:
|
|
isdst = dston <= dt < dstoff
|
|
else:
|
|
isdst = not dstoff <= dt < dston
|
|
|
|
return isdst
|
|
|
|
@property
|
|
def _dst_base_offset(self):
|
|
return self._dst_offset - self._std_offset
|
|
|
|
__hash__ = None
|
|
|
|
def __ne__(self, other):
|
|
return not (self == other)
|
|
|
|
def __repr__(self):
|
|
return "%s(...)" % self.__class__.__name__
|
|
|
|
__reduce__ = object.__reduce__
|