Skip to content

Commit 4b3f85d

Browse files
committed
Improve typing: add RemotesCollection to Repository
The following command mypy test reports Found 284 errors in 9 files (checked 50 source files) after this change.
1 parent 823af24 commit 4b3f85d

File tree

5 files changed

+151
-59
lines changed

5 files changed

+151
-59
lines changed

pygit2/_pygit2.pyi

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Iterator, Literal, Optional, overload, Type
1+
from typing import Iterator, Literal, Optional, overload, Type, TypedDict
22
from io import IOBase
33
from . import Index
44
from .enums import (
@@ -19,8 +19,10 @@ from .enums import (
1919
ResetMode,
2020
SortMode,
2121
)
22+
from collections.abc import Generator
2223

2324
from .repository import BaseRepository
25+
from .remotes import Remote
2426

2527
GIT_OBJ_BLOB = Literal[3]
2628
GIT_OBJ_COMMIT = Literal[1]
@@ -346,6 +348,48 @@ class References:
346348
def objects(self) -> list[Reference]: ...
347349
def compress(self) -> None: ...
348350

351+
_Proxy = None | Literal[True] | str
352+
353+
class _StrArray:
354+
# incomplete
355+
count: int
356+
357+
class ProxyOpts:
358+
# incomplete
359+
type: object
360+
url: str
361+
362+
class PushOptions:
363+
version: int
364+
pb_parallelism: int
365+
callbacks: object # TODO
366+
proxy_opts: ProxyOpts
367+
follow_redirects: object # TODO
368+
custom_headers: _StrArray
369+
remote_push_options: _StrArray
370+
371+
class _LsRemotesDict(TypedDict):
372+
local: bool
373+
loid: Oid | None
374+
name: str | None
375+
symref_target: str | None
376+
oid: Oid
377+
378+
class RemoteCollection:
379+
def __init__(self, repo: BaseRepository) -> None: ...
380+
def __len__(self) -> int: ...
381+
def __iter__(self): ...
382+
def __getitem__(self, name: str | int) -> Remote: ...
383+
def names(self) -> Generator[str, None, None]: ...
384+
def create(self, name: str, url: str, fetch: str | None = None) -> Remote: ...
385+
def create_anonymous(self, url: str) -> Remote: ...
386+
def rename(self, name: str, new_name: str) -> list[str]: ...
387+
def delete(self, name: str) -> None: ...
388+
def set_url(self, name: str, url: str) -> None: ...
389+
def set_push_url(self, name: str, url: str) -> None: ...
390+
def add_fetch(self, name: str, refspec: str) -> None: ...
391+
def add_push(self, name: str, refspec: str) -> None: ...
392+
349393
class Repository:
350394
_pointer: bytes
351395
default_signature: Signature
@@ -360,6 +404,7 @@ class Repository:
360404
refdb: Refdb
361405
workdir: str
362406
references: References
407+
remotes: RemoteCollection
363408
def __init__(self, *args, **kwargs) -> None: ...
364409
def TreeBuilder(self, src: Tree | _OidArg = ...) -> TreeBuilder: ...
365410
def _disown(self, *args, **kwargs) -> None: ...

pygit2/callbacks.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,29 @@
6565
# Standard Library
6666
from contextlib import contextmanager
6767
from functools import wraps
68-
from typing import Optional, Union
68+
from typing import Optional, Union, TYPE_CHECKING, Callable, Generator
6969

7070
# pygit2
7171
from ._pygit2 import Oid, DiffFile
7272
from .enums import CheckoutNotify, CheckoutStrategy, CredentialType, StashApplyProgress
7373
from .errors import check_error, Passthrough
7474
from .ffi import ffi, C
7575
from .utils import maybe_string, to_bytes, ptr_to_bytes, StrArray
76+
from .credentials import Username, UserPass, Keypair
7677

78+
_Credentials = Username | UserPass | Keypair
7779

80+
if TYPE_CHECKING:
81+
from .remotes import TransferProgress
82+
from ._pygit2 import ProxyOpts, PushOptions
7883
#
7984
# The payload is the way to pass information from the pygit2 API, through
8085
# libgit2, to the Python callbacks. And back.
8186
#
8287

8388

8489
class Payload:
85-
def __init__(self, **kw: object):
90+
def __init__(self, **kw: object) -> None:
8691
for key, value in kw.items():
8792
setattr(self, key, value)
8893
self._stored_exception = None
@@ -113,12 +118,18 @@ class RemoteCallbacks(Payload):
113118
RemoteCallbacks(certificate=certificate).
114119
"""
115120

116-
def __init__(self, credentials=None, certificate_check=None):
121+
push_options: 'PushOptions'
122+
123+
def __init__(
124+
self,
125+
credentials: _Credentials | None = None,
126+
certificate_check: Callable[[None, bool, bytes], bool] | None = None,
127+
) -> None:
117128
super().__init__()
118129
if credentials is not None:
119-
self.credentials = credentials
130+
self.credentials = credentials # type: ignore[method-assign, assignment]
120131
if certificate_check is not None:
121-
self.certificate_check = certificate_check
132+
self.certificate_check = certificate_check # type: ignore[method-assign, assignment]
122133

123134
def sideband_progress(self, string: str) -> None:
124135
"""
@@ -136,7 +147,7 @@ def credentials(
136147
url: str,
137148
username_from_url: Union[str, None],
138149
allowed_types: CredentialType,
139-
):
150+
) -> _Credentials:
140151
"""
141152
Credentials callback. If the remote server requires authentication,
142153
this function will be called and its return value used for
@@ -159,7 +170,7 @@ def credentials(
159170
"""
160171
raise Passthrough
161172

