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.
- http_checker-1.4.0/LICENSE +0 -0
- http_checker-1.4.0/PKG-INFO +28 -0
- http_checker-1.4.0/README.md +9 -0
- http_checker-1.4.0/pyproject.toml +77 -0
- http_checker-1.4.0/setup.cfg +4 -0
- http_checker-1.4.0/src/http_checker/__init__.py +0 -0
- http_checker-1.4.0/src/http_checker/checker.py +57 -0
- http_checker-1.4.0/src/http_checker/cli.py +54 -0
- http_checker-1.4.0/src/http_checker.egg-info/PKG-INFO +28 -0
- http_checker-1.4.0/src/http_checker.egg-info/SOURCES.txt +14 -0
- http_checker-1.4.0/src/http_checker.egg-info/dependency_links.txt +1 -0
- http_checker-1.4.0/src/http_checker.egg-info/entry_points.txt +2 -0
- http_checker-1.4.0/src/http_checker.egg-info/requires.txt +11 -0
- http_checker-1.4.0/src/http_checker.egg-info/top_level.txt +1 -0
- http_checker-1.4.0/tests/test_checker.py +144 -0
- http_checker-1.4.0/tests/test_cli.py +28 -0
|
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"]
|
|
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 @@
|
|
|
1
|
+
|
|
@@ -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
|