flask 问题记录
flask request传递发送文件数据。
前端发送文件数据到一个服务,由此服务转发到另外一个服务。
requests.request 方法 files 参数说明
files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
to add for the file.
- 从前端获取文件列表数据:
file_list = request.files.getlist(“file”)
for file in file_list:
content = file.read()
files_dict[file.filename] = (file.filename, content, file.content_type)
从form表单获取数据:
data = request.form.to_dict() - 转发files数据
response = requests.request(method, endpoint + url, headers=headers, files=files_dict, data=data)
db session 超时
在异步处理数据时,如果进入异步操作时,没有执行db.session.close(),可能导致数据库长连接而报错。
因此在进入长时间的异步操作,请记住关闭当前数据库连接。
flask tls 启动 版本 加密算法设置
待补充
使用chardet解码大文件可能内存泄露,时间超长。建议使用cchardet。
https://github.com/chardet/chardet/issues/29
contains 查询注意通配符 % _
Projects.name.contains(name)
如果不对通配符做另外处理,前端查询 _ % 会查询出所有内容
Projects.name.contains(name,autoescape=True)
https://docs.sqlalchemy.org/en/14/core/type_basics.html#sql-standard-and-multiple-vendor-types
https://www.w3schools.com/sql/sql_like.asp
log打印
‘propagate’: False # True 的时候会执行root 的handle
https://www.cnblogs.com/cwp-bg/p/8946394.html
https://www.pylenin.com/blogs/python-logging-guide/#:~:text=aka%20Root%20logger.-,Root%20Logger,are%20using%20the%20root%20logger.
import os
import stat
from logging.config import dictConfig
from logging.handlers import RotatingFileHandler
class IngestionRotatingFileHandler(RotatingFileHandler):
def doRollover(self):
if self.stream:
self.stream.close()
self.stream = None
if self.backupCount > 0:
for i in range(self.backupCount - 1, 0, -1):
sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
dfn = self.rotation_filename("%s.%d" % (self.baseFilename, i + 1))
if os.path.exists(sfn):
if os.path.exists(dfn):
os.remove(dfn)
os.rename(sfn, dfn)
dfn = self.rotation_filename(self.baseFilename + ".1")
if os.path.exists(dfn):
os.remove(dfn)
self.rotate(self.baseFilename, dfn)
os.chmod(dfn, stat.S_IRUSR)
if not self.delay:
self.stream = self._open()
os.chmod(self.baseFilename, stat.S_IRUSR + stat.S_IWUSR)
LOGGER_PATH = os.getenv("LOGGING_FILE_DIR")
if not LOGGER_PATH:
LOGGER_PATH = os.getenv("LOGGING_FILE_DIR")
if not os.path.exists(LOGGER_PATH):
os.makedirs(LOGGER_PATH)
import logging.handlers
logging.handlers.IngestionRotatingFileHandler = IngestionRotatingFileHandler
log_dict = {
'version': 1,
'disable_existing_loggers': False,
'root': {
# handler中的level会覆盖掉这里的level
'level': 'INFO',
'handlers': ['server', 'console']
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'standard',
'level': 'INFO',
},
'default': {
'class': 'logging.handlers.IngestionRotatingFileHandler',
'level': 'INFO',
'filename': os.path.join(LOGGER_PATH, 'server.log'),
'formatter': 'standard',
'maxBytes': 1024 * 1024 * 50, # 50 MB
'backupCount': 5,
'encoding': 'utf-8',
},
'server': {
'class': 'logging.handlers.IngestionRotatingFileHandler',
'level': 'INFO',
'filename': os.path.join(LOGGER_PATH, 'server.log'),
'formatter': 'standard',
'maxBytes': 1024 * 1024 * 50, # 50 MB
'backupCount': 5,
'encoding': 'utf-8',
},
'error': {
'level': 'ERROR',
'class': 'logging.handlers.IngestionRotatingFileHandler',
'filename': os.path.join(LOGGER_PATH, 'error.log'),
'formatter': 'standard',
'maxBytes': 1024 * 1024 * 50, # 50 MB
'backupCount': 5,
'encoding': 'utf-8',
},
'audit': {
'level': 'INFO',
'class': 'logging.handlers.IngestionRotatingFileHandler',
'filename': os.path.join(LOGGER_PATH, 'audit.log'),
'formatter': 'standard',
'maxBytes': 1024 * 1024 * 50, # 50 MB
'backupCount': 5,
'encoding': 'utf-8',
},
'scheduler': {
'level': 'INFO',
'class': 'logging.handlers.IngestionRotatingFileHandler',
'filename': os.path.join(LOGGER_PATH, 'scheduler.log'),
'formatter': 'standard',
'maxBytes': 1024 * 1024 * 50, # 50 MB
'backupCount': 5,
'encoding': 'utf-8',
},
},
'formatters': {
'standard': {
'format': '[%(asctime)s][%(levelname)s][%(pathname)s:%(funcName)s:%(lineno)d]:%(message)s'
}
},
'loggers': {
'server': {
'handlers': ['server'],
'level': os.getenv('LOG_LEVEL', 'INFO'),
# 是否向作用到root handle上
'propagate': True
},
'error': {
'handlers': ['error'],
# 生产设置为ERROR
'level': 'ERROR',
},
'audit': {
'handlers': ['audit'],
# 生产设置为INFO
'level': os.getenv('LOG_LEVEL', 'INFO'),
'propagate': False
},
'flask': {
'handlers': ['console', 'server'],
'level': 'INFO',
'propagate': False
},
'scheduler': {
'handlers': ['scheduler'],
'level': 'INFO',
}
},
}
dictConfig(log_dict)
__logger_instances = {
'server': logging.getLogger("server"),
'error': logging.getLogger("error"),
'flask': logging.getLogger('flask'),
'audit': logging.getLogger('audit')
}
def getLogger(name):
if name in __logger_instances:
logger = __logger_instances[name]
else:
logger = __logger_instances['server']
return logger
如果本地要打印日志,root handlers里要加上console,并且’propagate’为 True
数据库迁移
from flask_migrate import Migrate, MigrateCommand
def main():
app = create_app()
db.app = app
manager = Manager(app)
manager.add_command('db', MigrateCommand)
manager.run()
1、初始化migrate文件夹,仅第一次时需要执行。
python3 __main__.py db init
2、生成迁移记录
python3 __main__.py db migrate
3、提交更新至数据库
python __main__.py db upgrade
4、查看历史
python3 __main__.py db history
<base> -> xxxx (head), empty message
5、回退
python sql_.py db downgrade 6af28765840d
项目目录初始化
import os
import sys
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, ROOT_DIR)
SERVICE_DIR = os.path.dirname(os.path.realpath(__file__))
sys.path.append(SERVICE_DIR)
国际化
-
初始化,在命令行中进入到工程目录下,创建babel.cfg文件(要扫描的文件类型)。
文件内容如下:[python: **.py]
执行以下命令
pybabel extract -F babel.cfg -o messages.pot . pybabel init -i messages.pot -d translations -l en
执行命令后会在当前目录生成一个 translations 文件夹,翻译模板就在
translations/zh/LC_MESSAGES/messages.po 文件里。 -
增加翻译词条操作说明
-
导入 from flask_babel import gettext as _
-
将需要翻译的文字用 _(“xxxx”) 表示
-
重新执行自动检索
pybabel extract -F babel.cfg -o messages.pot .
在当前目录生成messages.pot , 此时应该增加了 xxxx 词条。
-
执行更新
pybabel update -i messages.pot -d translations
-
translations/en/LC_MESSAGES/messages.po文件对应词条下增加翻译。
-
编译
pybabel compile -d translations
9、
-
【结合swagger 开发flask】
1、生成基础工程 generate server
http://editor.swagger.io/
2、参数类型
参考:https://www.breakyizhan.com/swagger/2952.html
- 在uri中携带的参数:如 datasets/{dataset_id}/status
in: “path”
参数获取:operationId 对应函数的 参数里 def xxx(dataset_id) - 在url中携带的参数:如 xxuri?status=‘failed’
in: “query”
参数获取:operationId 对应函数的 参数里 def xxx(status) - 在请求体里
in: “body”
request.json.get(‘dataset_id’) - 请求体里,类型是form
request.form
request.files
【工程记录】
1、不能把绑定id的事情交给前端。
2、nginx 传输大文件 404,可能是权限不对
open() “xx/nginx/tmp/nginx_client_body/0000000009” failed (13: Permission denied)
chown -R xx /opt/huawei/openresty/nginx/tmp/
3、通过postman form 发送文件
应该把conten-type 去掉,用postman 默认生成的。
4、Mysql默认的字符检索策略:utf8_general_ci,表示不区分大小写;utf8_general_cs表示区分大小写,utf8_bin表示二进制比较,同样也区分大小写 。(注意:在Mysql5.6.10版本中,不支持utf8_genral_cs!!!!)
创建表时,直接设置表的collate属性为utf8_general_cs或者utf8_bin;如果已经创建表,则直接修改字段的Collation属性为utf8_general_cs或者utf8_bin。
5、nginx 转发
注意resolve问题
location /ixxdatasets/show/ {
proxy_set_header Host xxx.com;
proxy_pass https://xxx.com;
}
Nginx 报错 no resolver defined to resolve
起因是使用 ngxin + lua 链接外部机器
原因是 Nginx 0.6.18以后的版本中启用了一个resolver指令,在使用变量来构造某个server地址的时候一定要用resolver指令来指定DNS服务器的地址,所以解决这个问题的方法很简单:在nginx的配置文件中的http{}部分添加一行DNS地址即可
http {
resolver 8.8.8.8;
server {
…
}
}
如果机器支持 ipv6,可以去掉对它的支持,免得产生类似错误。
1
resolver 8.8.8.8 ipv6=off;
6、lua学习!!
lua 脚本
日志打印
ngx.log(ngx.ERR, “==url=” … url … “#####”)
ngx.var.arg与ngx.req.get_uri_args的区别 一个转码 一个不转码
local uri_args = ngx.req.get_uri_args()
local file_name = uri_args['file_name']
local user_obs_path = uri_args['user_obs_path']
local file_name = ngx.var.arg_file_name
local user_obs_path = ngx.var.arg_user_obs_path
7、curl 命令 linux 下& 需要转义成&& 并用在url上加上双引号