Написание функций выбора для настроенного Пони

Много раз я писал такие запросы:

pony.orm.select(u for u in User if u.available and u.friends > 0 and ...)

Итак, я хотел бы написать свою собственную версию selectальтернатива этому. Что-то вроде того, чтобы я не писал каждый раз первую часть предиката, if u.available and u.friends > 0,

Мой вопрос более общий: как я могу написать такую ​​функцию, как select что он принимает аргументы, подобные тем, которые select метод или count метод может принять.

1 ответ

Решение

Давайте определим User сущность следующим образом:

from datetime import datetime, timedelta
from pony.orm import *

db = Database('sqlite', ':memory:')

class User(db.Entity):
    username = Required(str, unique=True)
    password = Required(str)
    friends = Set("User", reverse='friends')  # many-to-many symmetric relation
    online = Required(bool, default=False)    # if user is currently online
    last_visit = Optional(datetime)           # last visit time
    disabled = Required(bool, default=False)  # if user is disabled by administrator

sql_debug(True)
db.generate_mapping(create_tables=True)

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

def active_users():
    return User.select(lambda user: not user.disabled)

В этой функции я использую select метод User Сущность, которая принимает лямбда-функцию, но ту же функцию можно написать с помощью глобальной select функция, которая принимает выражение генератора:

def active_users():
    return select(u for u in User if not user.disabled)

Результат active_users Функция является объектом запроса. Ты можешь позвонить filter метод объекта запроса для создания более конкретного запроса. Например, я могу использовать active_users Функция для выбора активных пользователей, имена которых начинаются с буквы "А":

users = active_users().filter(lambda user: user.name.startswith('A')) \
                      .order_by(User.name)[:10]

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

def recent_users(days=1):
    return active_users().filter(lambda u: u.last_visit > datetime.now() - timedelta(days))

В этом примере я передаю days аргумент функции и использовать его значение внутри фильтра.

Вы можете определить набор таких функций, которые будут формировать слой доступа к данным вашего приложения. Еще несколько примеров:

def users_with_at_least_n_friends(n=1):
    return active_users().filter(lambda u: count(u.friends) >= n)

def online_users():
    return User.select(lambda u: u.online)

def online_users_with_at_least_n_online_friends(n=1):
    return online_users().filter(lambda u: count(f for f in u.friends if f.online) >= n)

users = online_users_with_at_least_n_online_friends(n=10) \
            .order_by(User.name)[:10]

В приведенных выше примерах я определяю глобальные функции. Другим вариантом является определение этих функций как методов класса User юридическое лицо:

class User(db.Entity):
    username = Required(str, unique=True)
    ...
    @classmethod
    def name_starts_with(cls, prefix):
        return cls.select(lambda user: not user.disabled
                                       and user.name.startswith(prefix))

...
users = User.name_starts_with('A').order_by(desc(User.last_visit))[:10]

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

def select_active(cls):
    return cls.select(lambda obj: not obj.deleted)

select_active(Message).filter(lambda msg: msg.author == current_user)

Все функции, представленные выше, имеют один недостаток - они не могут быть компонованы. Вы не можете получить запрос от одной функции и дополнить его другой функцией. Если вы хотите иметь функцию, которая может дополнять существующий запрос, эта функция должна принять запрос в качестве аргумента. Пример:

def active_users():
    return User.select(lambda user: not user.disabled)

def name_starts_with(query, prefix):
    return query.filter(lambda user: user.name.startswith('prefix'))

name_starts_with Функция может быть применена к другому запросу:

users1 = name_starts_with(active_users(), 'A').order_by(User.last_visited)
users2 = name_starts_with(recent_users(), 'B').filter(lambda user: user.online)

Также мы работаем над API расширения запросов, который позволит программисту создавать собственные методы запросов. Когда мы выпустим этот API, можно будет просто объединить пользовательские методы запросов следующим образом:

select(u for u in User).recent(days=3).name_starts_with('A')[:10]

Надеюсь, я ответил на ваш вопрос. Если это так, пожалуйста, примите ответ как правильный.

Другие вопросы по тегам