2015-08-21 7 views
5

Un sacco di volte, ho scritto le query come la seguente:Scrittura selezionare le funzioni per la misura Pony

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

Quindi, vorrei scrivere la mia versione di select, un'alternativa di esso. Qualcosa mi piace evitare di scrivere ogni volta la prima parte del predicato, if u.available and u.friends > 0.

La mia domanda è più generale: come posso scrivere una funzione come select che accetta argomenti come quelli che il metodo select o il metodo count possono accettare.

risposta

6

Definiamo un'entità User nel seguente modo:

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) 

Ora possiamo definire alcune comode funzioni per recuperare i tipi di uso più frequente di utenti. La prima funzione restituirà gli utenti che non sono disabilitati da un amministratore:

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

In quella funzione io uso select metodo User soggetto che accetta una funzione lambda, ma la stessa funzione può essere scritto utilizzando funzione globale select che accetta un generatore di espressione:

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

Il risultato di active_users funzione è un oggetto query. È possibile chiamare il metodo filter dell'oggetto query per produrre una query più specifica. Ad esempio, posso usare active_users funzione per selezionare gli utenti attivi i cui nomi iniziano con la 'A' lettera:

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

Ora voglio trovare gli utenti che visitano il sito in pochi ultimi giorni. Posso definire un'altra funzione che utilizza la query restituito dalla funzione precedente e aumentare nel seguente modo:

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

In questo esempio passare l'argomento days alla funzione e utilizzare il suo valore all'interno del filtro.

È possibile definire un insieme di tali funzioni che formeranno il livello di accesso ai dati dell'applicazione. Alcuni altri esempi:

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] 

Negli esempi sopra definisco le funzioni globali. Un'altra opzione è quella di definire queste funzioni come classmethods del User entità:

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] 

Se si potrebbe desiderare di avere una funzione generica che può essere applicata a diverse classi di entità, allora è necessario passare la classe di entità come parametro. Ad esempio, se un certo numero di classi diverse hanno l'attributo deleted, e si desidera avere un metodo generico per selezionare solo gli oggetti non eliminati, è possibile scrivere qualcosa di simile:

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

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

Tutte le funzioni sopra riportate sono uno svantaggio - non sono componibili. Non è possibile ottenere una query da una funzione e aumentarla con un'altra funzione. Se si desidera avere una funzione che può aumentare la query esistente, quella funzione dovrebbe accettare la query come argomento.Esempio:

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')) 

La funzione name_starts_with può essere applicato a un'altra domanda:

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

Inoltre stiamo lavorando sull'estensione API interrogazione che permetterà un programmatore di scrivere metodi di query personalizzati. Quando rilasciamo questa API sarà possibile suddividere i metodi di query personalizzati nel seguente modo:

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

Spero di aver risposto alla tua domanda. Se questo è il caso, si prega di accettare la risposta come corretta.

Problemi correlati