Доступ к табличным переменным в запросе Flask-Sqlalchemy во время поиска - PullRequest
0 голосов
/ 30 октября 2018

Я пишу RESTful API, используя Flask и Flask-SQLalchemy, а также Flask-Login. У меня есть модель Users, которая инициализируется как:

class Users(UserMixin, db.Model):
    __tablename__ = "users"
    # STATIC Standard required info
    id = db.Column("id", db.Integer, primary_key = True)
    public_id = db.Column("public_id", db.String(50), unique = True)
    email = db.Column("email", db.String(50), unique = True)
    username = db.Column("username", db.String(20), unique = True)
    password = db.Column("password", db.String(20))

    # DYNAMIC Standard required info
    lat = db.Column("lat", db.Float)
    lon = db.Column("lon", db.Float)

В таблице есть другие элементы, но наиболее важны лат и лон. У меня есть метод distanceMath(), который учитывает текущую зарегистрированную широту и долготу пользователя, а также широту и долготу из таблицы, чтобы вычислить расстояние между ними. Но я не могу понять, как получить доступ к значениям таблицы во время следующего запроса, который вызывает distanceMath без получения синтаксической ошибки:

lat1 = current_user.lat
lon1 = current_user.lon
users = Users.query.filter(distanceMath(lat1, Users.lat, lon1, Users.lon)==1)
    output = []
    for user in users:
        data = {}
        data["username"] = user.username
        output.append(data)
    return str(output)

На всякий случай вот метод distanceMath:

# Haversine Distance
def distanceMath(lat1, lat2, lon1, lon2):
    distance = math.acos(math.sin(math.radians(lat1))*math.sin(math.radians(lat2))) + math.cos(math.radians(lat1))*math.cos(math.radians(lat2))*math.cos(math.radians(lon1)-math.radians(lon2))
    return distance

Пример ошибки:

TypeError: must be real number, not InstrumentedAttribute

Что, в моем понимании, в сущности говорит о том, что User.lat и User.lon не ссылаются на число с плавающей запятой или даже число, а вместо этого на атрибут таблицы. Мой вопрос заключается в том, как на самом деле использовать то, что lat и lon равны (в базе данных они равны 37,7 и -122,4 соответственно).

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Я решил это, взяв функцию distanceMath и поместив ее в мой класс модели Users и передав ей "self" и "math", а также сославшись на значения таблицы соответственно:

    def distanceMath(self, lat1, lon1, math=math):
        distance = math.acos(math.sin(math.radians(lat1))*math.sin(math.radians(self.lat))) + math.cos(math.radians(lat1))*math.cos(math.radians(self.lat))*math.cos(math.radians(lon1)-math.radians(self.lon))
        return distance

Затем при выполнении запроса просто передайте current_user как экземпляр Users, и все будет отлично работать:

    users = Users.query.filter(current_user.distanceMath(lat1, lon1) == 1).all()
0 голосов
/ 30 октября 2018

Ваша интуиция верна, и ошибка является результатом передачи инструментированных атрибутов SQLAlchemy в библиотечные функции Python math, которые не имеют ни малейшего представления, что с ними делать.

Чтобы выполнить запрос на основе расстояния, вы можете либо извлечь все данные из базы данных и отфильтровать в Python (обычно это нежелательно), либо отправить операцию в СУБД в запросе для оценки. Хитрость заключается в том, чтобы сформировать подходящее выражение SQL. Вместо вызова математических функций Python вам нужно создать выражения функций SQL с использованием func:

# Bind math as a keyword argument and provide Python's math module as the default
def distanceMath(lat1, lat2, lon1, lon2, math=math):
    distance = math.acos(math.sin(math.radians(lat1))*math.sin(math.radians(lat2))) + math.cos(math.radians(lat1))*math.cos(math.radians(lat2))*math.cos(math.radians(lon1)-math.radians(lon2))
    return distance

и в представлении:

users = Users.query.filter(distanceMath(lat1, Users.lat, lon1, Users.lon, math=db.func) == 1)

Вы также можете обернуть вычисления расстояния в гибридный атрибут , который будет обрабатывать либо создание выражения SQL, либо выполнение вычислений для экземпляров в Python.

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

import math
from sqlalchemy import event

# Before anything else DB related is performed:
event.listens_for(db.get_engine(), 'connect')
def create_math_functions_on_connect(dbapi_connection, connection_record):
    dbapi_connection.create_function('sin', 1, math.sin)
    dbapi_connection.create_function('cos', 1, math.cos)
    dbapi_connection.create_function('acos', 1, math.acos)
    dbapi_connection.create_function('radians', 1, math.radians)
...