bison
bison

Reputation: 747

Combining Optional Passed query filters in Peewee

I am trying to Link a flask server to a Peewee database. I have a Rest GET request that passes data of the form

{'a':1,'b':2, 'filter':{'name':'Foo', 'count':3}}

I want to write a method that converts my filters into a database query and execute it to return their resource:

import datetime
import peewee as pw
import uuid

DATABASE = "Resources.db"

database = pw.SqliteDatabase(DATABASE)


class BaseModel(pw.Model):
    class Meta:
        database = database


class Resource(BaseModel):
    name = pw.CharField(unique=True)
    current_count = pw.IntegerField(default=1)
    total_count = pw.IntegerField(default=1)
    flavor = pw.CharField(default="undefined")
    users = pw.TextField()
    metadata = pw.TextField(default="")
    is_avalible = pw.BooleanField(default=True)
    uuid = pw.UUIDField(primary_key=True, default=uuid.uuid4)
    max_reservation_time = pw.IntegerField(default=10)

    def __str__(self):
        return f"My name is {self.name} {vars(self)}"

This is kinda what my resource looks like. Here is what I am trying to do... (not a working full example)

def filter(filters):

    for i,j in filters.items():
        dq = Resource.select().where(getattr(Resource, i) == j)

    for resource in dq:
        print(resource)

if __name__ == "__main__":
    try:
        database.connect()
    except pw.OperationalError:
        print("Open Connection")
    try:
        create_tables()
    except pw.OperationalError:
        print("Resource table already exists!")

    with database.atomic():
        reso = Resource.create(name="Burns", current_count=4, total_count=5, users="Bar", blah=2)

    filter({'name':"Burns","total_count":5})

Here I would expect to get back: My name is Burns {'__data__': {'uuid': UUID('80808e3a-4b10-47a5-9d4f-ff9ff9ca6f5c'), 'name': 'Burns', 'current_count': 4, 'total_count': 5, 'flavor': 'undefined', 'users': 'Grant', 'metadata': '', 'is_avalible': True, 'max_reservation_time': 10}, '_dirty': set(), '__rel__': {}}I believe I might be able to create individual peewee.expressions and join them some how, I just am not sure how.

Upvotes: 0

Views: 771

Answers (2)

bison
bison

Reputation: 747

Thanks to @coleifer for the reminder. Here was my solution:

OP_MAP = {
    "==": pw.OP.EQ,
    "!=": pw.OP.NE,
    ">": pw.OP.GT,
    "<": pw.OP.LT,
    ">=": pw.OP.GTE,
    "<=": pw.OP.LTE,
}

def _generate_expressions(model, query_filter):
    expressions = []
    for expression in query_filter:
        expressions.append(
            pw.Expression(
                getattr(model, expression["attr"]), OP_MAP[expression["op"]], expression["value"]
            )
        )
    return expressions

def generate_query(model, query_data):
    if query_data.get("filters") is None:
        database_query = model.select()
    else:
        database_query = model.select().where(
            *(_generate_expressions(model, query_data["filters"]))
        )
    return database_query

I pass the type of object I want to create an expression for and operator in the filter data. Iterating over the filters I can build the expressions and combine them.

Upvotes: -2

coleifer
coleifer

Reputation: 26245

Since peewee expressions can be arbitrarily combined using the builtin & and | operators, we'll use the reduce() function to combine the list using the given operand:

def filter(filters):
    expression_list = [getattr(Resource, field) == value
                       for field, value in filters.items()]
    # To combine all expressions with "AND":
    anded_expr = reduce(operator.and_, expression_list)

    # To combine all expressions with "OR":
    ored_expr = reduce(operator.or_, expression_list)

    # Then:
    return Resource.select().where(anded_expr)  # or, ored_expr

Upvotes: 4

Related Questions