httpcheck 1.4.2__tar.gz → 1.5.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.
Files changed (37) hide show
  1. {httpcheck-1.4.2/httpcheck.egg-info → httpcheck-1.5.0}/PKG-INFO +119 -26
  2. httpcheck-1.4.2/PKG-INFO → httpcheck-1.5.0/README.md +105 -65
  3. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/__init__.py +4 -1
  4. httpcheck-1.5.0/httpcheck/async_site_checker.py +238 -0
  5. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/cli.py +103 -45
  6. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/common.py +4 -6
  7. httpcheck-1.5.0/httpcheck/config.py +143 -0
  8. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/file_handler.py +6 -7
  9. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/logger.py +2 -3
  10. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/output_formatter.py +0 -1
  11. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/site_checker.py +70 -38
  12. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/tld_manager.py +48 -25
  13. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/validation.py +12 -12
  14. httpcheck-1.4.2/README.md → httpcheck-1.5.0/httpcheck.egg-info/PKG-INFO +159 -21
  15. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/SOURCES.txt +4 -0
  16. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/requires.txt +11 -2
  17. {httpcheck-1.4.2 → httpcheck-1.5.0}/pyproject.toml +20 -6
  18. httpcheck-1.5.0/tests/test_async_site_checker.py +360 -0
  19. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_cli_integration.py +250 -28
  20. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_common.py +2 -2
  21. httpcheck-1.5.0/tests/test_config.py +323 -0
  22. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_httpcheck.py +1 -1
  23. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_init.py +2 -2
  24. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_performance.py +22 -4
  25. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_site_checker.py +151 -8
  26. httpcheck-1.5.0/tests/test_tld_manager.py +357 -0
  27. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_validation.py +5 -1
  28. httpcheck-1.4.2/tests/test_tld_manager.py +0 -101
  29. {httpcheck-1.4.2 → httpcheck-1.5.0}/LICENSE +0 -0
  30. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/notification.py +0 -0
  31. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/dependency_links.txt +0 -0
  32. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/entry_points.txt +0 -0
  33. {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/top_level.txt +0 -0
  34. {httpcheck-1.4.2 → httpcheck-1.5.0}/setup.cfg +0 -0
  35. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_file_handler.py +0 -0
  36. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_output_formats.py +0 -0
  37. {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_request_customization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpcheck
3
- Version: 1.4.2
3
+ Version: 1.5.0
4
4
  Summary: HTTP status code checker with advanced features
5
5
  Author-email: Thomas Juul Dyhr <thomas@dyhr.com>
6
6
  License: MIT
@@ -13,19 +13,22 @@ Classifier: Intended Audience :: System Administrators
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Topic :: System :: Monitoring
21
22
  Classifier: Topic :: System :: Networking :: Monitoring
22
- Requires-Python: >=3.9
23
+ Requires-Python: >=3.10
23
24
  Description-Content-Type: text/markdown
24
25
  License-File: LICENSE
25
- Requires-Dist: requests>=2.32.5
26
+ Requires-Dist: requests>=2.33.0
27
+ Requires-Dist: httpx>=0.27.0
26
28
  Requires-Dist: tabulate>=0.9.0
27
29
  Requires-Dist: tqdm>=4.67.0
28
30
  Requires-Dist: validators>=0.22.0
31
+ Requires-Dist: tomli>=2.0.0; python_version < "3.11"
29
32
  Provides-Extra: macos
30
33
  Requires-Dist: rumps>=0.4.0; extra == "macos"
31
34
  Requires-Dist: pyobjc-framework-Cocoa>=10.0; extra == "macos"
@@ -37,61 +40,122 @@ Requires-Dist: pytest>=7.0; extra == "dev"
37
40
  Requires-Dist: pytest-mock>=3.0; extra == "dev"
38
41
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
39
42
  Requires-Dist: pytest-benchmark>=5.0; extra == "dev"
43
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
40
44
  Requires-Dist: pylint>=3.1.0; extra == "dev"
41
- Requires-Dist: mypy>=1.0; extra == "dev"
45
+ Requires-Dist: mypy==1.20.2; extra == "dev"
46
+ Requires-Dist: types-requests>=2.31.0; extra == "dev"
47
+ Requires-Dist: types-tabulate>=0.9.0; extra == "dev"
48
+ Requires-Dist: types-tqdm>=4.66.0; extra == "dev"
49
+ Requires-Dist: tomli>=2.0.0; extra == "dev"
42
50
  Provides-Extra: build
43
51
  Requires-Dist: py2app>=0.28; extra == "build"
44
52
  Dynamic: license-file
45
53
 
54
+ <div align="right">
55
+
56
+ <a href="https://railway.com?referralCode=QhjuBc">
57
+
58
+ <img width="160" src="https://raw.githubusercontent.com/docdyhr/.github/main/assets/railway-corner-v2@2x.png" alt="Deploy on Railway — $20 free credits">
59
+
60
+ </a>
61
+
62
+ </div>
63
+
64
+ [![PyPI version](https://badge.fury.io/py/httpcheck.svg)](https://badge.fury.io/py/httpcheck)
65
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/httpcheck)](https://pypi.org/project/httpcheck/)
66
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/httpcheck)](https://pypi.org/project/httpcheck/)
46
67
  [![CI/CD Pipeline](https://github.com/docdyhr/httpcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/docdyhr/httpcheck/actions/workflows/ci.yml)
47
68
  [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
48
- ![Python Version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)
49
69
  [![GitHub issues](https://img.shields.io/github/issues/docdyhr/httpcheck)](https://github.com/docdyhr/httpcheck/issues)
50
70
  ![GitHub repo size](https://img.shields.io/github/repo-size/docdyhr/httpcheck)
51
71
  ![GitHub](https://img.shields.io/github/license/docdyhr/httpcheck)
72
+ [![Docs](https://img.shields.io/badge/docs-readthedocs-brightgreen)](https://httpcheck.readthedocs.io/)
52
73
 
53
74
  # Check Websites HTTP Status Codes with httpcheck
54
75
 
55
76
  <img src="images/onSiteLogo.png" alt="onSite Logo" width="50%">
56
77
 
57
78
  * **Name**: httpcheck (CLI) / onSite (Menu Bar App)
58
- * **Current Version**: 1.4.1 (Security & Architecture Patch)
59
- * **Target Version**: 1.5.0 (Performance & Configuration Features)
60
- * **Programming Language**: Python 3.9+
79
+ * **Current Version**: 1.5.0 (Async I/O & Configuration)
80
+ * **Next Version**: 1.6.0 (Monitoring Mode, target Q1 2027)
81
+ * **Programming Language**: Python 3.10+
61
82
  * **Author**: Thomas Juul Dyhr
62
83
  * **Purpose**: Advanced HTTP status checker with monitoring capabilities
63
84
  * **Development Status**: Active - See [ROADMAP.md](ROADMAP.md) for development plan
85
+ * **PyPI**: https://pypi.org/project/httpcheck/
64
86
 
65
87
  ## 🚀 Release Status
66
88
 
67
- **httpcheck v1.4.1 has been released! 🔒**
68
-
69
- ✅ **NEW in v1.4.1:**
70
- - **Security Patches**: Updated dependencies (requests 2.32.5, urllib3 2.5.0)
71
- - **Improved Entry Point**: Refactored CLI to proper package function
72
- - **Mypy Support**: Added type checking configuration
73
- - **Pytest Fix**: Resolved asyncio deprecation warning
74
-
75
- ✅ **v1.4.0 Foundation:**
89
+ **httpcheck v1.5.0 has been released! 🎉**
90
+
91
+ ✅ **NEW in v1.5.0:**
92
+ - **Configuration Files**: User-defined defaults in `~/.httpcheck.toml` and
93
+ `./.httpcheck.toml` (the project file overrides the user file); CLI flags always
94
+ take precedence
95
+ - **Async I/O** (experimental): `--async` uses an httpx-based concurrent path. In
96
+ the [benchmark against v1.4.3](docs/development/ASYNC_BENCHMARK.md) it is not
97
+ faster than `-f` at the same `--workers`; for speed, raise `-f --workers`
98
+ - **Python 3.10+**: v1.5.0 drops Python 3.9 support (pip on Python 3.9 keeps
99
+ installing 1.4.3); Python 3.13 and 3.14 are tested in CI
100
+ - **Retry fix**: with `--follow-redirects http-only` or `https-only`, retries now
101
+ work (1.4.3 reported the site as failed without retrying)
102
+
103
+ ✅ **v1.4.2-1.4.3 (Published to PyPI):**
104
+ - **Structured Logging**: --debug, --log-file, --log-json flags for production monitoring
105
+ - **Comprehensive Testing**: 297 tests with 88% coverage (CLI: 94%)
106
+ - **Professional Documentation**: 24-page documentation with ReadTheDocs theme
107
+ - **Performance Benchmarks**: 18 automated regression tests
108
+ - **PyPI Published**: Available via `pip install httpcheck`
109
+ - **Automated Publishing**: GitHub Actions with Trusted Publishers (OIDC)
110
+
111
+ ✅ **v1.4.0-1.4.1 Foundation:**
76
112
  - **Modular Architecture**: 8 specialized modules
77
- - **Enhanced Security**: Enterprise-grade input validation
78
- - **Comprehensive Testing**: 88% test coverage with 190 test cases
113
+ - **Enhanced Security**: Enterprise-grade input validation with pip-audit clean
79
114
  - **Multiple Output Formats**: JSON and CSV support
80
115
  - **Advanced Request Control**: Custom headers, SSL options
81
- - **Package Installation**: `pip install -e .`
82
116
 
83
- 🚀 **Next: v1.5.0 Development Focus**
84
- - **Async I/O**: 2-3x performance improvement for large site lists
85
- - **Configuration Files**: User-defined defaults and settings
117
+ 🔭 **Next: v1.6.0 (target Q1 2027)**
86
118
  - **Monitoring Mode**: Continuous site monitoring with notifications
87
119
  - **Enhanced UX**: Colored output and improved progress reporting
88
120
 
89
121
  See detailed plans in:
90
- - [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md) - Executive overview and timeline
91
- - [DEVELOPMENT.md](DEVELOPMENT.md) - Technical implementation guide
122
+ - [DEVELOPMENT_PLAN.md](docs/development/DEVELOPMENT_PLAN.md) - Executive overview and timeline
123
+ - [DEVELOPMENT.md](docs/development/DEVELOPMENT.md) - Technical implementation guide
92
124
  - [TODO.md](TODO.md) - Current prioritized task list
93
125
  - [ROADMAP.md](ROADMAP.md) - Long-term vision through v2.0.0
94
126
 
127
+ ## 📦 Installation
128
+
129
+ ### From PyPI (Recommended)
130
+
131
+ ```bash
132
+ pip install httpcheck
133
+ ```
134
+
135
+ ### From Source
136
+
137
+ ```bash
138
+ git clone https://github.com/docdyhr/httpcheck.git
139
+ cd httpcheck
140
+ pip install -e .
141
+ ```
142
+
143
+ ### With Optional Dependencies
144
+
145
+ ```bash
146
+ # For macOS menu bar app
147
+ pip install httpcheck[macos]
148
+
149
+ # For development
150
+ pip install httpcheck[dev]
151
+ ```
152
+
153
+ ### Upgrade to Latest Version
154
+
155
+ ```bash
156
+ pip install --upgrade httpcheck
157
+ ```
158
+
95
159
  ## usage:
96
160
 
97
161
  ```
@@ -364,6 +428,35 @@ httpcheck --timeout 30 --retries 5 --retry-delay 2.0 slow.example.com
364
428
  httpcheck -H "API-Key: secret" --output json --no-verify-ssl https://api.example.com
365
429
  ```
366
430
 
431
+ ### configuration file
432
+
433
+ httpcheck reads defaults from `~/.httpcheck.toml` (user-wide) and/or `./.httpcheck.toml` (project-local). Project-level settings override user-level settings; any CLI flag always takes final precedence.
434
+
435
+ ```toml
436
+ # ~/.httpcheck.toml
437
+
438
+ [defaults]
439
+ timeout = 10.0
440
+ retries = 3
441
+ follow_redirects = "always" # always | never | http-only | https-only
442
+ output_format = "table" # table | json | csv
443
+ verify_ssl = true
444
+ workers = 20
445
+ retry_delay = 0.5
446
+ max_redirects = 10
447
+
448
+ [headers]
449
+ User-Agent = "mybot/1.0"
450
+ Accept = "application/json"
451
+
452
+ [notifications]
453
+ enabled = true
454
+ on_failure = true
455
+ sound = "Ping"
456
+ ```
457
+
458
+ Unknown keys are silently ignored with a `UserWarning`. Invalid TOML falls back to no config (also a `UserWarning`).
459
+
367
460
  ### file input examples
368
461
 
369
462
  Create a domains file with comments and use it with httpcheck:
@@ -1,97 +1,108 @@
1
- Metadata-Version: 2.4
2
- Name: httpcheck
3
- Version: 1.4.2
4
- Summary: HTTP status code checker with advanced features
5
- Author-email: Thomas Juul Dyhr <thomas@dyhr.com>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/docdyhr/httpcheck
8
- Project-URL: Bug Tracker, https://github.com/docdyhr/httpcheck/issues
9
- Project-URL: Documentation, https://github.com/docdyhr/httpcheck/blob/master/README.md
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Environment :: Console
12
- Classifier: Intended Audience :: System Administrators
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.9
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Classifier: Topic :: System :: Monitoring
21
- Classifier: Topic :: System :: Networking :: Monitoring
22
- Requires-Python: >=3.9
23
- Description-Content-Type: text/markdown
24
- License-File: LICENSE
25
- Requires-Dist: requests>=2.32.5
26
- Requires-Dist: tabulate>=0.9.0
27
- Requires-Dist: tqdm>=4.67.0
28
- Requires-Dist: validators>=0.22.0
29
- Provides-Extra: macos
30
- Requires-Dist: rumps>=0.4.0; extra == "macos"
31
- Requires-Dist: pyobjc-framework-Cocoa>=10.0; extra == "macos"
32
- Requires-Dist: pyobjc-framework-Foundation>=10.0; extra == "macos"
33
- Requires-Dist: pyobjc-framework-AppKit>=10.0; extra == "macos"
34
- Requires-Dist: pyobjc-core>=10.0; extra == "macos"
35
- Provides-Extra: dev
36
- Requires-Dist: pytest>=7.0; extra == "dev"
37
- Requires-Dist: pytest-mock>=3.0; extra == "dev"
38
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
39
- Requires-Dist: pytest-benchmark>=5.0; extra == "dev"
40
- Requires-Dist: pylint>=3.1.0; extra == "dev"
41
- Requires-Dist: mypy>=1.0; extra == "dev"
42
- Provides-Extra: build
43
- Requires-Dist: py2app>=0.28; extra == "build"
44
- Dynamic: license-file
1
+ <div align="right">
45
2
 
3
+ <a href="https://railway.com?referralCode=QhjuBc">
4
+
5
+ <img width="160" src="https://raw.githubusercontent.com/docdyhr/.github/main/assets/railway-corner-v2@2x.png" alt="Deploy on Railway — $20 free credits">
6
+
7
+ </a>
8
+
9
+ </div>
10
+
11
+ [![PyPI version](https://badge.fury.io/py/httpcheck.svg)](https://badge.fury.io/py/httpcheck)
12
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/httpcheck)](https://pypi.org/project/httpcheck/)
13
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/httpcheck)](https://pypi.org/project/httpcheck/)
46
14
  [![CI/CD Pipeline](https://github.com/docdyhr/httpcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/docdyhr/httpcheck/actions/workflows/ci.yml)
47
15
  [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
48
- ![Python Version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)
49
16
  [![GitHub issues](https://img.shields.io/github/issues/docdyhr/httpcheck)](https://github.com/docdyhr/httpcheck/issues)
50
17
  ![GitHub repo size](https://img.shields.io/github/repo-size/docdyhr/httpcheck)
51
18
  ![GitHub](https://img.shields.io/github/license/docdyhr/httpcheck)
19
+ [![Docs](https://img.shields.io/badge/docs-readthedocs-brightgreen)](https://httpcheck.readthedocs.io/)
52
20
 
53
21
  # Check Websites HTTP Status Codes with httpcheck
54
22
 
55
23
  <img src="images/onSiteLogo.png" alt="onSite Logo" width="50%">
56
24
 
57
25
  * **Name**: httpcheck (CLI) / onSite (Menu Bar App)
58
- * **Current Version**: 1.4.1 (Security & Architecture Patch)
59
- * **Target Version**: 1.5.0 (Performance & Configuration Features)
60
- * **Programming Language**: Python 3.9+
26
+ * **Current Version**: 1.5.0 (Async I/O & Configuration)
27
+ * **Next Version**: 1.6.0 (Monitoring Mode, target Q1 2027)
28
+ * **Programming Language**: Python 3.10+
61
29
  * **Author**: Thomas Juul Dyhr
62
30
  * **Purpose**: Advanced HTTP status checker with monitoring capabilities
63
31
  * **Development Status**: Active - See [ROADMAP.md](ROADMAP.md) for development plan
32
+ * **PyPI**: https://pypi.org/project/httpcheck/
64
33
 
65
34
  ## 🚀 Release Status
66
35
 
67
- **httpcheck v1.4.1 has been released! 🔒**
68
-
69
- ✅ **NEW in v1.4.1:**
70
- - **Security Patches**: Updated dependencies (requests 2.32.5, urllib3 2.5.0)
71
- - **Improved Entry Point**: Refactored CLI to proper package function
72
- - **Mypy Support**: Added type checking configuration
73
- - **Pytest Fix**: Resolved asyncio deprecation warning
74
-
75
- ✅ **v1.4.0 Foundation:**
36
+ **httpcheck v1.5.0 has been released! 🎉**
37
+
38
+ ✅ **NEW in v1.5.0:**
39
+ - **Configuration Files**: User-defined defaults in `~/.httpcheck.toml` and
40
+ `./.httpcheck.toml` (the project file overrides the user file); CLI flags always
41
+ take precedence
42
+ - **Async I/O** (experimental): `--async` uses an httpx-based concurrent path. In
43
+ the [benchmark against v1.4.3](docs/development/ASYNC_BENCHMARK.md) it is not
44
+ faster than `-f` at the same `--workers`; for speed, raise `-f --workers`
45
+ - **Python 3.10+**: v1.5.0 drops Python 3.9 support (pip on Python 3.9 keeps
46
+ installing 1.4.3); Python 3.13 and 3.14 are tested in CI
47
+ - **Retry fix**: with `--follow-redirects http-only` or `https-only`, retries now
48
+ work (1.4.3 reported the site as failed without retrying)
49
+
50
+ ✅ **v1.4.2-1.4.3 (Published to PyPI):**
51
+ - **Structured Logging**: --debug, --log-file, --log-json flags for production monitoring
52
+ - **Comprehensive Testing**: 297 tests with 88% coverage (CLI: 94%)
53
+ - **Professional Documentation**: 24-page documentation with ReadTheDocs theme
54
+ - **Performance Benchmarks**: 18 automated regression tests
55
+ - **PyPI Published**: Available via `pip install httpcheck`
56
+ - **Automated Publishing**: GitHub Actions with Trusted Publishers (OIDC)
57
+
58
+ ✅ **v1.4.0-1.4.1 Foundation:**
76
59
  - **Modular Architecture**: 8 specialized modules
77
- - **Enhanced Security**: Enterprise-grade input validation
78
- - **Comprehensive Testing**: 88% test coverage with 190 test cases
60
+ - **Enhanced Security**: Enterprise-grade input validation with pip-audit clean
79
61
  - **Multiple Output Formats**: JSON and CSV support
80
62
  - **Advanced Request Control**: Custom headers, SSL options
81
- - **Package Installation**: `pip install -e .`
82
63
 
83
- 🚀 **Next: v1.5.0 Development Focus**
84
- - **Async I/O**: 2-3x performance improvement for large site lists
85
- - **Configuration Files**: User-defined defaults and settings
64
+ 🔭 **Next: v1.6.0 (target Q1 2027)**
86
65
  - **Monitoring Mode**: Continuous site monitoring with notifications
87
66
  - **Enhanced UX**: Colored output and improved progress reporting
88
67
 
89
68
  See detailed plans in:
90
- - [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md) - Executive overview and timeline
91
- - [DEVELOPMENT.md](DEVELOPMENT.md) - Technical implementation guide
69
+ - [DEVELOPMENT_PLAN.md](docs/development/DEVELOPMENT_PLAN.md) - Executive overview and timeline
70
+ - [DEVELOPMENT.md](docs/development/DEVELOPMENT.md) - Technical implementation guide
92
71
  - [TODO.md](TODO.md) - Current prioritized task list
93
72
  - [ROADMAP.md](ROADMAP.md) - Long-term vision through v2.0.0
94
73
 
74
+ ## 📦 Installation
75
+
76
+ ### From PyPI (Recommended)
77
+
78
+ ```bash
79
+ pip install httpcheck
80
+ ```
81
+
82
+ ### From Source
83
+
84
+ ```bash
85
+ git clone https://github.com/docdyhr/httpcheck.git
86
+ cd httpcheck
87
+ pip install -e .
88
+ ```
89
+
90
+ ### With Optional Dependencies
91
+
92
+ ```bash
93
+ # For macOS menu bar app
94
+ pip install httpcheck[macos]
95
+
96
+ # For development
97
+ pip install httpcheck[dev]
98
+ ```
99
+
100
+ ### Upgrade to Latest Version
101
+
102
+ ```bash
103
+ pip install --upgrade httpcheck
104
+ ```
105
+
95
106
  ## usage:
96
107
 
97
108
  ```
@@ -364,6 +375,35 @@ httpcheck --timeout 30 --retries 5 --retry-delay 2.0 slow.example.com
364
375
  httpcheck -H "API-Key: secret" --output json --no-verify-ssl https://api.example.com
365
376
  ```
366
377
 
378
+ ### configuration file
379
+
380
+ httpcheck reads defaults from `~/.httpcheck.toml` (user-wide) and/or `./.httpcheck.toml` (project-local). Project-level settings override user-level settings; any CLI flag always takes final precedence.
381
+
382
+ ```toml
383
+ # ~/.httpcheck.toml
384
+
385
+ [defaults]
386
+ timeout = 10.0
387
+ retries = 3
388
+ follow_redirects = "always" # always | never | http-only | https-only
389
+ output_format = "table" # table | json | csv
390
+ verify_ssl = true
391
+ workers = 20
392
+ retry_delay = 0.5
393
+ max_redirects = 10
394
+
395
+ [headers]
396
+ User-Agent = "mybot/1.0"
397
+ Accept = "application/json"
398
+
399
+ [notifications]
400
+ enabled = true
401
+ on_failure = true
402
+ sound = "Ping"
403
+ ```
404
+
405
+ Unknown keys are silently ignored with a `UserWarning`. Invalid TOML falls back to no config (also a `UserWarning`).
406
+
367
407
  ### file input examples
368
408
 
369
409
  Create a domains file with comments and use it with httpcheck:
@@ -5,13 +5,14 @@ A Python CLI tool for checking HTTP status codes of websites with
5
5
  threading, redirect handling, TLD validation, and system notifications.
6
6
  """
7
7
 
8
- __version__ = "1.4.2"
8
+ __version__ = "1.5.0"
9
9
  __author__ = "Thomas Juul Dyhr"
10
10
  __email__ = "thomas@dyhr.com"
11
11
 
12
12
  # Import specific items for public API
13
13
  from .cli import main
14
14
  from .common import VERSION, InvalidTLDException, SiteStatus
15
+ from .config import DEFAULT_CONFIG, load_config
15
16
  from .file_handler import FileInputHandler, url_validation
16
17
  from .notification import notify
17
18
  from .output_formatter import (
@@ -37,6 +38,8 @@ from .validation import (
37
38
  __all__ = [
38
39
  # Core functionality
39
40
  "check_site",
41
+ "load_config",
42
+ "DEFAULT_CONFIG",
40
43
  "print_format",
41
44
  "format_json",
42
45
  "format_csv",
@@ -0,0 +1,238 @@
1
+ """Async HTTP site checking for httpcheck."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import time
7
+ from collections.abc import Iterable
8
+ from urllib.parse import urlparse
9
+
10
+ import httpx
11
+
12
+ from .common import STATUS_CODES, VERSION, SiteStatus
13
+
14
+ DEFAULT_LIMITS = httpx.Limits(max_connections=50, max_keepalive_connections=20)
15
+
16
+
17
+ def _create_headers(custom_headers: dict | None = None) -> dict:
18
+ """Create default headers including User-Agent."""
19
+ headers = {"User-Agent": f"httpcheck Async Agent {VERSION}"}
20
+ if custom_headers:
21
+ headers.update(custom_headers)
22
+ return headers
23
+
24
+
25
+ def _extract_domain(url_like: str | httpx.URL, fallback: str) -> str:
26
+ """Return host component from httpx.URL or string URL."""
27
+ try:
28
+ if isinstance(url_like, httpx.URL):
29
+ return str(url_like.host)
30
+ parsed = urlparse(str(url_like))
31
+ return parsed.hostname or fallback
32
+ except (AttributeError, ValueError):
33
+ return fallback
34
+
35
+
36
+ def _extract_redirects(
37
+ response: httpx.Response,
38
+ ) -> tuple[list[tuple[str, int]], list[tuple[str, int, float]]]:
39
+ """Return redirect chain and timing from an httpx response."""
40
+ chain: list[tuple[str, int]] = []
41
+ timing: list[tuple[str, int, float]] = []
42
+ for hop in response.history:
43
+ chain.append((str(hop.url), hop.status_code))
44
+ timing.append((str(hop.url), hop.status_code, 0.0))
45
+ chain.append((str(response.url), response.status_code))
46
+ timing.append((str(response.url), response.status_code, 0.0))
47
+ return chain, timing
48
+
49
+
50
+ def _should_stop_redirect(follow_redirects: str, redirect_url: httpx.URL) -> bool:
51
+ """Return True when protocol policy blocks following this redirect."""
52
+ if follow_redirects == "http-only" and redirect_url.scheme == "https":
53
+ return True
54
+ if follow_redirects == "https-only" and redirect_url.scheme == "http":
55
+ return True
56
+ return False
57
+
58
+
59
+ async def _manual_redirect_flow(
60
+ client: httpx.AsyncClient,
61
+ url: str,
62
+ *,
63
+ timeout: float,
64
+ max_redirects: int,
65
+ follow_redirects: str,
66
+ ) -> tuple[httpx.Response, float, list[tuple[str, int]], list[tuple[str, int, float]]]:
67
+ """Follow redirects manually to enforce protocol restrictions."""
68
+ chain: list[tuple[str, int]] = []
69
+ timing: list[tuple[str, int, float]] = []
70
+
71
+ current_url: httpx.URL = httpx.URL(url)
72
+ total_start = time.monotonic()
73
+ redirects = 0
74
+ while True:
75
+ hop_start = time.monotonic()
76
+ response = await client.get(
77
+ str(current_url),
78
+ follow_redirects=False,
79
+ timeout=timeout,
80
+ )
81
+ hop_elapsed = time.monotonic() - hop_start
82
+
83
+ chain.append((str(response.url), response.status_code))
84
+ timing.append((str(response.url), response.status_code, hop_elapsed))
85
+
86
+ if not response.is_redirect or "location" not in response.headers:
87
+ break
88
+
89
+ if redirects >= max_redirects:
90
+ break
91
+
92
+ location = response.headers.get("location", "")
93
+ base_url = (
94
+ response.url
95
+ if isinstance(response.url, httpx.URL)
96
+ else httpx.URL(str(response.url))
97
+ )
98
+ next_url = base_url.join(location)
99
+ if _should_stop_redirect(follow_redirects, next_url):
100
+ break
101
+
102
+ redirects += 1
103
+ current_url = next_url
104
+
105
+ total_elapsed = time.monotonic() - total_start
106
+ return response, total_elapsed, chain, timing
107
+
108
+
109
+ async def async_check_site(
110
+ site: str,
111
+ timeout: float = 5.0,
112
+ retries: int = 2,
113
+ *,
114
+ follow_redirects: str = "always",
115
+ max_redirects: int = 30,
116
+ custom_headers: dict | None = None,
117
+ verify_ssl: bool = True,
118
+ retry_delay: float = 1.0,
119
+ client: httpx.AsyncClient | None = None,
120
+ ) -> SiteStatus:
121
+ """Async version of check_site with optional protocol-restricted redirects."""
122
+ headers = _create_headers(custom_headers)
123
+ allow_redirects = follow_redirects == "always"
124
+ manual_redirects = follow_redirects in {"http-only", "https-only"}
125
+
126
+ async def _execute_request(
127
+ active_client: httpx.AsyncClient,
128
+ ) -> tuple[httpx.Response, float]:
129
+ start = time.monotonic()
130
+ response = await active_client.get(
131
+ site,
132
+ headers=headers,
133
+ timeout=timeout,
134
+ follow_redirects=allow_redirects,
135
+ )
136
+ elapsed = time.monotonic() - start
137
+ return response, elapsed
138
+
139
+ # httpx sets max_redirects per client, not per request: a caller-supplied
140
+ # client keeps its own limit.
141
+ owns_client = client is None
142
+ active_client = client or httpx.AsyncClient(
143
+ verify=verify_ssl,
144
+ limits=DEFAULT_LIMITS,
145
+ headers=headers,
146
+ max_redirects=max_redirects,
147
+ )
148
+
149
+ try:
150
+ for attempt in range(retries + 1):
151
+ try:
152
+ if manual_redirects:
153
+ (
154
+ response,
155
+ response_time,
156
+ redirect_chain,
157
+ redirect_timing,
158
+ ) = await _manual_redirect_flow(
159
+ active_client,
160
+ site,
161
+ timeout=timeout,
162
+ max_redirects=max_redirects,
163
+ follow_redirects=follow_redirects,
164
+ )
165
+ else:
166
+ response, response_time = await _execute_request(active_client)
167
+ redirect_chain, redirect_timing = _extract_redirects(response)
168
+
169
+ domain = _extract_domain(getattr(response, "url", site), site)
170
+ return SiteStatus(
171
+ domain=domain,
172
+ status=str(response.status_code),
173
+ message=STATUS_CODES.get(str(response.status_code), "Unknown"),
174
+ redirect_chain=redirect_chain,
175
+ response_time=response_time,
176
+ redirect_timing=redirect_timing,
177
+ )
178
+ except (httpx.RequestError, httpx.HTTPError, ValueError, OSError) as exc:
179
+ if attempt == retries:
180
+ status = (
181
+ "[timeout]"
182
+ if isinstance(exc, httpx.TimeoutException)
183
+ else "[connection error]"
184
+ )
185
+ domain = site
186
+ if isinstance(exc, httpx.HTTPError):
187
+ try:
188
+ # .request raises RuntimeError when it was never set.
189
+ domain = _extract_domain(exc.request.url, site)
190
+ except RuntimeError:
191
+ pass
192
+ return SiteStatus(domain, status, str(exc))
193
+ if retry_delay > 0:
194
+ await asyncio.sleep(retry_delay)
195
+ # Only reached when retries < 0, as in the sync check_site.
196
+ return SiteStatus(site, "[unknown error]", "All retries failed")
197
+ finally:
198
+ if owns_client:
199
+ await active_client.aclose()
200
+
201
+
202
+ async def async_check_sites(
203
+ sites: Iterable[str],
204
+ *,
205
+ timeout: float = 5.0,
206
+ retries: int = 2,
207
+ follow_redirects: str = "always",
208
+ max_redirects: int = 30,
209
+ custom_headers: dict | None = None,
210
+ verify_ssl: bool = True,
211
+ retry_delay: float = 1.0,
212
+ concurrency: int = 50,
213
+ ) -> list[SiteStatus]:
214
+ """Check many sites concurrently with a shared client and semaphore."""
215
+ sem = asyncio.Semaphore(concurrency)
216
+ headers = _create_headers(custom_headers)
217
+ async with httpx.AsyncClient(
218
+ verify=verify_ssl,
219
+ limits=DEFAULT_LIMITS,
220
+ headers=headers,
221
+ max_redirects=max_redirects,
222
+ ) as client:
223
+
224
+ async def worker(url: str) -> SiteStatus:
225
+ async with sem:
226
+ return await async_check_site(
227
+ url,
228
+ timeout=timeout,
229
+ retries=retries,
230
+ follow_redirects=follow_redirects,
231
+ max_redirects=max_redirects,
232
+ custom_headers=None, # headers already applied on client
233
+ verify_ssl=verify_ssl,
234
+ retry_delay=retry_delay,
235
+ client=client,
236
+ )
237
+
238
+ return await asyncio.gather(*[worker(u) for u in sites])