Skip to content

Commit e0d6916

Browse files
2.0.0 Release 🚀😄 adding new filters and capabilities to create your own filters
2 parents d64b2a8 + bfedebf commit e0d6916

File tree

12 files changed

+269
-48
lines changed

12 files changed

+269
-48
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
__pycache__
2+
build/
3+
dist/
4+
*.egg-info
5+
*.swp
6+
*.pyc

README.md

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pip install nocodb
1414
### Client configuration
1515
```python
1616
from nocodb.nocodb import NocoDBProject, APIToken, JWTAuthToken
17-
from nocodb.filters import InFilter, EqFilter
17+
from nocodb.filters import LikeFilter, EqFilter
1818
from nocodb.infra.requests_client import NocoDBRequestsClient
1919

2020

@@ -98,7 +98,7 @@ table_rows = client.table_row_list(project, table_name, params={'offset': 100})
9898
# Filter the query
9999
# Currently only one filter at a time is allowed. I don't know how to join
100100
# multiple conditions in nocodb dsl. If you know how please let me know :).
101-
table_rows = client.table_row_list(project, table_name, InFilter("name", "sam"))
101+
table_rows = client.table_row_list(project, table_name, LikeFilter("name", "%sam%"))
102102
table_rows = client.table_row_list(project, table_name, filter_obj=EqFilter("Id", 100))
103103

104104
# Filter and count rows
@@ -114,22 +114,101 @@ row = client.table_row_detail(project, table_name, row_id)
114114
# Create a new row
115115
row_info = {
116116
"name": "my thoughts",
117-
"content": "i'm going to buy samuel a beer because i love this module",
117+
"content": "i'm going to buy samuel a beer 🍻 because I 💚 this module",
118118
"mood": ":)"
119119
}
120120
client.table_row_create(project, table_name, row_info)
121121

122122
# Update a row
123123
row_id = 2
124124
row_info = {
125-
"content": "i'm going to buy samuel a new car because i love this module",
125+
"content": "i'm going to buy samuel a new car 🚙 because I 💚 this module",
126126
}
127127
client.table_row_update(project, table_name, row_id, row_info)
128128

129129
# Delete a row (only if you've already bought me a beer)
130130
client.table_row_delete(project, table_name, row_id)
131131
```
132132

