Skip to content

Commit

Permalink
Merge pull request #9 from juliotrigo/optional-op
Browse files Browse the repository at this point in the history
Make operator filter attribute not mandatory
  • Loading branch information
juliotrigo authored May 30, 2017
2 parents a7ac733 + 323b5ba commit 5bf898f
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 32 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ Backwards-compatible changes increment the minor version number only.
Version 0.3.0
-------------

Released 2017-05-16
Released 2017-05-22

* Adds support for boolean functions within filters
* Adds the possibility of supplying a single dictionary as filters when
only one filter is provided
* Makes the `op` filter attribute optional: `==` is the default operator

Version 0.2.0
-------------
Expand Down
10 changes: 5 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,16 @@ following format:
{'field': 'field_2_name', 'op': '!=', 'value': 'field_2_value'},
# ...
]
Optionally, if there is only one filter, the containing list may be omitted:

.. code-block:: python
filters = {'field': 'field_name', 'op': '==', 'value': 'field_value'}
Where ``field`` is the name of the field that will be filtered using the
operator provided in ``op`` and (optionally, depending on the operator)
the provided ``value``.
operator provided in ``op`` (optional, defaults to `==`) and the
provided ``value`` (optional, depending on the operator).

This is the list of operators that can be used:

Expand Down Expand Up @@ -151,9 +151,9 @@ Boolean Functions
}
]
Note: ``or`` and ``and`` must reference a list of at least one element. ``not`` must reference a list of exactly one element.

Sort format
-----------

Expand Down
18 changes: 10 additions & 8 deletions sqlalchemy_filters/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ class Operator(object):
'not_in': lambda f, a: ~f.in_(a),
}

def __init__(self, operator):
def __init__(self, operator=None):
if not operator:
operator = '=='

if operator not in self.OPERATORS:
raise BadFilterFormat('Operator `{}` not valid.'.format(operator))

Expand All @@ -59,18 +62,15 @@ class Filter(object):
def __init__(self, filter_, models):
try:
field_name = filter_['field']
op = filter_['op']
except KeyError:
raise BadFilterFormat(
'`field` and `op` are mandatory filter attributes.'
)
raise BadFilterFormat('`field` is a mandatory filter attribute.')
except TypeError:
raise BadFilterFormat(
'Filter `{}` should be a dictionary.'.format(filter_)
)

self.field = Field(models, field_name)
self.operator = Operator(op)
self.operator = Operator(filter_.get('op'))
self.value = filter_.get('value')
self.value_present = True if 'value' in filter_ else False

Expand Down Expand Up @@ -113,12 +113,14 @@ def _build_sqlalchemy_filters(filterdef, models):
if boolean_function.only_one_arg and len(fn_args) != 1:
raise BadFilterFormat(
'`{}` must have one argument'.format(
boolean_function.key)
boolean_function.key
)
)
if not boolean_function.only_one_arg and len(fn_args) < 1:
raise BadFilterFormat(
'`{}` must have one or more arguments'.format(
boolean_function.key)
boolean_function.key
)
)
return [
boolean_function.sqlalchemy_fn(
Expand Down
38 changes: 20 additions & 18 deletions test/interface/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,19 @@ def test_multiple_models(self, session):
assert expected_error == err.value.args[0]


class TestProvidedFilters(object):
class TestFiltersMixin(object):

@pytest.fixture
def multiple_bars_inserted(self, session):
bar_1 = Bar(id=1, name='name_1', count=5)
bar_2 = Bar(id=2, name='name_2', count=10)
bar_3 = Bar(id=3, name='name_1', count=None)
bar_4 = Bar(id=4, name='name_4', count=15)
session.add_all([bar_1, bar_2, bar_3, bar_4])
session.commit()


class TestProvidedFilters(TestFiltersMixin):

def test_no_filters_provided(self, session):
query = session.query(Bar)
Expand Down Expand Up @@ -67,15 +79,17 @@ def test_invalid_operator(self, session):

assert 'Operator `op_not_valid` not valid.' == err.value.args[0]

@pytest.mark.usefixtures('multiple_bars_inserted')
def test_no_operator_provided(self, session):
query = session.query(Bar)
filters = [{'field': 'name', 'value': 'name_1'}]

with pytest.raises(BadFilterFormat) as err:
apply_filters(query, filters)
filtered_query = apply_filters(query, filters)
result = filtered_query.all()

expected_error = '`field` and `op` are mandatory filter attributes.'
assert expected_error == err.value.args[0]
assert len(result) == 2
assert result[0].id == 1
assert result[1].id == 3

def test_no_field_provided(self, session):
query = session.query(Bar)
Expand All @@ -84,7 +98,7 @@ def test_no_field_provided(self, session):
with pytest.raises(BadFilterFormat) as err:
apply_filters(query, filters)

expected_error = '`field` and `op` are mandatory filter attributes.'
expected_error = '`field` is a mandatory filter attribute.'
assert expected_error == err.value.args[0]

# TODO: replace this test once we add the option to compare against
Expand Down Expand Up @@ -129,18 +143,6 @@ def test_invalid_field_but_valid_model_attribute(self, session, attr_name):
assert expected_error == err.value.args[0]


class TestFiltersMixin(object):

@pytest.fixture
def multiple_bars_inserted(self, session):
bar_1 = Bar(id=1, name='name_1', count=5)
bar_2 = Bar(id=2, name='name_2', count=10)
bar_3 = Bar(id=3, name='name_1', count=None)
bar_4 = Bar(id=4, name='name_4', count=15)
session.add_all([bar_1, bar_2, bar_3, bar_4])
session.commit()


class TestApplyIsNullFilter(TestFiltersMixin):

@pytest.mark.usefixtures('multiple_bars_inserted')
Expand Down

0 comments on commit 5bf898f

Please sign in to comment.