comparison mercurial/wireproto.py @ 35981:ef683a0fd21f

wireproto: define and use types for wire protocol commands Wire protocol commands have historically been declared as 2-tuples in wireproto.commands. There are some additional features I'd like to implement that require going beyond 2-tuples. But because the 2-tuple API (both destructuring assignment and direct assignment into the dict) is used throughout the code base and in 3rd party extensions, we can't do a trivial type change. This commit creates a new "commandentry" type to represent declared wire protocol commands. It implements __getitem__ and __iter__ so it can quack like a 2-tuple. The @wireprotocommand decorator now creates "commandentry" instances. We also create a "commanddict" type to represent the dictionary of declared wire protocol commands. It inherits from "dict" but provides a custom __setitem__ to coerce passed 2-tuples to "commandentry" instances. wireproto.commands is now an instance of this type. Various callers in core rely on the new functionality. And tests pass. So I'm reasonably confident things will "just work" in 3rd party extensions as well. Differential Revision: https://phab.mercurial-scm.org/D1998
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 31 Jan 2018 14:05:11 -0800
parents b4976912a6ef
children 5a56bf4180ad
comparison
equal deleted inserted replaced
35980:b4976912a6ef 35981:ef683a0fd21f
632 hint=_('usable compression engines: %s') % 632 hint=_('usable compression engines: %s') %
633 ', '.sorted(validnames)) 633 ', '.sorted(validnames))
634 634
635 return compengines 635 return compengines
636 636
637 # list of commands 637 class commandentry(object):
638 commands = {} 638 """Represents a declared wire protocol command."""
639 def __init__(self, func, args=''):
640 self.func = func
641 self.args = args
642
643 def _merge(self, func, args):
644 """Merge this instance with an incoming 2-tuple.
645
646 This is called when a caller using the old 2-tuple API attempts
647 to replace an instance. The incoming values are merged with
648 data not captured by the 2-tuple and a new instance containing
649 the union of the two objects is returned.
650 """
651 return commandentry(func, args)
652
653 # Old code treats instances as 2-tuples. So expose that interface.
654 def __iter__(self):
655 yield self.func
656 yield self.args
657
658 def __getitem__(self, i):
659 if i == 0:
660 return self.func
661 elif i == 1:
662 return self.args
663 else:
664 raise IndexError('can only access elements 0 and 1')
665
666 class commanddict(dict):
667 """Container for registered wire protocol commands.
668
669 It behaves like a dict. But __setitem__ is overwritten to allow silent
670 coercion of values from 2-tuples for API compatibility.
671 """
672 def __setitem__(self, k, v):
673 if isinstance(v, commandentry):
674 pass
675 # Cast 2-tuples to commandentry instances.
676 elif isinstance(v, tuple):
677 if len(v) != 2:
678 raise ValueError('command tuples must have exactly 2 elements')
679
680 # It is common for extensions to wrap wire protocol commands via
681 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
682 # doing this aren't aware of the new API that uses objects to store
683 # command entries, we automatically merge old state with new.
684 if k in self:
685 v = self[k]._merge(v[0], v[1])
686 else:
687 v = commandentry(v[0], v[1])
688 else:
689 raise ValueError('command entries must be commandentry instances '
690 'or 2-tuples')
691
692 return super(commanddict, self).__setitem__(k, v)
693
694 commands = commanddict()
639 695
640 def wireprotocommand(name, args=''): 696 def wireprotocommand(name, args=''):
641 """Decorator to declare a wire protocol command. 697 """Decorator to declare a wire protocol command.
642 698
643 ``name`` is the name of the wire protocol command being provided. 699 ``name`` is the name of the wire protocol command being provided.
644 700
645 ``args`` is a space-delimited list of named arguments that the command 701 ``args`` is a space-delimited list of named arguments that the command
646 accepts. ``*`` is a special value that says to accept all arguments. 702 accepts. ``*`` is a special value that says to accept all arguments.
647 """ 703 """
648 def register(func): 704 def register(func):
649 commands[name] = (func, args) 705 commands[name] = commandentry(func, args)
650 return func 706 return func
651 return register 707 return register
652 708
653 @wireprotocommand('batch', 'cmds *') 709 @wireprotocommand('batch', 'cmds *')
654 def batch(repo, proto, cmds, others): 710 def batch(repo, proto, cmds, others):