Skip to content

Commit 7e50157

Browse files
committed
refactor!: Add typings
1 parent c19cf98 commit 7e50157

20 files changed

+405
-252
lines changed

conftest.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,40 @@ def cwd_default(monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path) -> None
3434

3535
@pytest.fixture(autouse=True, scope="session")
3636
@pytest.mark.usefixtures("set_home")
37-
def xdg_config_path(user_path: pathlib.Path):
37+
def xdg_config_path(user_path: pathlib.Path) -> pathlib.Path:
3838
p = user_path / ".config"
3939
p.mkdir()
4040
return p
4141

4242

4343
@pytest.fixture(scope="function")
44-
def config_path(xdg_config_path: pathlib.Path, request: pytest.FixtureRequest):
44+
def config_path(
45+
xdg_config_path: pathlib.Path, request: pytest.FixtureRequest
46+
) -> pathlib.Path:
4547
conf_path = xdg_config_path / "vcspull"
4648
conf_path.mkdir(exist_ok=True)
4749

48-
def clean():
50+
def clean() -> None:
4951
shutil.rmtree(conf_path)
5052

5153
request.addfinalizer(clean)
5254
return conf_path
5355

5456

5557
@pytest.fixture(autouse=True)
56-
def set_xdg_config_path(monkeypatch: pytest.MonkeyPatch, xdg_config_path: pathlib.Path):
58+
def set_xdg_config_path(
59+
monkeypatch: pytest.MonkeyPatch, xdg_config_path: pathlib.Path
60+
) -> None:
5761
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_config_path))
5862

5963

6064
@pytest.fixture(scope="function")
61-
def repos_path(user_path: pathlib.Path, request: pytest.FixtureRequest):
65+
def repos_path(user_path: pathlib.Path, request: pytest.FixtureRequest) -> pathlib.Path:
6266
"""Return temporary directory for repository checkout guaranteed unique."""
6367
dir = user_path / "repos"
6468
dir.mkdir(exist_ok=True)
6569

66-
def clean():
70+
def clean() -> None:
6771
shutil.rmtree(dir)
6872

6973
request.addfinalizer(clean)

docs/conf.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# flake8: noqa E501
2+
import inspect
23
import os
34
import sys
5+
import typing as t
6+
from os.path import dirname, relpath
47
from pathlib import Path
58

9+
import vcspull
10+
611
# Get the project root dir, which is the parent dir of this
712
cwd = Path.cwd()
813
project_root = cwd.parent
@@ -60,7 +65,7 @@
6065
html_css_files = ["css/custom.css"]
6166
html_extra_path = ["manifest.json"]
6267
html_theme = "furo"
63-
html_theme_path: list = []
68+
html_theme_path: list[str] = []
6469
html_theme_options = {
6570
"light_logo": "img/vcspull.svg",
6671
"dark_logo": "img/vcspull-dark.svg",
@@ -171,13 +176,9 @@
171176
}
172177

173178

174-
def linkcode_resolve(domain, info): # NOQA: C901
175-
import inspect
176-
import sys
177-
from os.path import dirname, relpath
178-
179-
import vcspull
180-
179+
def linkcode_resolve(
180+
domain: str, info: dict[str, str]
181+
) -> t.Union[None, str]: # NOQA: C901
181182
"""
182183
Determine the URL corresponding to Python object
183184
@@ -210,7 +211,8 @@ def linkcode_resolve(domain, info): # NOQA: C901
210211
except AttributeError:
211212
pass
212213
else:
213-
obj = unwrap(obj)
214+
if callable(obj):
215+
obj = unwrap(obj)
214216

215217
try:
216218
fn = inspect.getsourcefile(obj)

scripts/generate_gitlab.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import requests
88
import yaml
99

10+
from libvcs.sync.git import GitRemote
11+
from vcspull.cli.sync import guess_vcs
12+
from vcspull.types import RawConfig
13+
1014
try:
1115
gitlab_token = os.environ["GITLAB_TOKEN"]
1216
except KeyError:
@@ -69,24 +73,37 @@
6973
sys.exit(1)
7074

7175
path_prefix = os.getcwd()
72-
config: dict = {}
76+
config: RawConfig = {}
7377

7478
for group in response.json():
7579
url_to_repo = group["ssh_url_to_repo"].replace(":", "/")
7680
namespace_path = group["namespace"]["full_path"]
7781
reponame = group["path"]
7882

79-
path = "%s/%s" % (path_prefix, namespace_path)
83+
path = f"{path_prefix}/{namespace_path}"
8084

8185
if path not in config:
8286
config[path] = {}
8387

8488
# simplified config not working - https://github.com/vcs-python/vcspull/issues/332
8589
# config[path][reponame] = 'git+ssh://%s' % (url_to_repo)
8690

91+
vcs = guess_vcs(url_to_repo)
92+
if vcs is None:
93+
raise Exception(f"Could not guess VCS for URL: {url_to_repo}")
94+
8795
config[path][reponame] = {
88-
"url": "git+ssh://%s" % (url_to_repo),
89-
"remotes": {"origin": "ssh://%s" % (url_to_repo)},
96+
"name": reponame,
97+
"dir": path / reponame,
98+
"url": f"git+ssh://{url_to_repo}",
99+
"remotes": {
100+
"origin": GitRemote(
101+
name="origin",
102+
fetch_url=f"ssh://{url_to_repo}",
103+
push_url=f"ssh://{url_to_repo}",
104+
)
105+
},
106+
"vcs": vcs,
90107
}
91108

92109
config_yaml = yaml.dump(config)

src/vcspull/_internal/config_reader.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ class ConfigReader:
2222
'{\n "session_name": "my session"\n}'
2323
"""
2424

