http-checker 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: http-checker
3
+ Version: 1.4.0
4
+ Summary: A simple CLI tool to check the status of URLs.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: requests<3.0,>=2.28
9
+ Requires-Dist: click<9.0,>=8.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest; extra == "dev"
12
+ Requires-Dist: pytest-mock; extra == "dev"
13
+ Requires-Dist: ruff; extra == "dev"
14
+ Requires-Dist: black; extra == "dev"
15
+ Requires-Dist: mypy; extra == "dev"
16
+ Requires-Dist: bandit; extra == "dev"
17
+ Requires-Dist: types-requests; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ ## Implementaion
21
+
22
+ [x] Project files (code files)
23
+ [x] Add simple GHA workflow and make sure it runs completion
24
+ [x] Add linting (ruff) and format checks (black)
25
+ [] Add typing and security checks
26
+ [] Add test automation
27
+ [] Build our Python project
28
+ [] Publish the project to bet TestPyPi and PyPi when a new tag is pushed
@@ -0,0 +1,9 @@
1
+ ## Implementaion
2
+
3
+ [x] Project files (code files)
4
+ [x] Add simple GHA workflow and make sure it runs completion
5
+ [x] Add linting (ruff) and format checks (black)
6
+ [] Add typing and security checks
7
+ [] Add test automation
8
+ [] Build our Python project
9
+ [] Publish the project to bet TestPyPi and PyPi when a new tag is pushed
@@ -0,0 +1,77 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "http-checker"
7
+ version = "1.4.0"
8
+ description = "A simple CLI tool to check the status of URLs."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "requests>=2.28,<3.0",
13
+ "click>=8.0,<9.0"
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = [
18
+ "pytest",
19
+ "pytest-mock",
20
+ "ruff",
21
+ "black",
22
+ "mypy",
23
+ "bandit",
24
+ "types-requests"
25
+ ]
26
+
27
+ [project.scripts]
28
+ check-urls = "http_checker.cli:main"
29
+
30
+ [tool.ruff]
31
+ line-length = 65
32
+
33
+ [tool.black]
34
+ line-length = 65
35
+
36
+ [tool.mypy]
37
+ warn_return_any = true
38
+ warn_unused_configs = true
39
+ ignore_missing_imports = true
40
+
41
+ [tool.bandit]
42
+ exclude_dirs = [".venv", "__pycache__", "build", "dist"]
43
+ skips = ["B101"]
44
+
45
+ [tool.pytest.ini_options]
46
+ addopts = "-rA -s -v"
47
+
48
+ [tool.semantic_release]
49
+ version_toml = ["pyproject.toml:project.version"]
50
+ build_command = "pip install build && python -m build"
51
+ dist_path = "dist/"
52
+ upload_to_pypi = false
53
+ upload_to_release = true
54
+ commit_message = "chore(release): {version} [skip ci]"
55
+
56
+ [tool.semanctic_release.branches.master]
57
+ match = "master"
58
+ prerelease = false
59
+
60
+ [tool.semantic_release.changelog.default_templates]
61
+ changelog_file = "CHANGELOG.md"
62
+
63
+ [tools.semanctic_release.commit_parser_options]
64
+ allowed_tags = [
65
+ "build",
66
+ "chore",
67
+ "ci",
68
+ "docs",
69
+ "feat",
70
+ "fix",
71
+ "perf",
72
+ "style",
73
+ "reafactor",
74
+ "test"
75
+ ]
76
+ minor_tags = ["feat"]
77
+ patch_tags = ["fix", "perf"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,57 @@
1
+ import logging
2
+ import requests
3
+ from typing import Collection
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ def check_urls(
9
+ urls: Collection[str], timeout: int = 5
10
+ ) -> dict[str, str]:
11
+ """
12
+ Checks a list of URLs and returns their status.
13
+
14
+ Args:
15
+ urls: A list of URL strings to check.
16
+ timeout: Maximum time in seconds to wait for each request. Defaults to 5.
17
+
18
+ Returns:
19
+ A dictionary mapping each URL to its status string.
20
+ """
21
+
22
+ logger.info(
23
+ f"Starting check for {len(urls)} URLs with a timeout of {timeout}"
24
+ )
25
+ results: dict[str, str] = {}
26
+
27
+ for url in urls:
28
+ status = "UNKNOWN"
29
+
30
+ try:
31
+ logger.debug(f"Checking URL: {url}")
32
+ response = requests.get(url, timeout=timeout)
33
+
34
+ if response.ok:
35
+ status = f"{response.status_code} OK"
36
+ else:
37
+ status = (
38
+ f"{response.status_code} {response.reason}"
39
+ )
40
+ except requests.exceptions.Timeout:
41
+ status = "TIMEOUT"
42
+ logger.warning(f"Request to {url} timed out.")
43
+ except requests.exceptions.ConnectionError:
44
+ status = "CONNECTION_ERROR"
45
+ logger.warning(f"Connection error for {url}.")
46
+ except requests.exceptions.RequestException as e:
47
+ status = f"REQUEST_ERROR: {type(e).__name__}"
48
+ logger.error(
49
+ f"An unexpected request error occured for {url}: {e}",
50
+ exc_info=True,
51
+ )
52
+
53
+ results[url] = status
54
+ logger.debug(f"Checked: {url:<40} -> {status}")
55
+
56
+ logger.info("URL check finished.")
57
+ return results
@@ -0,0 +1,54 @@
1
+ import logging
2
+ import click
3
+ from .checker import check_urls
4
+ from typing import Collection
5
+
6
+ logging.basicConfig(
7
+ level=logging.INFO,
8
+ format="[%(asctime)s] %(levelname)-8s %(name)s: %(message)s",
9
+ datefmt="%Y-%m-%d %H:%M:%S",
10
+ )
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @click.command()
16
+ @click.argument("urls", nargs=-1)
17
+ @click.option(
18
+ "--timeout",
19
+ default=5,
20
+ help="Timeout for each URL check in seconds.",
21
+ )
22
+ @click.option(
23
+ "--verbose",
24
+ "-v",
25
+ is_flag=True,
26
+ help="Enable verbose logging.",
27
+ )
28
+ def main(urls: Collection[str], timeout: int, verbose: bool):
29
+
30
+ if verbose:
31
+ logging.getLogger().setLevel(logging.DEBUG)
32
+ logger.debug("Verbose logging enabled.")
33
+
34
+ logger.debug(f"Received urls: {urls}")
35
+ logger.debug(f"Received timeout: {timeout}")
36
+ logger.debug(f"Received verbose flag: {verbose}")
37
+
38
+ if not urls:
39
+ logger.warning(
40
+ "No URLs provided. Please provide at least one URL to check."
41
+ )
42
+ click.echo("Usage: check-urls <URL1> <URL2> ...")
43
+ return
44
+
45
+ logger.info(f"Starting check for {len(urls)} URLs.")
46
+ results = check_urls(urls, timeout)
47
+
48
+ click.echo("\n --- Results ----")
49
+ for url, status in results.items():
50
+ if "OK" in status:
51
+ fg_color = "green"
52
+ else:
53
+ fg_color = "red"
54
+ click.secho(f"{url:<40} -> {status}", fg=fg_color)
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: http-checker
3
+ Version: 1.4.0
4
+ Summary: A simple CLI tool to check the status of URLs.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: requests<3.0,>=2.28
9
+ Requires-Dist: click<9.0,>=8.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest; extra == "dev"
12
+ Requires-Dist: pytest-mock; extra == "dev"
13
+ Requires-Dist: ruff; extra == "dev"
14
+ Requires-Dist: black; extra == "dev"
15
+ Requires-Dist: mypy; extra == "dev"
16
+ Requires-Dist: bandit; extra == "dev"
17
+ Requires-Dist: types-requests; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ ## Implementaion
21
+
22
+ [x] Project files (code files)
23
+ [x] Add simple GHA workflow and make sure it runs completion
24
+ [x] Add linting (ruff) and format checks (black)
25
+ [] Add typing and security checks
26
+ [] Add test automation
27
+ [] Build our Python project
28
+ [] Publish the project to bet TestPyPi and PyPi when a new tag is pushed
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/http_checker/__init__.py
5
+ src/http_checker/checker.py
6
+ src/http_checker/cli.py
7
+ src/http_checker.egg-info/PKG-INFO
8
+ src/http_checker.egg-info/SOURCES.txt
9
+ src/http_checker.egg-info/dependency_links.txt
10
+ src/http_checker.egg-info/entry_points.txt
11
+ src/http_checker.egg-info/requires.txt
12
+ src/http_checker.egg-info/top_level.txt
13
+ tests/test_checker.py
14
+ tests/test_cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ check-urls = http_checker.cli:main
@@ -0,0 +1,11 @@
1
+ requests<3.0,>=2.28
2
+ click<9.0,>=8.0
3
+
4
+ [dev]
5
+ pytest
6
+ pytest-mock
7
+ ruff
8
+ black
9
+ mypy
10
+ bandit
11
+ types-requests
@@ -0,0 +1 @@
1
+ http_checker
@@ -0,0 +1,144 @@
1
+ import pytest
2
+ import requests
3
+ from pytest_mock import MockerFixture
4
+
5
+ from http_checker.checker import check_urls
6
+
7
+
8
+ def test_check_urls_success(mocker):
9
+ mock_requests_get = mocker.patch(
10
+ "http_checker.checker.requests.get"
11
+ )
12
+
13
+ mock_response = mocker.MagicMock(spec=requests.Response)
14
+ mock_response.status_code = 200
15
+ mock_response.reason = "OK"
16
+ mock_response.ok = True
17
+ mock_requests_get.return_value = mock_response
18
+
19
+ urls = ["https://www.example.com"]
20
+ results = check_urls(urls, timeout=5)
21
+
22
+ mock_requests_get.asser_called_once_with(urls[0], timeout=5)
23
+ assert results[urls[0]] == "200 OK"
24
+
25
+
26
+ def test_check_urls_client_error(mocker: MockerFixture):
27
+ mock_requests_get = mocker.patch(
28
+ "http_checker.checker.requests.get"
29
+ )
30
+
31
+ mock_response = mocker.MagicMock(spec=requests.Response)
32
+ mock_response.status_code = 404
33
+ mock_response.reason = "Not Found"
34
+ mock_response.ok = False
35
+ mock_requests_get.return_value = mock_response
36
+
37
+ urls = ["https://www.example.com"]
38
+ results = check_urls(urls, timeout=5)
39
+
40
+ mock_requests_get.assert_called_once_with(urls[0], timeout=5)
41
+ assert results[urls[0]] == "404 Not Found"
42
+
43
+
44
+ @pytest.mark.parametrize(
45
+ "error_exception, expected_status",
46
+ [
47
+ (requests.exceptions.Timeout, "TIMEOUT"),
48
+ (
49
+ requests.exceptions.ConnectionError,
50
+ "CONNECTION_ERROR",
51
+ ),
52
+ (
53
+ requests.exceptions.RequestException,
54
+ "REQUEST_ERROR: RequestException",
55
+ ),
56
+ ],
57
+ )
58
+ def test_check_urls_exceptions(
59
+ mocker: MockerFixture,
60
+ error_exception: type[requests.exceptions.RequestException],
61
+ expected_status: str,
62
+ ):
63
+ mock_requests_get = mocker.patch(
64
+ "http_checker.checker.requests.get"
65
+ )
66
+
67
+ mock_requests_get.side_effect = error_exception(
68
+ f"Simulated {expected_status}"
69
+ )
70
+
71
+ urls = ["https://www.problem.com"]
72
+ results = check_urls(urls, timeout=5)
73
+
74
+ mock_requests_get.asser_called_once_with(urls[0], timeout=5)
75
+ assert results[urls[0]] == expected_status
76
+
77
+
78
+ def test_check_urls_with_multiple_urls(mocker: MockerFixture):
79
+
80
+ mock_requests_get = mocker.patch(
81
+ "http_checker.checker.requests.get"
82
+ )
83
+
84
+ # First call
85
+ mock_response_ok = mocker.MagicMock(spec=requests.Response)
86
+ mock_response_ok.status_code = 200
87
+ mock_response_ok.reason = "OK"
88
+ mock_response_ok.ok = True
89
+
90
+ # Second call
91
+ timeout_exception = requests.exceptions.Timeout(
92
+ "Simulated timeout"
93
+ )
94
+
95
+ # Third call
96
+ mock_response_fail = mocker.MagicMock(spec=requests.Response)
97
+ mock_response_fail.status_code = 500
98
+ mock_response_fail.reason = "Server Error"
99
+ mock_response_fail.ok = False
100
+
101
+ mock_requests_get.side_effect = [
102
+ mock_response_ok,
103
+ timeout_exception,
104
+ mock_response_fail,
105
+ ]
106
+
107
+ urls = [
108
+ "https://www.success.com",
109
+ "https://timeout.com",
110
+ "https://servererror.com",
111
+ ]
112
+ results = check_urls(urls, timeout=5)
113
+
114
+ assert len(results) == 3
115
+ assert mock_requests_get.call_count == 3
116
+ assert results["https://www.success.com"] == "200 OK"
117
+ assert results["https://timeout.com"] == "TIMEOUT"
118
+ assert (
119
+ results["https://servererror.com"] == "500 Server Error"
120
+ )
121
+
122
+
123
+ def test_check_urls_empty():
124
+
125
+ result = check_urls([])
126
+ assert result == {}
127
+
128
+
129
+ def test_check_urls_custom_timeout(mocker):
130
+ mock_requests_get = mocker.patch(
131
+ "http_checker.checker.requests.get"
132
+ )
133
+
134
+ mock_response = mocker.MagicMock(spec=requests.Response)
135
+ mock_response.status_code = 200
136
+ mock_response.reason = "OK"
137
+ mock_response.ok = True
138
+ mock_requests_get.return_value = mock_response
139
+
140
+ urls = ["https://www.example.com"]
141
+ results = check_urls(urls, timeout=10)
142
+
143
+ mock_requests_get.asser_called_once_with(urls[0], timeout=10)
144
+ assert results[urls[0]] == "200 OK"
@@ -0,0 +1,28 @@
1
+ from click.testing import CliRunner
2
+ from pytest_mock import MockerFixture
3
+
4
+ from http_checker.cli import main
5
+
6
+
7
+ def test_no_urls():
8
+ runner = CliRunner()
9
+ result = runner.invoke(main, [])
10
+
11
+ assert result.exit_code == 0
12
+ assert "Usage: check-urls" in result.output
13
+
14
+
15
+ def test_main_single_url_success(mocker: MockerFixture):
16
+ url = "https://www.example.com"
17
+ mock_check = mocker.patch("http_checker.cli.check_urls")
18
+ mock_check.return_value = {url: "200 OK"}
19
+
20
+ runner = CliRunner()
21
+ result = runner.invoke(main, [url])
22
+
23
+ assert result.exit_code == 0
24
+ mock_check.assert_called_once_with((url,), 5)
25
+
26
+ assert "--- Results ---" in result.output
27
+ assert url in result.output
28
+ assert "--- Results ---" in result.output