Skip to content

Commit e507ce9

Browse files
authored
Improvements to Advanced Formulas (baserow#4169)
* Allow lexicographical comparisons for >, <, >=, and <= operators. * Improve/correct examples * Ensure arithmetic operators can accept 2 or more args. Ensure frontend/backend formula functions are implemented similarly. * Add and formula types. * Fix bug reported by Frederik: concat should use Text arg type * Update examples and small fixes. * Lint fix * Update and/or examples to use symbols. Add useStrict option to ensureBoolean(). * For operators, specify 2 args, since operators already reduce * Replace manual checking of arg len * Ensure backend implementation matches frontend * Add castToInt option and make 2nd arg to round() an optional int * Lint fix
1 parent 70be921 commit e507ce9

File tree

10 files changed

+237
-215
lines changed

10 files changed

+237
-215
lines changed

backend/src/baserow/core/apps.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def ready(self):
3737
from baserow.core.formula.registries import formula_runtime_function_registry
3838
from baserow.core.formula.runtime_formula_types import (
3939
RuntimeAdd,
40+
RuntimeAnd,
4041
RuntimeCapitalize,
4142
RuntimeConcat,
4243
RuntimeDateTimeFormat,
@@ -53,13 +54,15 @@ def ready(self):
5354
RuntimeIsEven,
5455
RuntimeIsOdd,
5556
RuntimeLessThan,
57+
RuntimeLessThanOrEqual,
5658
RuntimeLower,
5759
RuntimeMinus,
5860
RuntimeMinute,
5961
RuntimeMonth,
6062
RuntimeMultiply,
6163
RuntimeNotEqual,
6264
RuntimeNow,
65+
RuntimeOr,
6366
RuntimeRandomBool,
6467
RuntimeRandomFloat,
6568
RuntimeRandomInt,
@@ -78,8 +81,9 @@ def ready(self):
7881
formula_runtime_function_registry.register(RuntimeDivide())
7982
formula_runtime_function_registry.register(RuntimeEqual())
8083
formula_runtime_function_registry.register(RuntimeNotEqual())
81-
formula_runtime_function_registry.register(RuntimeGreaterThan())
8284
formula_runtime_function_registry.register(RuntimeLessThan())
85+
formula_runtime_function_registry.register(RuntimeLessThanOrEqual())
86+
formula_runtime_function_registry.register(RuntimeGreaterThan())
8387
formula_runtime_function_registry.register(RuntimeGreaterThanOrEqual())
8488
formula_runtime_function_registry.register(RuntimeUpper())
8589
formula_runtime_function_registry.register(RuntimeLower())
@@ -102,6 +106,8 @@ def ready(self):
102106
formula_runtime_function_registry.register(RuntimeRandomBool())
103107
formula_runtime_function_registry.register(RuntimeGenerateUUID())
104108
formula_runtime_function_registry.register(RuntimeIf())
109+
formula_runtime_function_registry.register(RuntimeAnd())
110+
formula_runtime_function_registry.register(RuntimeOr())
105111

106112
from baserow.core.permission_manager import (
107113
AllowIfTemplatePermissionManagerType,

backend/src/baserow/core/formula/argument_types.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ def parse(self, value):
2525

2626

2727
class NumberBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
28+
def __init__(self, *args, **kwargs):
29+
self.cast_to_int = kwargs.pop("cast_to_int", False)
30+
super().__init__(*args, **kwargs)
31+
2832
def test(self, value):
2933
try:
3034
ensure_numeric(value)
@@ -33,7 +37,8 @@ def test(self, value):
3337
return False
3438

3539
def parse(self, value):
36-
return ensure_numeric(value)
40+
value = ensure_numeric(value)
41+
return int(value) if self.cast_to_int else value
3742

3843

3944
class TextBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
@@ -48,22 +53,6 @@ def parse(self, value):
4853
return ensure_string(value)
4954

5055

51-
class AddableBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
52-
def test(self, value):
53-
return hasattr(value, "__add__")
54-
55-
def parse(self, value):
56-
return value
57-
58-
59-
class SubtractableBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
60-
def test(self, value):
61-
return hasattr(value, "__sub__")
62-
63-
def parse(self, value):
64-
return value
65-
66-
6756
class DateTimeBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
6857
def test(self, value):
6958
try:

backend/src/baserow/core/formula/parser/python_executor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ def visitBinaryOp(self, ctx: BaserowFormula.BinaryOpContext):
9090
op = "greater_than_or_equal"
9191
elif ctx.LTE():
9292
op = "less_than_or_equal"
93+
elif ctx.AMP_AMP():
94+
op = "and"
95+
elif ctx.PIPE_PIPE():
96+
op = "or"
9397
else:
9498
raise UnknownOperator(ctx.getText())
9599

backend/src/baserow/core/formula/runtime_formula_types.py

Lines changed: 60 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
import operator
21
import random
32
import uuid
4-
from functools import reduce
53
from typing import Optional
64
from zoneinfo import ZoneInfo
75

86
from django.utils import timezone
97

108
from baserow.core.formula.argument_types import (
11-
AddableBaserowRuntimeFormulaArgumentType,
129
AnyBaserowRuntimeFormulaArgumentType,
1310
BooleanBaserowRuntimeFormulaArgumentType,
1411
DateTimeBaserowRuntimeFormulaArgumentType,
1512
DictBaserowRuntimeFormulaArgumentType,
1613
NumberBaserowRuntimeFormulaArgumentType,
17-
SubtractableBaserowRuntimeFormulaArgumentType,
1814
TextBaserowRuntimeFormulaArgumentType,
1915
TimezoneBaserowRuntimeFormulaArgumentType,
2016
)
@@ -28,7 +24,7 @@ class RuntimeConcat(RuntimeFormulaFunction):
2824
type = "concat"
2925

3026
def validate_type_of_args(self, args) -> Optional[FormulaArg]:
31-
arg_type = AddableBaserowRuntimeFormulaArgumentType()
27+
arg_type = TextBaserowRuntimeFormulaArgumentType()
3228
return next(
3329
(arg for arg in args if not arg_type.test(arg)),
3430
None,
@@ -52,61 +48,47 @@ def execute(self, context: FormulaContext, args: FormulaArgs):
5248
class RuntimeAdd(RuntimeFormulaFunction):
5349
type = "add"
5450

55-
def validate_type_of_args(self, args) -> Optional[FormulaArg]:
56-
arg_type = AddableBaserowRuntimeFormulaArgumentType()
57-
return next(
58-
(arg for arg in args if not arg_type.test(arg)),
59-
None,
60-
)
61-
62-
def validate_number_of_args(self, args):
63-
return len(args) >= 1
51+
args = [
52+
NumberBaserowRuntimeFormulaArgumentType(),
53+
NumberBaserowRuntimeFormulaArgumentType(),
54+
]
6455

6556
def execute(self, context: FormulaContext, args: FormulaArgs):
66-
return reduce(operator.add, args)
57+
return args[0] + args[1]
6758

6859

6960
class RuntimeMinus(RuntimeFormulaFunction):
7061
type = "minus"
7162

72-
def validate_type_of_args(self, args) -> Optional[FormulaArg]:
73-
arg_type = SubtractableBaserowRuntimeFormulaArgumentType()
74-
return next(
75-
(arg for arg in args if not arg_type.test(arg)),
76-
None,
77-
)
78-
79-
def validate_number_of_args(self, args):
80-
return len(args) > 1
63+
args = [
64+
NumberBaserowRuntimeFormulaArgumentType(),
65+
NumberBaserowRuntimeFormulaArgumentType(),
66+
]
8167

8268
def execute(self, context: FormulaContext, args: FormulaArgs):
83-
return reduce(operator.sub, args)
69+
return args[0] - args[1]
8470

8571

8672
class RuntimeMultiply(RuntimeFormulaFunction):
8773
type = "multiply"
74+
8875
args = [
8976
NumberBaserowRuntimeFormulaArgumentType(),
9077
NumberBaserowRuntimeFormulaArgumentType(),
9178
]
9279

93-
def validate_number_of_args(self, args):
94-
return len(args) == 2
95-
9680
def execute(self, context: FormulaContext, args: FormulaArgs):
9781
return args[0] * args[1]
9882

9983

10084
class RuntimeDivide(RuntimeFormulaFunction):
10185
type = "divide"
86+
10287
args = [
10388
NumberBaserowRuntimeFormulaArgumentType(),
10489
NumberBaserowRuntimeFormulaArgumentType(),
10590
]
10691

107-
def validate_number_of_args(self, args):
108-
return len(args) == 2
109-
11092
def execute(self, context: FormulaContext, args: FormulaArgs):
11193
return args[0] / args[1]
11294

@@ -118,9 +100,6 @@ class RuntimeEqual(RuntimeFormulaFunction):
118100
AnyBaserowRuntimeFormulaArgumentType(),
119101
]
120102

121-
def validate_number_of_args(self, args):
122-
return len(args) == 2
123-
124103
def execute(self, context: FormulaContext, args: FormulaArgs):
125104
return args[0] == args[1]
126105

@@ -132,65 +111,50 @@ class RuntimeNotEqual(RuntimeFormulaFunction):
132111
AnyBaserowRuntimeFormulaArgumentType(),
133112
]
134113

135-
def validate_number_of_args(self, args):
136-
return len(args) == 2
137-
138114
def execute(self, context: FormulaContext, args: FormulaArgs):
139115
return args[0] != args[1]
140116

141117

142118
class RuntimeGreaterThan(RuntimeFormulaFunction):
143119
type = "greater_than"
144120
args = [
145-
NumberBaserowRuntimeFormulaArgumentType(),
146-
NumberBaserowRuntimeFormulaArgumentType(),
121+
AnyBaserowRuntimeFormulaArgumentType(),
122+
AnyBaserowRuntimeFormulaArgumentType(),
147123
]
148124

149-
def validate_number_of_args(self, args):
150-
return len(args) == 2
151-
152125
def execute(self, context: FormulaContext, args: FormulaArgs):
153126
return args[0] > args[1]
154127

155128

156129
class RuntimeLessThan(RuntimeFormulaFunction):
157130
type = "less_than"
158131
args = [
159-
NumberBaserowRuntimeFormulaArgumentType(),
160-
NumberBaserowRuntimeFormulaArgumentType(),
132+
AnyBaserowRuntimeFormulaArgumentType(),
133+
AnyBaserowRuntimeFormulaArgumentType(),
161134
]
162135

163-
def validate_number_of_args(self, args):
164-
return len(args) == 2
165-
166136
def execute(self, context: FormulaContext, args: FormulaArgs):
167137
return args[0] < args[1]
168138

169139

170140
class RuntimeGreaterThanOrEqual(RuntimeFormulaFunction):
171141
type = "greater_than_or_equal"
172142
args = [
173-
NumberBaserowRuntimeFormulaArgumentType(),
174-
NumberBaserowRuntimeFormulaArgumentType(),
143+
AnyBaserowRuntimeFormulaArgumentType(),
144+
AnyBaserowRuntimeFormulaArgumentType(),
175145
]
176146

177-
def validate_number_of_args(self, args):
178-
return len(args) == 2
179-
180147
def execute(self, context: FormulaContext, args: FormulaArgs):
181148
return args[0] >= args[1]
182149

183150

184151
class RuntimeLessThanOrEqual(RuntimeFormulaFunction):
185152
type = "less_than_or_equal"
186153
args = [
187-
NumberBaserowRuntimeFormulaArgumentType(),
188-
NumberBaserowRuntimeFormulaArgumentType(),
154+
AnyBaserowRuntimeFormulaArgumentType(),
155+
AnyBaserowRuntimeFormulaArgumentType(),
189156
]
190157

191-
def validate_number_of_args(self, args):
192-
return len(args) == 2
193-
194158
def execute(self, context: FormulaContext, args: FormulaArgs):
195159
return args[0] <= args[1]
196160

@@ -227,7 +191,7 @@ class RuntimeRound(RuntimeFormulaFunction):
227191

228192
args = [
229193
NumberBaserowRuntimeFormulaArgumentType(),
230-
NumberBaserowRuntimeFormulaArgumentType(),
194+
NumberBaserowRuntimeFormulaArgumentType(optional=True, cast_to_int=True),
231195
]
232196

233197
def execute(self, context: FormulaContext, args: FormulaArgs):
@@ -272,7 +236,7 @@ def execute(self, context: FormulaContext, args: FormulaArgs):
272236
datetime_obj = args[0]
273237
moment_format = args[1]
274238

275-
if (len(args)) == 2:
239+
if len(args) == 2:
276240
timezone_name = context.get_timezone_name()
277241
else:
278242
timezone_name = args[2]
@@ -350,13 +314,17 @@ def execute(self, context: FormulaContext, args: FormulaArgs):
350314
class RuntimeNow(RuntimeFormulaFunction):
351315
type = "now"
352316

317+
args = []
318+
353319
def execute(self, context: FormulaContext, args: FormulaArgs):
354320
return timezone.now()
355321

356322

357323
class RuntimeToday(RuntimeFormulaFunction):
358324
type = "today"
359325

326+
args = []
327+
360328
def execute(self, context: FormulaContext, args: FormulaArgs):
361329
return timezone.localdate()
362330

@@ -400,26 +368,53 @@ def execute(self, context: FormulaContext, args: FormulaArgs):
400368
class RuntimeRandomBool(RuntimeFormulaFunction):
401369
type = "random_bool"
402370

371+
args = []
372+
403373
def execute(self, context: FormulaContext, args: FormulaArgs):
404374
return random.choice([True, False]) # nosec: B311
405375

406376

407377
class RuntimeGenerateUUID(RuntimeFormulaFunction):
408378
type = "generate_uuid"
409379

380+
args = []
381+
410382
def execute(self, context: FormulaContext, args: FormulaArgs):
411383
return str(uuid.uuid4())
412384

413385

414386
class RuntimeIf(RuntimeFormulaFunction):
415387
type = "if"
416388

417-
def validate_type_of_args(self, args) -> Optional[FormulaArg]:
418-
arg_type = BooleanBaserowRuntimeFormulaArgumentType()
419-
if not arg_type.test(args[0]):
420-
return args[0]
421-
422-
return None
389+
args = [
390+
BooleanBaserowRuntimeFormulaArgumentType(),
391+
AnyBaserowRuntimeFormulaArgumentType(),
392+
AnyBaserowRuntimeFormulaArgumentType(),
393+
]
423394

424395
def execute(self, context: FormulaContext, args: FormulaArgs):
425396
return args[1] if args[0] else args[2]
397+
398+
399+
class RuntimeAnd(RuntimeFormulaFunction):
400+
type = "and"
401+
402+
args = [
403+
BooleanBaserowRuntimeFormulaArgumentType(),
404+
BooleanBaserowRuntimeFormulaArgumentType(),
405+
]
406+
407+
def execute(self, context: FormulaContext, args: FormulaArgs):
408+
return args[0] and args[1]
409+
410+
411+
class RuntimeOr(RuntimeFormulaFunction):
412+
type = "or"
413+
414+
args = [
415+
BooleanBaserowRuntimeFormulaArgumentType(),
416+
BooleanBaserowRuntimeFormulaArgumentType(),
417+
]
418+
419+
def execute(self, context: FormulaContext, args: FormulaArgs):
420+
return args[0] or args[1]

0 commit comments

Comments
 (0)