25-
def __init__(self, content: "RawConfigData"):
25+
def __init__(self, content: "RawConfigData") -> None:
2626
self.content = content
2727

2828
@staticmethod
29-
def _load(format: "FormatLiteral", content: str):
29+
def _load(format: "FormatLiteral", content: str) -> t.Dict[str, t.Any]:
3030
"""Load raw config data and directly return it.
3131
3232
>>> ConfigReader._load("json", '{ "session_name": "my session" }')
@@ -36,17 +36,20 @@ def _load(format: "FormatLiteral", content: str):
3636
{'session_name': 'my session'}
3737
"""
3838
if format == "yaml":
39-
return yaml.load(
40-
content,
41-
Loader=yaml.SafeLoader,
39+
return t.cast(
40+
t.Dict[str, t.Any],
41+
yaml.load(
42+
content,
43+
Loader=yaml.SafeLoader,
44+
),
4245
)
4346
elif format == "json":
44-
return json.loads(content)
47+
return t.cast(t.Dict[str, t.Any], json.loads(content))
4548
else:
4649
raise NotImplementedError(f"{format} not supported in configuration")
4750

4851
@classmethod
49-
def load(cls, format: "FormatLiteral", content: str):
52+
def load(cls, format: "FormatLiteral", content: str) -> "ConfigReader":
5053
"""Load raw config data into a ConfigReader instance (to dump later).
5154
5255
>>> cfg = ConfigReader.load("json", '{ "session_name": "my session" }')
@@ -69,7 +72,7 @@ def load(cls, format: "FormatLiteral", content: str):
6972
)
7073

7174
@classmethod
72-
def _from_file(cls, path: pathlib.Path):
75+
def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]:
7376
r"""Load data from file path directly to dictionary.
7477
7578
**YAML file**
@@ -102,7 +105,7 @@ def _from_file(cls, path: pathlib.Path):
102105
content = open(path).read()
103106

104107
if path.suffix in [".yaml", ".yml"]:
105-
format: FormatLiteral = "yaml"
108+
format: "FormatLiteral" = "yaml"
106109
elif path.suffix == ".json":
107110
format = "json"
108111
else:
@@ -114,7 +117,7 @@ def _from_file(cls, path: pathlib.Path):
114117
)
115118

