Skip to content
This repository has been archived by the owner on Feb 21, 2023. It is now read-only.

Extended HMSET #130

Merged
merged 15 commits into from Jun 23, 2016
Merged

Extended HMSET #130

merged 15 commits into from Jun 23, 2016

Conversation

aamalev
Copy link
Contributor

@aamalev aamalev commented Jun 18, 2016

Implementation

@coveralls
Copy link

coveralls commented Jun 18, 2016

Coverage Status

Coverage decreased (-0.002%) to 97.191% when pulling 27f974e on aamalev:master into 8726c2e on aio-libs:master.

@coveralls
Copy link

coveralls commented Jun 18, 2016

Coverage Status

Coverage increased (+0.02%) to 97.21% when pulling 40f3c9b on aamalev:master into 8726c2e on aio-libs:master.

@coveralls
Copy link

coveralls commented Jun 19, 2016

Coverage Status

Coverage increased (+0.001%) to 97.194% when pulling 6820357 on aamalev:master into 8726c2e on aio-libs:master.

@popravich
Copy link
Contributor

Hi, this is good feature but I dont like your implementation -- it is too complex and has bugs.
First of all, if you add this test it will fail:

yield from redis.hmset('key', {'a': 1}, {'b': 2}, 'c', 3, d=4, a=5)
res = yield from redis.hgetall('key')
assert res == {b'a': b'5',
               b'b': b'2',
               b'c': b'3',
               b'd': b'4'}

Second, implementation, the public interface must be as follows:
hmset(key, *args, **kwargs)
internally this must be handled as follows:

  • check if not args and not kwargs -> TypeError
  • if len(args) == 1 -> args[0] must be a dict
  • or len(args) % 2 == 0 -> args is a field-value pairs
  • finally if len(args) != 1 or len(args) % 2 != 0 -> TypeError
  • add kwargs if any
  • and execute HMSET

@aamalev
Copy link
Contributor Author

aamalev commented Jun 21, 2016

Hi @popravich. My implementation dicts_or_pairs not supported mixed types.

This test will fail in both cases in one and the same place.

def execute(self, command, *args, encoding=_NOTSET):
         ...
>       if None in set(args):
E       TypeError: unhashable type: 'dict'

A similar bug in hset:
yield from redis.hset('key', {'a': 1}, {'b': 2})

@coveralls
Copy link

coveralls commented Jun 21, 2016

Coverage Status

Coverage increased (+0.02%) to 97.213% when pulling f77c6f8 on aamalev:master into 8726c2e on aio-libs:master.

@coveralls
Copy link

coveralls commented Jun 21, 2016

Coverage Status

Coverage increased (+0.003%) to 97.196% when pulling 0135a20 on aamalev:master into 8726c2e on aio-libs:master.

@popravich
Copy link
Contributor

A similar bug in hset:
yield from redis.hset('key', {'a': 1}, {'b': 2})

This is intended behaviour

My implementation dicts_or_pairs not supported mixed types.

If so it should raise exception when mixing arg types

@coveralls
Copy link

coveralls commented Jun 21, 2016

Coverage Status

Coverage increased (+0.001%) to 97.194% when pulling 07134f9 on aamalev:master into 8726c2e on aio-libs:master.

@coveralls
Copy link

coveralls commented Jun 21, 2016

Coverage Status

Coverage increased (+0.001%) to 97.194% when pulling 07134f9 on aamalev:master into 8726c2e on aio-libs:master.

@aamalev
Copy link
Contributor Author

aamalev commented Jun 21, 2016

More successful implementation

@coveralls
Copy link

coveralls commented Jun 21, 2016

Coverage Status

Coverage increased (+0.001%) to 97.194% when pulling 42e9e4e on aamalev:master into 8726c2e on aio-libs:master.

if not args:
pairs = functools.reduce(operator.add, kwargs.items())

elif all(is_dict):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need all args to be dicts?
what is the reason for calling hmset like this:

redis.hmset(key, {'a': 1}, {'b': 2})

and not simply with one dict?

@aamalev
Copy link
Contributor Author

aamalev commented Jun 22, 2016

An example of the need OrderedDict

redis.hmset(key, {'a': 1}, {b'a': 2}, a=3)

As the regular dict:

{'a': 3, b'a':2} or {b'a': 2, 'a':3}

Another crazy example:

redis.hmset(key, {1: 1}, {b'1': 2}, {'1': 3})

@coveralls
Copy link

coveralls commented Jun 22, 2016

Coverage Status

Coverage increased (+0.07%) to 97.262% when pulling 16314fb on aamalev:master into 8726c2e on aio-libs:master.

@popravich
Copy link
Contributor

OMG this is totaly crazy)
It is end-user's responsibility to control input data.
Ok, what is gonna happen in this case (which value will win)?

redis.hmset(key, {b'a': 1, 'a': 2}, {'a': 3, b'a': 4})

@kxepal
Copy link
Contributor

kxepal commented Jun 22, 2016

I agree with @popravich: no need to be too smart. If conflicts happens, better to cry loudly instead of picking decision that user may not be aware/expected about. Only user may solve them right.

@popravich
Copy link
Contributor

