Mercurial > public > mercurial-scm > hg
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): |