162-
def certificate_check(self, certificate: None, valid: bool, host: str) -> bool:
173+
def certificate_check(self, certificate: None, valid: bool, host: bytes) -> bool:
163174
"""
164175
Certificate callback. Override with your own function to determine
165176
whether to accept the server's certificate.
@@ -181,7 +192,7 @@ def certificate_check(self, certificate: None, valid: bool, host: str) -> bool:
181192

182193
raise Passthrough
183194

184-
def transfer_progress(self, stats):
195+
def transfer_progress(self, stats: 'TransferProgress') -> None:
185196
"""
186197
During the download of new data, this will be regularly called with
187198
the indexer's progress.
@@ -196,7 +207,7 @@ def transfer_progress(self, stats):
196207

197208
def push_transfer_progress(
198209
self, objects_pushed: int, total_objects: int, bytes_pushed: int
199-
):
210+
) -> None:
200211
"""
201212
During the upload portion of a push, this will be regularly called
202213
with progress information.
@@ -207,7 +218,7 @@ def push_transfer_progress(
207218
Override with your own function to report push transfer progress.
208219
"""
209220

210-
def update_tips(self, refname, old, new):
221+
def update_tips(self, refname: str, old: Oid, new: Oid) -> None:
211222
"""
212223
Update tips callback. Override with your own function to report
213224
reference updates.
@@ -224,7 +235,7 @@ def update_tips(self, refname, old, new):
224235
The reference's new value.
225236
"""
226237

227-
def push_update_reference(self, refname, message):
238+
def push_update_reference(self, refname: str, message: str) -> None:
228239
"""
229240
Push update reference callback. Override with your own function to
230241
report the remote's acceptance or rejection of reference updates.
@@ -244,7 +255,7 @@ class CheckoutCallbacks(Payload):
244255
in your class, which you can then pass to checkout operations.
245256
"""
246257

247-
def __init__(self):
258+
def __init__(self) -> None:
248259
super().__init__()
249260

250261
def checkout_notify_flags(self) -> CheckoutNotify:
@@ -275,7 +286,7 @@ def checkout_notify(
275286
baseline: Optional[DiffFile],
276287
target: Optional[DiffFile],
277288
workdir: Optional[DiffFile],
278-
):
289+
) -> None:
279290
"""
280291
Checkout will invoke an optional notification callback for
281292
certain cases - you pick which ones via `checkout_notify_flags`.
@@ -290,7 +301,9 @@ def checkout_notify(
290301
"""
291302
pass
292303

