# Filter Expressions

## Overview

**Filter expressions** enable you to define filter criteria for your procedures using the **whose** keyword. These expressions allow you to filter data based on conditions such as equality, comparisons, and more. For example:

```
get users from office365 whose email is "john@mail.org"
```

## Implementation

### 1. Include the `filter_expression` parameter

To implement a filter expression, you need to provide the special `filter_expression` parameter in your procedure method definition. For example:

```python
@procedure("to read some (*SMS* messages)")
def read_sms_messages(
    self,
    offset: Optional[int],
    limit: Optional[int],
    filter_expression: Optional[FilterExpression],
) -> List[SMSMessage]:
    """
    Read some SMS messages using the Twilio API.
    """
```

### 2. Implement Visitors

The `FilterExpressionVisitor` class is an abstract base class that defines methods for visiting different types of filter expressions. Each type of filter expression (binary, unary, value, noun phrase) is defined as a subclass of `FilterExpression`. You will need to define a class that implements these methods to handle filter expressions. For example:

```python
class MyFilterVisitor(FilterExpressionVisitor):
    def visit_binary_expression(self, expression: FilterBinaryExpression) -> T:
        # Implement logic for handling binary expressions
        pass

    def visit_unary_expression(self, expression: FilterUnaryExpression) -> T:
        # Implement logic for handling unary expressions
        pass

    def visit_value(self, expression: ValueExpression) -> T:
        # Implement logic for handling value expressions
        pass

    def visit_noun_phrases(self, expression: NounPhrasesExpression) -> T:
        # Implement logic for handling noun phrases expressions
        pass
```

Below is an example implementation of the `FilterExpressionVisitor` class in the Twilio book:

```python
SENDER_NUMBER = NounPhrase.from_word_list(["sender", "number"])
RECIPIENT_NUMBER = NounPhrase.from_word_list(["recipient", "number"])
DATE_SENT = NounPhrase.from_word_list(["date", "sent"])


class SMSMessageFilter(FilterExpressionVisitor):
    """
    Extract filtering information from the expression
    """

    current_noun_phrase: Optional[NounPhrase] = None
    current_value: Optional[Any] = None

    recipient_number: Union[str, object] = values.unset
    sender_number: Union[str, object] = values.unset
    date_sent: Union[datetime, object] = values.unset
    date_sent_before: Union[datetime, object] = values.unset
    date_sent_after: Union[datetime, object] = values.unset

    def visit_binary_expression(self, expression: FilterBinaryExpression):
        expression.left.accept(self)
        expression.right.accept(self)

        if expression.operator == FilterBinaryOperator.EQUALS:
            if self.current_noun_phrase == SENDER_NUMBER:
                self.sender_number = str(self.current_value)
            elif self.current_noun_phrase == RECIPIENT_NUMBER:
                self.recipient_number = str(self.current_value)
            elif self.current_noun_phrase == DATE_SENT:
                if not isinstance(self.current_value, datetime):
                    raise TypeMismatchError("date_sent", ConceptScalarType.DATETIME)

                self.date_sent = self.current_value
        elif expression.operator == FilterBinaryOperator.GREATER_THAN:
            if self.current_noun_phrase == DATE_SENT:
                if not isinstance(self.current_value, datetime):
                    raise TypeMismatchError("date_sent", ConceptScalarType.DATETIME)

                self.date_sent_after = self.current_value
        elif expression.operator == FilterBinaryOperator.LESS_THAN:
            if self.current_noun_phrase == DATE_SENT:
                if not isinstance(self.current_value, datetime):
                    raise TypeMismatchError("date_sent", ConceptScalarType.DATETIME)

                self.date_sent_before = self.current_value
        elif expression.operator == FilterBinaryOperator.AND:
            pass
        else:
            raise ValueError(f"unsupported filtering operator: {expression.operator}")

    def visit_unary_expression(self, expression: FilterUnaryExpression):
        pass

    def visit_value(self, expression: ValueExpression):
        self.current_value = expression.value

    def visit_noun_phrases(self, expression: NounPhrasesExpression):
        if len(expression.noun_phrases) != 1:
            raise ValueError(
                f"unsupported filtering noun phrase: {expression.noun_phrases}"
            )

        if expression.noun_phrases[0] not in [
            SENDER_NUMBER,
            RECIPIENT_NUMBER,
            DATE_SENT,
        ]:
            raise ValueError(
                f"unsupported filtering noun phrase: {expression.noun_phrases}"
            )

        self.current_noun_phrase = expression.noun_phrases[0]
```

### 3. Processing Filter Expressions

Once you’ve defined your visitor class, you need to pass an instance of it to the filter expression. This is done by calling **accept** on`filter_expression`. For example:

```python
visitor = MyFilterVisitor()
filter_expression.accept(visitor)
```

***

## Example

In this example, a filter expression is used in the **read some SMS messages** procedure:

```python
@procedure("to read some (*SMS* messages)")
def read_sms_messages(
    self,
    offset: Optional[int],
    limit: Optional[int],
    filter_expression: Optional[FilterExpression],
) -> List[SMSMessage]:
    """
    Read some SMS messages using the Twilio API.

    Returns:
        A list of SMS messages that matches the specified filtering criteria

    Example 1:
        Retrieve SMS messages filtered by sender and recipient numbers

        >>> read some sms messages whose sender number is "+18004445555" and whose recipient number is "+18004446666"

    Example 2:
        Retrieve SMS messages filtered by the date in which they were sent

        >>> convert "2022-03-01T15:00:00Z" to a datetime
        >>> use the above as the message date
        >>> read some sms messages whose date sent is the message date

    Example 3:
        Retrieve SMS messages that were sent in the specified time period

        >>> convert "2022-03-01T15:00:00Z" to a datetime
        >>> use the above as the start date
        >>> convert "2022-03-03T15:00:00Z" to a datetime
        >>> use the above as the end date
        >>> read some sms messages whose date sent is after the start date and whose date sent is before the end date
    """
    client = Client(self._account_sid, self._auth_token)

    filter_visitor = SMSMessageFilter()

    if filter_expression is not None:
        filter_expression.accept(filter_visitor)

    messages = client.messages.list(
        to=filter_visitor.recipient_number,
        from_=filter_visitor.sender_number,
        date_sent=filter_visitor.date_sent,
        date_sent_before=filter_visitor.date_sent_before,
        date_sent_after=filter_visitor.date_sent_after,
    )

    if offset is not None:
        messages = messages[offset:]

    if limit is not None:
        messages = messages[:limit]

    return [SMSMessage.from_message_instance(message) for message in messages]
```
