Flask API молча умирает во время курсора.executePython

Программы на Python
Anonymous
Flask API молча умирает во время курсора.execute

Сообщение Anonymous »

У меня есть простой API Flask, используемый для извлечения данных из разных баз данных для отображения в приложении реагирования. Недавно я выпустил новую версию API, которая, похоже, сломала его, хотя я не знаю почему. Я сузил точку отказа до вызова курсора.execute в следующих функциях:

Код: Выделить всё

def get_sites(self):
query = "SELECT * FROM some_table"

cursor = self.conn.cursor(dictionary=True)
cursor.execute(query)
results = cursor.fetchall()
cursor.close()

return results

def get_assets(self):
query = "SELECT * FROM some_table"

cursor = self.conn.cursor(dictionary=True)
cursor.execute(query)
results = cursor.fetchall()
cursor.close()

return results
Иногда возникает ошибка «Потеряно соединение с сервером MySQL во время запроса», но это происходит не каждый раз; обычно он просто тихо умирает.
Вот как я устанавливаю и сохраняю SQL-соединения для использования в верхней части моего приложения Flask:

Код: Выделить всё

CUSTOMER_DBS = {}
customer_db_routing_config = configparser.ConfigParser()
customer_db_routing_config.optionxform = str
customer_db_routing_config.read_file(open("customer_db_routing.ini"))
for token in customer_db_routing_config['customers']:
customer_db_config = configparser.ConfigParser()
customer_db_config.read_file(open(customer_db_routing_config['customers'][token]))
customer_db_conn = DatabaseInterface(customer_db_config)
CUSTOMER_DBS[token] = customer_db_conn
Где DatabaseInterface — это специальный класс для выполнения запросов со следующей init:

Код: Выделить всё

def __init__(self, config):

self.config = config

self.conn = mysql.connector.connect(user=config['mysql']['user'],
password=config['mysql']['password'],
host=config['mysql']['host'])
cursor = self.conn.cursor()
cursor.execute("SET SESSION TRANSACTION READ ONLY")
Да, я знаю, что не закрываю курсор, однако пока я оставил все как есть, поскольку это не влияет на ошибку.
Вот два маршрута, на которых возникает проблема:

Код: Выделить всё

@app.route("/api/customer//site")
@key_required
def sites(key, token):
if token in FILTERED_TOKENS:
return {"error": f"No valid customer with token {token}" }

site_name = request.args.get("name")
site_type = request.args.get("type")

if len(request.args) > 2:
return {"error": "Only ONE of the following parameters may be used: name, type"}

# use customer token to get path to customer db config
if not CUSTOMER_DBS[token].conn.is_connected():
CUSTOMER_DBS[token].conn.reconnect()

# get sites from customer db
if len(request.args) == 1:
results = CUSTOMER_DBS[token].get_sites()
elif site_name:
results = CUSTOMER_DBS[token].get_site_by_name(site_name)
elif site_type:
results = CUSTOMER_DBS[token].get_sites_by_type(site_type)

# base64 encode logo png and info_json so they are json serializable
for row in results:
if row["logo"]:
row["logo"] = base64.b64encode(row["logo"]).decode("utf-8")
# this column is oddly missing from 
if "info_json" in row.keys():
if row["info_json"]:
row["info_json"] = base64.b64encode(row["info_json"]).decode("utf-8")

return results

@app.route("/api/customer//asset")
@key_required
def assets(key, token):
if token in FILTERED_TOKENS:
return {"error": f"No valid customer with token {token}"}

asset_name = request.args.get("name")
site_name = request.args.get("site_name")
site_id = request.args.get("site_id")
superasset_id = request.args.get("superasset_id")
superasset_name = request.args.get("superasset_name")
address = request.args.get("address")

if len(request.args) >  2:
return {"error": "Only ONE of the following parameters may be used: name, site_name, site_id, superasset_id, superasset_name, address"}

# use customer token to get path to customer db config
if not CUSTOMER_DBS[token].conn.is_connected():
CUSTOMER_DBS[token].conn.reconnect()

# get assets from customer db
if len(request.args) == 1:
results = CUSTOMER_DBS[token].get_assets()
elif asset_name:
results = CUSTOMER_DBS[token].get_asset_by_name(asset_name)
elif site_name:
results = CUSTOMER_DBS[token].get_assets_by_site_name(site_name)
elif site_id:
results = CUSTOMER_DBS[token].get_assets_by_site_id(site_id)
elif superasset_id:
results = CUSTOMER_DBS[token].get_assets_by_superasset_id(superasset_id)
elif superasset_name:
results = CUSTOMER_DBS[token].get_assets_by_superasset_name(superasset_name)
elif address:
results = CUSTOMER_DBS[token].get_assets_by_address(address)

# base64 encoding blobs to avoid errors when returning
for row in results:
if row["info_json"]:
row["info_json"] = base64.b64encode(row["info_json"]).decode("utf-8")
if row["rt_condition_json"]:
row["rt_condition_json"] = base64.b64encode(row["rt_condition_json"]).decode("utf-8")

# filter out NAA
results = [ row for row in results if row["asset_tag"] != "NAA" ]
return results
Еще одна интересная информация: проблема возникает на определенной странице приложения реагирования, которое вызывает оба этих маршрута. Однако, если я вернусь к предыдущей версии API, она заработает, поэтому я почти уверен, что проблема не в компоненте реагирования. Главное, что я изменил в новой версии API, — это создание соединений с БД при запуске и сохранение их в словаре. Раньше каждый маршрут создавал новое соединение с БД, а затем закрывал его после выполнения запроса. Я думаю, что это как-то связано с проблемой, однако этот метод хранения постоянных подключений к БД я использовал раньше в других приложениях, поэтому я действительно в растерянности. В консоли браузера я вижу ERR_CONNECTION_RESET, но в остальном это в основном тихий сбой.
EDIT: Нашел проблему. Это было в этом коде компонента реагирования:

Код: Выделить всё

useEffect(() => {
// track the selected customer by saving the token passed via useLocation
setCustomerToken(location.state.token)

// get the install sites of the selected token
client.get_sites(location.state.token).then((res) => {
setInstallSites(res) // extract only the customer names, that's all we need res.map(site => site.customer_name)
console.log(res)
})
.catch((error) => {
console.error('Error fetching customers:', error);
});
client.get_assets(location.state.token).then((res) => {
setAssets(res.map(asset => asset.asset_tag)) // extract only asset tags, that's all we need
})
.catch((error) => {
console.error('Error fetching assets:', error);
});
}, []);
Я думаю, что происходит следующее: поскольку эти клиентские методы являются асинхронными (они возвращают обещания), второй метод пытается выполнить запрос к БД, в то время как первый еще не завершен. Это должно привести к тому, что mysql-connector-python и, следовательно, Flask запаникуют и по какой-то причине закроются. Это можно исправить, обеспечив, чтобы эти вызовы выполнялись один за другим, используя .then, хотя мне это не очень нравится.

Подробнее здесь: https://stackoverflow.com/questions/786 ... or-execute

Вернуться в «Python»