Special `expressions.Error` type for returing messages from `expressions.Cond` branches

here’s an example to demonstrate what i’m looking for

import aerospike
from aerospike_helpers import cdt_ctx, expressions
from aerospike_helpers.expressions.arithmetic import Add
from aerospike_helpers.operations import expression_operations

ac = aerospike.client({'hosts': [('localhost', 3000)]})
ac.connect()
key = ('test', 'test', 'test')
ac.remove(key)
ac.put(key, {'bin': {'vals': [], 'tokens': 1}})

def add_val_if_enough_tokens_and_empty(val):
    ac.operate(
        key,
        [
            expression_operations.expression_write(
                'bin',
                expressions.Cond(
                    expressions.GT(
                        expressions.MapGetByKey(
                            ctx=None,
                            return_type=aerospike.MAP_RETURN_VALUE,
                            value_type=expressions.resources.ResultType.INTEGER,
                            key='tokens',
                            bin=expressions.MapBin('bin')
                        ),
                        0
                    ),
                    expressions.Cond(
                        expressions.GT(
                            expressions.ListSize(
                                ctx=[cdt_ctx.cdt_ctx_map_key('vals')],
                                bin=expressions.MapBin('bin')
                            ),
                            0
                        ),
                        expressions.Unknown(),
                        expressions.ListAppend(
                            ctx=[cdt_ctx.cdt_ctx_map_key('vals')],
                            policy=None,
                            value=val,
                            bin=expressions.MapBin('bin')
                        )
                    ),
                    expressions.Unknown()
                ).compile(),
                # aerospike.EXP_WRITE_EVAL_NO_FAIL
                # aerospike.EXP_WRITE_POLICY_NO_FAIL
            )
        ]
    )

for _ in range(2):
    print(ac.get(key)[2])
    try:
        add_val_if_enough_tokens_and_empty('val')
    except aerospike.exception.OpNotApplicable as e:
        print(f'op failed because {e}')

with output

{'bin': {'vals': [], 'tokens': 1}}
{'bin': {'vals': ['val'], 'tokens': 1}}
op failed because (26, '127.0.0.1:3000 AEROSPIKE_ERR_OP_NOT_APPLICABLE', 'src/main/client/operate.c', 813, False)

so here, i want to only add a value to vals if tokens is greater than 0 and vals is empty, which is achieved with this, but i cannot transactionally know which one of them was not satisfied on failure (unless of course i fetch the record, which isn’t transactional), so would like to have something like this

import aerospike
from aerospike_helpers import cdt_ctx, expressions
from aerospike_helpers.operations import expression_operations

ac = aerospike.client({'hosts': [('localhost', 3000)]})
ac.connect()
key = ('test', 'test', 'test')
ac.remove(key)
ac.put(key, {'bin': {'vals': [], 'tokens': 1}})

def add_val_if_enough_tokens_and_empty(val):
    ac.operate(
        key,
        [
            expression_operations.expression_write(
                'bin',
                expressions.Cond(
                    expressions.GT(
                        expressions.MapGetByKey(
                            ctx=None,
                            return_type=aerospike.MAP_RETURN_VALUE,
                            value_type=expressions.resources.ResultType.INTEGER,
                            key='tokens',
                            bin=expressions.MapBin('bin')
                        ),
                        0
                    ),
                    expressions.Cond(
                        expressions.GT(
                            expressions.ListSize(
                                ctx=[cdt_ctx.cdt_ctx_map_key('vals')],
                                bin=expressions.MapBin('bin')
                            ),
                            0
                        ),
                        expressions.Error("vals isn't empty"),
                        expressions.ListAppend(
                            ctx=[cdt_ctx.cdt_ctx_map_key('vals')],
                            policy=None,
                            value=val,
                            bin=expressions.MapBin('bin')
                        )
                    ),
                    expressions.Error("no tokens")
                ).compile(),
                # aerospike.EXP_WRITE_EVAL_NO_FAIL
                # aerospike.EXP_WRITE_POLICY_NO_FAIL
            )
        ]
    )

for _ in range(2):
    print(ac.get(key)[2])
    try:
        add_val_if_enough_tokens_and_empty('val')
    except aerospike.exception.OpNotApplicable as e:
        print(f'op failed because {e}')

with the expressions.Error’s which would be parsable from the resulting aerospike.exception.OpNotApplicable or some other error type specifically for this sorta thing

does this feature make sense?

Thanks for your suggestion. Let me pass this to our Product folks.

This is apparently not possible the way the code is currently structured but could be considered in the future. I have filed a formal product feature request internally (PROD-2118 – only for reference).

1 Like