Mercurial > public > mercurial-scm > hg
comparison mercurial/bundle2.py @ 51667:6fc31e7bd5db
typing: add some type hints for bundle2 capabilities
Somewhere between hg 3dbc7b1ecaba and hg 8e3f6b5bf720, pytype determined the
signature of `bundle20.capabilities` changed from `Dict[bytes, Tuple[bytes]]` to
`Dict[bytes, Union[List[bytes], Tuple[bytes]]]`.
First, I did try to simply be explicit about the previously inferred type, but
it does seem to mix and match list/tuple now (e.g. in `writenewbundle()`). I
tried changing the new list usage to tuple, but a couple of things complained,
(and I think lists of one item are a little more clear to read anyway). So then
I typed the dict value as `Sequence[bytes]`, which worked fine. But there's
also a module level `capabilities` field, and when that's typed, pytype
complains about `Sequence[bytes]` lacking `__add__`[1]. So I gave up, and just
assigned it the type it wanted, with an alias. If somebody feels motivated to
make the type consistent, it's simple enough to change the alias.
The mutable default value to the constructor was removed to appease PyCharm's
type checking on the field. (I didn't bother running the code through pytype
prior to changing it, because we've previously made an effort to remove this
pattern anyway.)
I'm not sure why `getrepocaps()` has a default value for `role` that apparently
raises an exception. It's just flagged for now so this series can land without
risking additional problems.
[1] https://foss.heptapod.net/mercurial/mercurial-devel/-/jobs/2466903
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Wed, 10 Jul 2024 17:09:34 -0400 |
parents | a0f1378b932e |
children | 493034cc3265 |
comparison
equal
deleted
inserted
replaced
51666:a09435c0eb14 | 51667:6fc31e7bd5db |
---|---|
151 import os | 151 import os |
152 import re | 152 import re |
153 import string | 153 import string |
154 import struct | 154 import struct |
155 import sys | 155 import sys |
156 import typing | |
156 | 157 |
157 from .i18n import _ | 158 from .i18n import _ |
158 from .node import ( | 159 from .node import ( |
159 hex, | 160 hex, |
160 short, | 161 short, |
178 from .utils import ( | 179 from .utils import ( |
179 stringutil, | 180 stringutil, |
180 urlutil, | 181 urlutil, |
181 ) | 182 ) |
182 from .interfaces import repository | 183 from .interfaces import repository |
184 | |
185 if typing.TYPE_CHECKING: | |
186 from typing import ( | |
187 Dict, | |
188 List, | |
189 Optional, | |
190 Tuple, | |
191 Union, | |
192 ) | |
193 | |
194 Capabilities = Dict[bytes, Union[List[bytes], Tuple[bytes, ...]]] | |
183 | 195 |
184 urlerr = util.urlerr | 196 urlerr = util.urlerr |
185 urlreq = util.urlreq | 197 urlreq = util.urlreq |
186 | 198 |
187 _pack = struct.pack | 199 _pack = struct.pack |
600 outpart.addparam( | 612 outpart.addparam( |
601 b'in-reply-to', pycompat.bytestr(part.id), mandatory=False | 613 b'in-reply-to', pycompat.bytestr(part.id), mandatory=False |
602 ) | 614 ) |
603 | 615 |
604 | 616 |
605 def decodecaps(blob): | 617 def decodecaps(blob: bytes) -> "Capabilities": |
606 """decode a bundle2 caps bytes blob into a dictionary | 618 """decode a bundle2 caps bytes blob into a dictionary |
607 | 619 |
608 The blob is a list of capabilities (one per line) | 620 The blob is a list of capabilities (one per line) |
609 Capabilities may have values using a line of the form:: | 621 Capabilities may have values using a line of the form:: |
610 | 622 |
660 populate it. Then call `getchunks` to retrieve all the binary chunks of | 672 populate it. Then call `getchunks` to retrieve all the binary chunks of |
661 data that compose the bundle2 container.""" | 673 data that compose the bundle2 container.""" |
662 | 674 |
663 _magicstring = b'HG20' | 675 _magicstring = b'HG20' |
664 | 676 |
665 def __init__(self, ui, capabilities=()): | 677 def __init__(self, ui, capabilities: "Optional[Capabilities]" = None): |
678 if capabilities is None: | |
679 capabilities = {} | |
680 | |
666 self.ui = ui | 681 self.ui = ui |
667 self._params = [] | 682 self._params = [] |
668 self._parts = [] | 683 self._parts = [] |
669 self.capabilities = dict(capabilities) | 684 self.capabilities: "Capabilities" = dict(capabilities) |
670 self._compengine = util.compengines.forbundletype(b'UN') | 685 self._compengine = util.compengines.forbundletype(b'UN') |
671 self._compopts = None | 686 self._compopts = None |
672 # If compression is being handled by a consumer of the raw | 687 # If compression is being handled by a consumer of the raw |
673 # data (e.g. the wire protocol), unsetting this flag tells | 688 # data (e.g. the wire protocol), unsetting this flag tells |
674 # consumers that the bundle is best left uncompressed. | 689 # consumers that the bundle is best left uncompressed. |
1610 return None | 1625 return None |
1611 | 1626 |
1612 | 1627 |
1613 # These are only the static capabilities. | 1628 # These are only the static capabilities. |
1614 # Check the 'getrepocaps' function for the rest. | 1629 # Check the 'getrepocaps' function for the rest. |
1615 capabilities = { | 1630 capabilities: "Capabilities" = { |
1616 b'HG20': (), | 1631 b'HG20': (), |
1617 b'bookmarks': (), | 1632 b'bookmarks': (), |
1618 b'error': (b'abort', b'unsupportedcontent', b'pushraced', b'pushkey'), | 1633 b'error': (b'abort', b'unsupportedcontent', b'pushraced', b'pushkey'), |
1619 b'listkeys': (), | 1634 b'listkeys': (), |
1620 b'pushkey': (), | 1635 b'pushkey': (), |
1624 b'phases': (b'heads',), | 1639 b'phases': (b'heads',), |
1625 b'stream': (b'v2',), | 1640 b'stream': (b'v2',), |
1626 } | 1641 } |
1627 | 1642 |
1628 | 1643 |
1629 def getrepocaps(repo, allowpushback=False, role=None): | 1644 # TODO: drop the default value for 'role' |
1645 def getrepocaps(repo, allowpushback: bool = False, role=None) -> "Capabilities": | |
1630 """return the bundle2 capabilities for a given repo | 1646 """return the bundle2 capabilities for a given repo |
1631 | 1647 |
1632 Exists to allow extensions (like evolution) to mutate the capabilities. | 1648 Exists to allow extensions (like evolution) to mutate the capabilities. |
1633 | 1649 |
1634 The returned value is used for servers advertising their capabilities as | 1650 The returned value is used for servers advertising their capabilities as |
1673 # for legacy clients. | 1689 # for legacy clients. |
1674 | 1690 |
1675 return caps | 1691 return caps |
1676 | 1692 |
1677 | 1693 |
1678 def bundle2caps(remote): | 1694 def bundle2caps(remote) -> "Capabilities": |
1679 """return the bundle capabilities of a peer as dict""" | 1695 """return the bundle capabilities of a peer as dict""" |
1680 raw = remote.capable(b'bundle2') | 1696 raw = remote.capable(b'bundle2') |
1681 if not raw and raw != b'': | 1697 if not raw and raw != b'': |
1682 return {} | 1698 return {} |
1683 capsblob = urlreq.unquote(remote.capable(b'bundle2')) | 1699 capsblob = urlreq.unquote(remote.capable(b'bundle2')) |
1684 return decodecaps(capsblob) | 1700 return decodecaps(capsblob) |
1685 | 1701 |
1686 | 1702 |
1687 def obsmarkersversion(caps): | 1703 def obsmarkersversion(caps: "Capabilities"): |
1688 """extract the list of supported obsmarkers versions from a bundle2caps dict""" | 1704 """extract the list of supported obsmarkers versions from a bundle2caps dict""" |
1689 obscaps = caps.get(b'obsmarkers', ()) | 1705 obscaps = caps.get(b'obsmarkers', ()) |
1690 return [int(c[1:]) for c in obscaps if c.startswith(b'V')] | 1706 return [int(c[1:]) for c in obscaps if c.startswith(b'V')] |
1691 | 1707 |
1692 | 1708 |
1723 count = len(repo.revs(b'%ln and _internal()', outgoing.missing)) | 1739 count = len(repo.revs(b'%ln and _internal()', outgoing.missing)) |
1724 msg = "backup bundle would contains %d internal changesets" | 1740 msg = "backup bundle would contains %d internal changesets" |
1725 msg %= count | 1741 msg %= count |
1726 raise error.ProgrammingError(msg) | 1742 raise error.ProgrammingError(msg) |
1727 | 1743 |
1728 caps = {} | 1744 caps: "Capabilities" = {} |
1729 if opts.get(b'obsolescence', False): | 1745 if opts.get(b'obsolescence', False): |
1730 caps[b'obsmarkers'] = (b'V1',) | 1746 caps[b'obsmarkers'] = (b'V1',) |
1731 stream_version = opts.get(b'stream', b"") | 1747 stream_version = opts.get(b'stream', b"") |
1732 if stream_version == b"v2": | 1748 if stream_version == b"v2": |
1733 caps[b'stream'] = [b'v2'] | 1749 caps[b'stream'] = [b'v2'] |