rest-filter 1.0.2
A query string parsing library for REST server queries
License: MIT
Requires Python: >=3.6

# rest-filter

rest-filter aims to ba a light weight fast parser written in pure Python, meant for use in REST API servers.
It was inspired by the [OData standard](https://www.odata.org/), but this query is a bit simpler and less feature rich.

## Goals
* An easily extendable querying syntax
* Be able to query any data source by writing a simple translator
* Usable in any Python REST framework

## Installation
```bash
pip install rest-filter
```

## Example usage

Here is an example of the most basic usage
```python
from rest_filter.encoder.common import encoder
from rest_filter.translator import get_mongo_translator

# The raw input expression
expression = r"((age gt 20) and (age ge 10))"

# Generate the encoded expression
encoded_expression = encoder.encode_expression(expression)
print(encoded_expression)

# Translate into a mongo query
mongo_query = get_mongo_translator().translate(encoded_expression)
print(mongo_query)
```

The resulting encoded expression looks like this:
```python
And(operands=[
    GraterThan(field=Field(name='age'), value=20.0), 
    GraterEqualsTo(field=Field(name='age'), value=10.0)
])
```

And the resulting mongo query looks like this:
```python
{
    '$and': [
        {'age': {'$gt': 20.0}}, 
        {'age': {'$ge': 10.0}}
    ]
}
```

This uses the default pre built expression encoding elements & the pre build
translation elements. you can add additional elements like this:
```python
import re

from rest_filter.encoder.common import encoder, And
from rest_filter.encoder import BinaryOperation
from rest_filter.translator import get_mongo_translator


mongo_translator = get_mongo_translator()
encoder.register_binary_logical_operator('&&')(And)


@encoder.register_ratio('contains')
class Contains(BinaryOperation):
    pass


@mongo_translator.register_binary_operation_translator(Contains)
def translate_equals(operation: Contains):
    return {
        operation.field.name: f'/{re.escape(operation.value)}/'
    }


# The raw input expression
expression = r"((age gt 20) && (name contains 'a string with special **^^ chars'))"

# Generate the encoded expression
encoded_expression = encoder.encode_expression(expression)
print(encoded_expression)

# Translate into a mongo query
mongo_query = get_mongo_translator().translate(encoded_expression)
print(mongo_query)
```

If you want you can forgo the pre loaded encoder & translator objects and create ones of your one with completely custom elements:

```python
from rest_filter.encoder import Encoder
from rest_filter.encoder import BooleanOperation, BinaryOperation

from rest_filter.translator import get_mongo_translator, Translator

encoder = Encoder()


# Register encoders
@encoder.register_binary_logical_operator('and')
class And(BooleanOperation):
    pass


@encoder.register_ratio('eq')
class Equals(BinaryOperation):
    pass

@encoder.register_type(r'-?\d+')
def number(text: str):
    return int(text)

mongo_translator = get_mongo_translator()

# Register translators
@mongo_translator.register_boolean_operation_translator(And)
def translate_and(operation: And, translator: Translator):
    return {
        '$and': [
            translator.translate(operand) for operand in operation.operands
        ]
    }


@mongo_translator.register_binary_operation_translator(Equals)
def translate_equals(operation: Equals):
    return {
        operation.field.name: {
            '$eq': operation.value
        }
    }

expression = "((age eq 3) and (height eq 18))"

# Generate the encoded expression
encoded_expression = encoder.encode_expression(expression)
print(encoded_expression)

# Translate into a mongo query
mongo_query = mongo_translator.translate(encoded_expression)
print(mongo_query)
```

## Limitations
* Expressions can only be parentheses complete (for now).
For example, these expressions are not allowed:
    - ```age gt 10 and name eq 'eric' and city eq 'tlv'```
    -  ```(age gt 10 and name eq 'eric' and city eq 'tlv')```
    -  ```(age gt 10) and (name eq 'eric') and (city eq 'tlv')```\
But this expression is valid:
    - ```(((age gt 10) and (name eq 'eric')) and (city eq 'tlv'))```
* You cannot register new value types (for now), you must use the prebuilt boolean, string or number values

## Encoder Translator design
This utility is separated into two parts: Encoder & Translator

### Encoder
The encoder is responsible for parsing the input string into a structured expression object, 
that can be easily read & parsed by code

The encoder can encodes grammar elements that are registered to it. each registered grammar is a 
ratio between a field & a value, or a logical operation between two logical values or expressions 

### Translator
The translator translates the generic expression format given from the encoder into a target language.\
There is a basic mongo translator built into this package (more yet to come), but you can easily write 
a translator of your own.

## Contributors
* Sagiv Oulu