293-
def checkout_progress(self, path: str, completed_steps: int, total_steps: int):
304+
def checkout_progress(
305+
self, path: str, completed_steps: int, total_steps: int
306+
) -> None:
294307
"""
295308
Optional callback to notify the consumer of checkout progress.
296309
"""
@@ -304,7 +317,7 @@ class StashApplyCallbacks(CheckoutCallbacks):
304317
in your class, which you can then pass to stash apply or pop operations.
305318
"""
306319

307-
def stash_apply_progress(self, progress: StashApplyProgress):
320+
def stash_apply_progress(self, progress: StashApplyProgress) -> None:
308321
"""
309322
Stash application progress notification function.
310323
@@ -373,9 +386,9 @@ def git_fetch_options(payload, opts=None):
373386
@contextmanager
374387
def git_proxy_options(
375388
payload: object,
376-
opts: object | None = None,
389+
opts: Optional['ProxyOpts'] = None,
377390
proxy: None | bool | str = None,
378-
):
391+
) -> Generator['ProxyOpts', None, None]:
379392
if opts is None:
380393
opts = ffi.new('git_proxy_options *')
381394
C.git_proxy_options_init(opts, C.GIT_PROXY_OPTIONS_VERSION)
@@ -386,8 +399,8 @@ def git_proxy_options(
386399
elif type(proxy) is str:
387400
opts.type = C.GIT_PROXY_SPECIFIED
388401
# Keep url in memory, otherwise memory is freed and bad things happen
389-
payload.__proxy_url = ffi.new('char[]', to_bytes(proxy))
390-
opts.url = payload.__proxy_url
402+
payload.__proxy_url = ffi.new('char[]', to_bytes(proxy)) # type: ignore[attr-defined, no-untyped-call]
403+
opts.url = payload.__proxy_url # type: ignore[attr-defined]
391404
else:
392405
raise TypeError('Proxy must be None, True, or a string')
393406
yield opts

pygit2/credentials.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def credential_type(self) -> CredentialType:
4949
return CredentialType.USERNAME
5050

5151
@property
52-
def credential_tuple(self):
52+
def credential_tuple(self) -> tuple[str]:
5353
return (self._username,)
5454

5555
def __call__(
@@ -74,7 +74,7 @@ def credential_type(self) -> CredentialType:
7474
return CredentialType.USERPASS_PLAINTEXT
7575

7676
@property
77-
def credential_tuple(self):
77+
def credential_tuple(self) -> tuple[str, str]:
7878
return (self._username, self._password)
7979

8080
def __call__(
@@ -107,7 +107,11 @@ class Keypair:
107107
"""
108108

109109
def __init__(
110-
self, username: str, pubkey: str | Path, privkey: str | Path, passphrase: str
110+
self,
111+
username: str,
112+
pubkey: str | Path | None,
113+
privkey: str | Path | None,
114+
passphrase: str | None,
111115
):
112116
self._username = username
113117
self._pubkey = pubkey
@@ -119,7 +123,9 @@ def credential_type(self) -> CredentialType:
119123
return CredentialType.SSH_KEY
120124

121125
@property
122-
def credential_tuple(self):
126+
def credential_tuple(
127+
self,
128+
) -> tuple[str, str | Path | None, str | Path | None, str | None]:
123129
return (self._username, self._pubkey, self._privkey, self._passphrase)
124130

125131
def __call__(

pygit2/remotes.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
# Boston, MA 02110-1301, USA.
2525

2626
from __future__ import annotations
27-
from typing import TYPE_CHECKING
27+
from typing import TYPE_CHECKING, Any
2828

2929
# Import from pygit2
3030
from ._pygit2 import Oid
@@ -49,7 +49,15 @@
4949
class TransferProgress:
5050
"""Progress downloading and indexing data during a fetch."""
5151

52-
def __init__(self, tp):
52+
total_objects: int
53+
indexed_objects: int
54+
received_objects: int
55+
local_objects: int
56+
total_deltas: int
57+
indexed_deltas: int
58+
received_bytes: int
59+
60+
def __init__(self, tp: Any) -> None:
5361
self.total_objects = tp.total_objects
5462
"""Total number of objects to download"""
5563

0 commit comments

Comments
 (0)