133+
### Available filters
134+
135+
- EqFilter
136+
- EqualFilter (Alias of EqFilter)
137+
- NotEqualFilter
138+
- GreaterThanFilter
139+
- GreaterOrEqualFilter
140+
- LessThanFilter
141+
- LessOrEqualFilter
142+
- LikeFilter
143+
144+
### Using custom filters
145+
146+
Nocodb is evolving and new operators are coming with each release.
147+
148+
Most of the basic operations are inside this package but you could need some new
149+
feature that could not be added yet.
150+
For those filters you can build your own.
151+
152+
Example for basic filters:
153+
154+
```python
155+
from nocodb.filters.factory import basic_filter_class_factory
156+
157+
BasicFilter = basic_filter_class_factory('=')
158+
table_rows = client.table_row_list(project, table_name, BasicFilter('age', '16'))
159+
160+
```
161+
162+
You can find the updated list of all the available nocodb operators [here](https://docs.nocodb.com/developer-resources/rest-apis/#comparison-operators).
163+
164+
In some cases you might want to write your own filter string as described in the previous link.
165+
For that cases you can use the less-semmantic RawFilter.
166+
167+
```python
168+
from nocodb.filters.raw_filter import RawFilter
169+
170+
table_rows = client.table_row_list(project, table_name, RawFilter('(birthday,eq,exactDate,2023-06-01)'))
171+
```
172+
173+
In some cases we might want to have a file with some custom raw filters already defined by us.
174+
We can easily create custom raw filter classes using `raw_template_filter_class_factory`.
175+
176+
```python
177+
from nocodb.filters.factory import raw_template_filter_class_factory
178+
179+
BirthdayDateFilter = raw_template_filter_class_factory('(birthday,eq,exactDate,{})')
180+
ExactDateEqFilter = raw_template_filter_class_factory('({},eq,exactDate,{})')
181+
ExactDateOpFilter = raw_template_filter_class_factory('({},{op},exactDate,{})')
182+
183+
table_rows = client.table_row_list(project, table_name, BirthdayDateFilter('2023-06-01'))
184+
table_rows = client.table_row_list(project, table_name, ExactDateEqFilter('column', '2023-06-01'))
185+
table_rows = client.table_row_list(project, table_name, ExactDateOpFilter('column', '2023-06-01', op='eq'))
186+
```
187+
188+
```python
189+
from nocodb import filters
190+
191+
# Basic filters...
192+
nick_filter = filters.EqFilter("nickname", "elchicodepython")
193+
country_filter = filters.EqFilter("country", "es")
194+
girlfriend_code = filters.EqFilter("gfcode", "404")
195+
current_mood_code = filters.EqFilter("moodcode", "418")
196+
197+
# Combining filters using logical filters
198+
or_filter = filters.Or(nick_filter, country_filter)
199+
and_filter = filters.And(girlfriend_code, current_mood_code)
200+
201+
# Negating filters with a Not filter
202+
not_me = filters.Not(filters.EqFilter("nickname", "elchicodepython"))
203+
204+
# You can also combine combinations
205+
or_combined_filter = filters.Or(or_filter, and_filter)
206+
and_combined_filter = filters.And(or_filter, and_filter)
207+
208+
```
209+
210+
Credits to @MitPitt for asking this feature.
211+
133212
## Author notes
134213

135214
I created this package to bootstrap some personal projects and I hope it

nocodb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.1"
1+
__version__ = "2.0.0"

nocodb/filters.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

nocodb/filters/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from .factory import basic_filter_class_factory
2+
from .logical import And, Not, Or
3+
4+
EqFilter = basic_filter_class_factory("eq")
5+
EqualFilter = EqFilter
6+
NotEqualFilter = basic_filter_class_factory("neq")
7+
GreaterThanFilter = basic_filter_class_factory("gt")
8+
GreaterOrEqualFilter = basic_filter_class_factory("ge")
9+
LessThanFilter = basic_filter_class_factory("lt")
10+
LessOrEqualFilter = basic_filter_class_factory("le")
11+
LikeFilter = basic_filter_class_factory("like")
12+
13+
__all__ = [
14+
"And",
15+
"Not",
16+
"Or",
17+
"EqFilter",
18+
"EqualFilter",
19+
"NotEqualFilter",
20+
"GreaterThanFilter",
21+
"GreaterOrEqualFilter",
22+
"LessThanFilter",
23+
"LessOrEqualFilter",
24+
"LikeFilter",
25+
]

nocodb/filters/factory.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from ..nocodb import WhereFilter
2+
3+
from .raw_filter import RawTemplateFilter
4+
5+
6+
def basic_filter_class_factory(filter_name: str):
7+
return raw_template_filter_class_factory('({},' + filter_name + ',{})')
8+
9+
def raw_template_filter_class_factory(template: str):
10+
class WrappedFilter(WhereFilter):
11+
def __init__(self, *args, **kwargs):
12+
self.__filter = RawTemplateFilter(template, *args, **kwargs)
13+
def get_where(self) -> str:
14+
return self.__filter.get_where()
15+
return WrappedFilter

nocodb/filters/factory_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .factory import basic_filter_class_factory, raw_template_filter_class_factory
2+
3+
4+
def test_basic_filter_class_factory():
5+
FilterClass = basic_filter_class_factory('eq')
6+
assert FilterClass('column', 'value').get_where() == '(column,eq,value)'
7+
8+
9+
def test_raw_template_filter_class_factory():
10+
FilterClassWithoutParams = raw_template_filter_class_factory('()')
11+
FilterClassWithParams = raw_template_filter_class_factory('({},{},{})')
12+
FilterClassWithKwargs = raw_template_filter_class_factory('({},{op},{})')
13+
assert FilterClassWithoutParams().get_where() == '()'
14+
assert FilterClassWithParams('1', '2','3').get_where() == '(1,2,3)'
15+
assert FilterClassWithKwargs('1', '2', op='eq').get_where() == '(1,eq,2)'

nocodb/filters/filters_test.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
3+
from .. import filters
4+
5+
from ..nocodb import WhereFilter
6+
7+
8+
@pytest.mark.parametrize(
9+
"filter_class, expected_operator",
10+
[
11+
(filters.EqFilter, "eq"),
12+
(filters.EqualFilter, "eq"),
13+
(filters.NotEqualFilter, "neq"),
14+
(filters.GreaterOrEqualFilter, "ge"),
15+
(filters.GreaterThanFilter, "gt"),
16+
(filters.LessThanFilter, "lt"),
17+
(filters.LessOrEqualFilter, "le"),
18+
(filters.LikeFilter, "like"),
19+
],
20+
)
21+
def test_basic_filters_are_correctly_created(
22+
filter_class: WhereFilter, expected_operator: str
23+
):
24+
test_filter = filter_class("column", "value")
25+
assert test_filter.get_where() == f"(column,{expected_operator},value)"
26+
27+
28+
def test_or_filter():
29+
nick_filter = filters.EqFilter("nickname", "elchicodepython")
30+
country_filter = filters.EqFilter("country", "es")
31+
nick_or_country_filter = filters.Or(nick_filter, country_filter)
32+
assert (
33+
nick_or_country_filter.get_where()
34+
== "((nickname,eq,elchicodepython)~or(country,eq,es))"
35+
)
36+
37+
38+
def test_and_filter():
39+
nick_filter = filters.EqFilter("nickname", "elchicodepython")
40+
country_filter = filters.EqFilter("country", "es")
41+
nick_or_country_filter = filters.And(nick_filter, country_filter)
42+
assert (
43+
nick_or_country_filter.get_where()
44+
== "((nickname,eq,elchicodepython)~and(country,eq,es))"
45+
)
46+
47+
48+
def test_combined_filter():
49+
nick_filter = filters.EqFilter("nickname", "elchicodepython")
50+
country_filter = filters.EqFilter("country", "es")
51+
girlfriend_code = filters.EqFilter("gfcode", "404")
52+
current_mood_code = filters.EqFilter("moodcode", "418")
53+
or_filter = filters.Or(nick_filter, country_filter)
54+
and_filter = filters.And(girlfriend_code, current_mood_code)
55+
or_combined_filter = filters.Or(or_filter, and_filter)
56+
and_combined_filter = filters.And(or_filter, and_filter)
57+
58+
assert (
59+
or_combined_filter.get_where()
60+
== "(((nickname,eq,elchicodepython)~or(country,eq,es))~or((gfcode,eq,404)~and(moodcode,eq,418)))"
61+
)
62+
assert (
63+
and_combined_filter.get_where()
64+
== "(((nickname,eq,elchicodepython)~or(country,eq,es))~and((gfcode,eq,404)~and(moodcode,eq,418)))"
65+
)
66+
67+
68+
def test_not_filter():
69+
me = filters.EqFilter("nickname", "elchicodepython")
70+
not_me = filters.Not(me)
71+
assert not_me.get_where() == "~not(nickname,eq,elchicodepython)"

nocodb/filters/logical.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import List
2+
from ..nocodb import WhereFilter
3+
4+
5+
class Or(WhereFilter):
6+
def __init__(self, *filters: List[WhereFilter]):
7+
self.__filters = filters
8+
9+
def get_where(self) -> str:
10+
return (
11+
"("
12+
+ "~or".join([filter.get_where() for filter in self.__filters])
13+
+ ")"
14+
)
15+
16+
17+
class And(WhereFilter):
18+
def __init__(self, *filters: List[WhereFilter]):
19+
self.__filters = filters
20+
21+
def get_where(self) -> str:
22+
return (
23+
"("
24+
+ "~and".join([filter.get_where() for filter in self.__filters])
25+
+ ")"
26+
)
27+
28+
29+
class Not(WhereFilter):
30+
def __init__(self, filter: WhereFilter):
31+
self.__filter = filter
32+
33+
def get_where(self) -> str:
34+
return "~not" + self.__filter.get_where()

nocodb/filters/raw_filter.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from ..nocodb import WhereFilter
2+
3+
4+
class RawFilter(WhereFilter):
5+
def __init__(self, raw: str):
6+
self.__raw = raw
7+
8+
def get_where(self) -> str:
9+
return self.__raw
10+
11+
12+
class RawTemplateFilter(WhereFilter):
13+
def __init__(self, template: str, *args, **kwargs):
14+
self.__template = template
15+
self.__template_values = args
16+
self.__template_kvalues = kwargs
17+
18+
def get_where(self) -> str:
19+
return self.__template.format(*self.__template_values, **self.__template_kvalues)

0 commit comments

Comments
 (0)