I like it as simple as possible -- so it 100% clear how it works and what to expect.
Lets not break current hmset but create another one for dicts:
hmset_dict(key, *args, **kwargs) args allowed only to hold single a dict otherwise kwargs must be provided.
The implementation should simply unpack args[0] & kwargs into pairs and call original hmset

@aamalev
Copy link
Contributor Author

aamalev commented Jun 22, 2016

Convinced. Therefore, I propose a simple implementation.

@coveralls
Copy link

coveralls commented Jun 22, 2016

Coverage Status

Coverage increased (+0.02%) to 97.211% when pulling f2e9b57 on aamalev:master into 8726c2e on aio-libs:master.

pairs = args

if kwargs:
pairs = make_flat(kwargs.items(), pairs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a point of generating tuples for every iteration? Sounds like you need in:

pairs = list(pairs)
pairs.extend(chain.from_iterable(kwargs.items())

It's all said:

In [6]: %timeit make_flat(kwargs.items(), tuple())
100 loops, best of 3: 2.45 ms per loop

In [8]: %timeit list(chain.from_iterable(kwargs.items()))
10000 loops, best of 3: 78.2 µs per loop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, but what is your kwargs?

In [46]: kwargs = dict(enumerate(string.ascii_lowercase))

In [47]: %timeit make_flat(kwargs.items(), tuple())
100000 loops, best of 3: 11.1 µs per loop

In [48]: %timeit make_flat(kwargs.items())
100000 loops, best of 3: 10.3 µs per loop

In [49]: %timeit list(chain.from_iterable(kwargs.items()))
The slowest run took 5.11 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 6.89 µs per loop

In [50]: %timeit list(chain.from_iterable(kwargs.items()))
The slowest run took 5.22 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 6.93 µs per loop

In [51]: %timeit tuple(chain.from_iterable(kwargs.items()))
100000 loops, best of 3: 6.04 µs per loop```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kwargs = {i:i for i in range(1000)}

@coveralls
Copy link

Coverage Status

Coverage decreased (-1.4%) to 95.768% when pulling 5c00508 on aamalev:master into 8726c2e on aio-libs:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage decreased (-1.4%) to 95.768% when pulling 5c00508 on aamalev:master into 8726c2e on aio-libs:master.

@coveralls
Copy link

coveralls commented Jun 22, 2016

Coverage Status

Coverage increased (+0.02%) to 97.21% when pulling 5c00508 on aamalev:master into 8726c2e on aio-libs:master.

@popravich popravich merged commit 2a12a16 into aio-libs-abandoned:master Jun 23, 2016
@popravich
Copy link
Contributor

thanks

@popravich
Copy link
Contributor

PR is squashed and merged.
However I've split hmset into original and hmset_dict to decrease ambiguity of arguments' combinations.

@aamalev
Copy link
Contributor Author

aamalev commented Jun 23, 2016

hmset_dict not the correct name, as well as the method of supporting the pair. Separate methods is that if someone thought to call the old hmset:

redis.hmset(key, field='a', value='1', 'b', '2', 'c', '3')

But I do not think that someone so does

@aamalev
Copy link
Contributor Author

aamalev commented Jun 23, 2016

make_pair can also be used in mset

@aamalev
Copy link
Contributor Author

aamalev commented Jun 23, 2016

Sorry, of course kwargs at the end

redis.hmset(key, 'b', '2', 'c', '3', field='a', value='1')

crazy?

@popravich
Copy link
Contributor

redis.hmset(key, 'b', '2', 'c', '3', field='a', value='1')

This does not work -- multiple values for positional args

@aamalev
Copy link
Contributor Author

aamalev commented Jun 23, 2016

I try to challenge the motivation of separation methods.
This work on the old method. But I think that no one uses this form

@popravich
Copy link
Contributor

Ok, I want to keep original hmset as is because I don't want something that would fail in current version to success in newer.
I don't want old hmset to magically work if someone pass invalid args,
currently everyone using hmset must call it with field-value pairs positional arguments
and if someone try to call it with a dict -- will get TypeError.
Now imagine something like this:

hopefully_its_pairs = get_something()
yield from redis.hmset(key, *hopefully_its_pairs)

# and assume `get_something` is a function with lots of if/else:
def get_something():
    if case_a:
        return 'field', 'value'
    elif case_b:
        return 'field2', 'value2'
    elif ...
    elif ...
    elif case_N:
        return {'field': "value"}

Such cases may not be covered with tests and/or may appear only in production environment.
So if hmset had changed to allowed dicts this bug would suddenly disappear.
And it sounds more like a joke -- fixing bug in user's code by upgrading 3rd-party library.

@aamalev
Copy link
Contributor Author

aamalev commented Jun 23, 2016

Reasonable.
I did not notice that hmset_dict only works with dict. For strictness propose to change the signature on

hmset_dict(key, fields: dict=None, **kwargs)

explicit arguments and simplified checks

@popravich
Copy link
Contributor

this may be conflicting with user's fields:
hmset_dict(key, {'foo': 1, 'bar': 2}, fields='foo,bar')

@aamalev
Copy link
Contributor Author

aamalev commented Jun 24, 2016

but current implementation already contains conflict

yield from redis.hmset_dict(key, key='kw')

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants