diff --git a/.gitignore b/.gitignore index 082793a..afcc414 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,8 @@ dmypy.json # PyCharm .idea/ -# tests poetry install +# tests +.nox/ .tox/ .coverage \ No newline at end of file diff --git a/many_repos/config.py b/many_repos/config.py index b5e4b62..421917c 100644 --- a/many_repos/config.py +++ b/many_repos/config.py @@ -25,12 +25,16 @@ class Source(NamedTuple): class Config(NamedTuple): output_dir: str | Path - sources_configs: dict[str, Source] | None + sources_configs: dict[str, Source] | dict @classmethod def from_dict(cls, _dict: dict[str, Any]) -> Self: - output_dir = _dict.get("config").get("output_dir", None) - sources = {name: Source.from_dict(src) for name, src in _dict.get("sources").items()} + + if not (cfg := _dict.get("config")): + raise errors.InvalidConfig("`[config]` cannot be empty!") + + output_dir = cfg.get("output_dir", None) + sources = {name: Source.from_dict(src) for name, src in _dict.get("sources", dict()).items()} if not output_dir: raise errors.InvalidConfig("`output_dir` needs to be specified!") cfg = cls(output_dir=output_dir, sources_configs=sources) diff --git a/many_repos/errors.py b/many_repos/errors.py index 5453c30..7ef3090 100644 --- a/many_repos/errors.py +++ b/many_repos/errors.py @@ -11,3 +11,8 @@ class InvalidConfig(Exception): class InvalidSource(Exception): """Raised when invalid source is used""" pass + + +class HTTPError(Exception): + """Raised when API rejects our request""" + pass diff --git a/many_repos/source_type/__init__.py b/many_repos/source_type/__init__.py index 6f3276d..ca0d220 100644 --- a/many_repos/source_type/__init__.py +++ b/many_repos/source_type/__init__.py @@ -1,13 +1,4 @@ -import importlib - -from . import base -from .. import errors - - -def get(source_name: str) -> type[base.BaseSource]: - try: - module = importlib.import_module(f"many_repos.source_type.{source_name}") - return module.Source - except (ModuleNotFoundError, AttributeError): - raise errors.InvalidSource(f"Source {source_name} not found!") - +from .utils import get +from . import github +from . import gitlab +from . import gitlab_group diff --git a/many_repos/source_type/base.py b/many_repos/source_type/base.py index 06a5782..09ec23b 100644 --- a/many_repos/source_type/base.py +++ b/many_repos/source_type/base.py @@ -1,8 +1,9 @@ import abc import json +from http.client import HTTPResponse from urllib import request -from many_repos import common +from many_repos import common, errors from many_repos import config @@ -36,6 +37,9 @@ class BaseSource(abc.ABC): """ Helper function to create http requests using urllib """ + if not headers: + headers = dict() + if body: headers["Content-Type"] = "application/json" data = json.dumps(body).encode('utf-8') @@ -48,5 +52,12 @@ class BaseSource(abc.ABC): headers=headers, data=data ) - res = request.urlopen(req) + + res: HTTPResponse = request.urlopen(req) + + if 400 <= res.status < 500: + raise errors.HTTPError(f"Client failed to make http request: {res.reason}") + if 500 <= res.status < 600: + raise errors.HTTPError(f"Server failed to respond to our request: {res.reason}") + return json.load(res) diff --git a/many_repos/source_type/github.py b/many_repos/source_type/github.py index be7bd31..7418187 100644 --- a/many_repos/source_type/github.py +++ b/many_repos/source_type/github.py @@ -11,13 +11,13 @@ class Source(BaseSource): for repo in repos_json: if not self.source_config.forks and repo.get("fork"): continue - namespace, name = repo.get("full_name").split("/", 2) + namespace, name = repo["full_name"].split("/", 2) repositories.append( common.Repository( name=name, namespace=namespace, - url=repo.get("ssh_url"), - fork=repo.get("fork"), + url=repo["ssh_url"], + fork=repo["fork"], vcs="github.com" ) ) diff --git a/many_repos/source_type/gitlab.py b/many_repos/source_type/gitlab.py index ea82406..4fe76da 100644 --- a/many_repos/source_type/gitlab.py +++ b/many_repos/source_type/gitlab.py @@ -7,13 +7,13 @@ class Source(BaseSource): repos_json = self._make_request(self._api_url, headers=self._headers) repositories = [] for repo in repos_json: - namespace, name = repo.get("path_with_namespace").split("/") + namespace, name = repo["path_with_namespace"].split("/") repositories.append( common.Repository( name=name, namespace=namespace, - url=repo.get("ssh_url_to_repo"), - fork=bool(repo.get("forked_from_project")), + url=repo["ssh_url_to_repo"], + fork=bool(repo["forked_from_project"]), vcs="gitlab.com" ) ) diff --git a/many_repos/source_type/utils.py b/many_repos/source_type/utils.py index e69de29..d5766aa 100644 --- a/many_repos/source_type/utils.py +++ b/many_repos/source_type/utils.py @@ -0,0 +1,12 @@ +import importlib + +from . import base +from .. import errors + + +def get(source_name: str) -> type[base.BaseSource]: + try: + module = importlib.import_module(f"many_repos.source_type.{source_name}") + return module.Source + except (ModuleNotFoundError, AttributeError): + raise errors.InvalidSource(f"Source {source_name} not found!") diff --git a/noxfile.py b/noxfile.py index 20fbfc7..e6e278b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -9,11 +9,11 @@ def tests(s): s.run('pytest', 'tests') -@session(python='python3.12') +@session(python='python3.12', name="type") def _type(s): """run type checks""" s.install('mypy') - s.run('mypy', 'many_repos', 'tests') + s.run('mypy', 'many_repos') @session(python='python3.12') diff --git a/pyproject.toml b/pyproject.toml index f42b21f..f0ea8f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ many-repos-clone = "many_repos.clone:main" [tool.poetry.dependencies] python = "^3.11" - [tool.poetry.group.dev.dependencies] pytest = "^8.2.1" ruff = "^0.4.5" diff --git a/tests/common_test.py b/tests/common_test.py new file mode 100644 index 0000000..bac5c05 --- /dev/null +++ b/tests/common_test.py @@ -0,0 +1,8 @@ +from pathlib import Path + +from many_repos.clone import _construct_path + + +def test_correct_construct_path(test_repo, many_repos_config): + assert _construct_path(test_repo, many_repos_config) == ( + Path(many_repos_config.output_dir) / test_repo.vcs / test_repo.namespace / test_repo.name).resolve() diff --git a/tests/config_test.py b/tests/config_test.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..432e145 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,67 @@ +import pytest + +from many_repos.common import Repository +from many_repos.config import Source, Config + + +@pytest.fixture(params=[True, False]) +def source_config(request): + return Source( + source_type="github", + username="JustScreaMy", + token="testToken", + forks=request.param + ) + + +@pytest.fixture +def test_repo(): + return Repository( + name="many-repos", + namespace="JustScreaMy", + url="git@github.com/JustScreaMy/many-repos.git", + fork=False, + vcs="github.com" + ) + + +@pytest.fixture +def many_repos_config(): + return Config( + output_dir="/tmp/test/repos", + sources_configs={ + "gitlab": Source( + source_type="gitlab", + username="JustScreaMy", + token="testToken", + forks=False + ), + "github": Source( + source_type="github", + username="JustScreaMy", + token="testTokasden", + forks=False + ) + } + ) + + +@pytest.fixture +def invalid_many_repos_config(): + return Config( + output_dir="/tmp/test/repos", + sources_configs={ + "gitlab": Source( + source_type="gitlab", + username="JustScreaMy", + token="testToken", + forks=False + ), + "bitbucket": Source( + source_type="bitbucket", + username="JustScreaMy", + token="testTokasden", + forks=False + ) + } + ) diff --git a/tests/dummy_test.py b/tests/dummy_test.py deleted file mode 100644 index 158613b..0000000 --- a/tests/dummy_test.py +++ /dev/null @@ -1,3 +0,0 @@ -class TestDummy: - def test_dummy(self): - assert 1 == 1 diff --git a/tests/source_type/conftest.py b/tests/source_type/conftest.py deleted file mode 100644 index c825826..0000000 --- a/tests/source_type/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -import pytest - -from many_repos.config import Source - - -@pytest.fixture(params=[True, False]) -def source_config(request): - return Source( - source_type="github", - username="JustScreaMy", - token="testToken", - forks=request.param - ) diff --git a/tests/source_type/github_test.py b/tests/source_type/github_test.py index b0ace34..77dfe85 100644 --- a/tests/source_type/github_test.py +++ b/tests/source_type/github_test.py @@ -1,7 +1,5 @@ from many_repos.source_type.github import Source as GithubSource -from unittest import mock -class TestGithubSource: - def test_api_url_generation(self, source_config, monkeypatch): - src = GithubSource(source_config) +def dummy(): + assert 1 == 1 \ No newline at end of file diff --git a/tests/source_type/utils_test.py b/tests/source_type/utils_test.py new file mode 100644 index 0000000..a4140d2 --- /dev/null +++ b/tests/source_type/utils_test.py @@ -0,0 +1,21 @@ +import pytest + +from many_repos import errors +from many_repos import source_type + + +@pytest.mark.parametrize( + "source, source_model", + ( + ("gitlab", source_type.gitlab.Source), + ("gitlab_group", source_type.gitlab_group.Source), + ("github", source_type.github.Source) + ) +) +def test_correct_source_model(source, source_model): + assert source_type.get(source) == source_model + + +def test_invalid_source_model(): + with pytest.raises(errors.InvalidSource): + source_type.get("bitbucket")