116119
@classmethod
117-
def from_file(cls, path: pathlib.Path):
120+
def from_file(cls, path: pathlib.Path) -> "ConfigReader":
118121
r"""Load data from file path
119122
120123
**YAML file**
@@ -169,11 +172,14 @@ def _dump(
169172
'{\n "session_name": "my session"\n}'
170173
"""
171174
if format == "yaml":
172-
return yaml.dump(
173-
content,
174-
indent=2,
175-
default_flow_style=False,
176-
Dumper=yaml.SafeDumper,
175+
return t.cast(
176+
str,
177+
yaml.dump(
178+
content,
179+
indent=2,
180+
default_flow_style=False,
181+
Dumper=yaml.SafeDumper,
182+
),
177183
)
178184
elif format == "json":
179185
return json.dumps(

src/vcspull/cli/__init__.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import argparse
88
import logging
99
import textwrap
10+
import typing as t
11+
from typing import overload
1012

1113
from libvcs.__about__ import __version__ as libvcs_version
1214

@@ -30,7 +32,21 @@
3032
).strip()
3133

3234

33-
def create_parser(return_subparsers: bool = False):
35+
@overload
36+
def create_parser(
37+
return_subparsers: t.Literal[True],
38+
) -> t.Tuple[argparse.ArgumentParser, t.Any]:
39+
...
40+
41+
42+
@overload
43+
def create_parser(return_subparsers: t.Literal[False]) -> argparse.ArgumentParser:
44+
...
45+
46+
47+
def create_parser(
48+
return_subparsers: bool = False,
49+
) -> t.Union[argparse.ArgumentParser, t.Tuple[argparse.ArgumentParser, t.Any]]:
3450
parser = argparse.ArgumentParser(
3551
prog="vcspull",
3652
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -64,9 +80,9 @@ def create_parser(return_subparsers: bool = False):
6480
return parser
6581

6682

67-
def cli(args=None):
83+
def cli(_args: t.Optional[t.List[str]] = None) -> None:
6884
parser, sync_parser = create_parser(return_subparsers=True)
69-
args = parser.parse_args(args)
85+
args = parser.parse_args(_args)
7086

7187
setup_logger(log=log, level=args.log_level.upper())
7288

src/vcspull/cli/sync.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import argparse
22
import logging
3+
import pathlib
34
import sys
45
import typing as t
56
from copy import deepcopy
7+
from datetime import datetime
68

79
from libvcs._internal.shortcuts import create_project
10+
from libvcs._internal.types import VCSLiteral
11+
from libvcs.sync.git import GitSync
812
from libvcs.url import registry as url_tools
913

1014
from ..config import filter_repos, find_config_files, load_configs
1115

1216
log = logging.getLogger(__name__)
1317

1418

15-
def clamp(n, _min, _max):
19+
def clamp(n: int, _min: int, _max: int) -> int:
1620
return max(_min, min(n, _max))
1721

1822

@@ -51,8 +55,8 @@ def create_sync_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP
5155

5256

5357
def sync(
54-
repo_patterns,
55-
config,
58+
repo_patterns: t.List[str],
59+
config: pathlib.Path,
5660
exit_on_error: bool,
5761
parser: t.Optional[
5862
argparse.ArgumentParser
@@ -102,12 +106,28 @@ def sync(
102106
raise SystemExit(EXIT_ON_ERROR_MSG)
103107

104108

105-
def progress_cb(output, timestamp):
109+
def progress_cb(output: str, timestamp: datetime) -> None:
106110
sys.stdout.write(output)
107111
sys.stdout.flush()
108112

109113

110-
def update_repo(repo_dict):
114+
def guess_vcs(url: str) -> t.Optional[VCSLiteral]:
115+
vcs_matches = url_tools.registry.match(url=url, is_explicit=True)
116+
117+
if len(vcs_matches) == 0:
118+
log.warning(f"No vcs found for {url}")
119+
return None
120+
if len(vcs_matches) > 1:
121+
log.warning(f"No exact matches for {url}")
122+
return None
123+
124+
return t.cast(VCSLiteral, vcs_matches[0].vcs)
125+
126+
127+
def update_repo(
128+
repo_dict: t.Any,
129+
# repo_dict: Dict[str, Union[str, Dict[str, GitRemote], pathlib.Path]]
130+
) -> GitSync:
111131
repo_dict = deepcopy(repo_dict)
112132
if "pip_url" not in repo_dict:
113133
repo_dict["pip_url"] = repo_dict.pop("url")
@@ -116,16 +136,16 @@ def update_repo(repo_dict):
116136
repo_dict["progress_callback"] = progress_cb
117137

118138
if repo_dict.get("vcs") is None:
119-
vcs_matches = url_tools.registry.match(url=repo_dict["url"], is_explicit=True)
120-
121-
if len(vcs_matches) == 0:
122-
raise Exception(f"No vcs found for {repo_dict}")
123-
if len(vcs_matches) > 1:
124-
raise Exception(f"No exact matches for {repo_dict}")
139+
vcs = guess_vcs(url=repo_dict["url"])
140+
if vcs is None:
141+
raise Exception(
142+
f'Could not automatically determine VCS for {repo_dict["url"]}'
143+
)
125144

126-
repo_dict["vcs"] = vcs_matches[0].vcs
145+
repo_dict["vcs"] = vcs
127146

128147
r = create_project(**repo_dict) # Creates the repo object
129148
r.update_repo(set_remotes=True) # Creates repo if not exists and fetches
130149

131-
return r
150+
# TODO: Fix this
151+
return r # type:ignore

0 commit comments

Comments
 (0)