Skip to content

Commit 40dd0cc

Browse files
authored
Allow using cached results
2 parents aba0a7d + 9f70806 commit 40dd0cc

File tree

4 files changed

+48
-13
lines changed

4 files changed

+48
-13
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
## [v1.1.0] - 2022/02/16
7+
## [v1.2.0] - 2023/03/06
8+
9+
### Added
10+
11+
- Allow using cached results
12+
13+
## [v1.1.0] - 2023/02/16
814

915
### Added
1016

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,19 @@ async def analyze():
5959
asyncio.run(analyze())
6060
```
6161

62-
This will give you a [Host object](https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v3.md#host) as dataclass. This call runs quite long as it takes time to run all tests. You probably know that from using the [webinterface](https://www.ssllabs.com/ssltest).
62+
This will give you a [Host object](https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v3.md#host) as dataclass. This call runs quite long as it takes time to run all tests. You probably know that from using the [webinterface](https://www.ssllabs.com/ssltest). If you don't need a fresh result on every run, you can allow using ssllabs' cache. This will speed up the tests, if there are cached results. The maximum cache validity can be set in full hour steps.
63+
64+
```python
65+
import asyncio
66+
67+
from ssllabs import Ssllabs
68+
69+
async def analyze():
70+
ssllabs = Ssllabs()
71+
return await ssllabs.analyze(host="devolo.de", from_cache=True, max_age=1)
72+
73+
asyncio.run(analyze())
74+
```
6375

6476
### Check availability of the SSL Labs servers
6577

@@ -75,7 +87,7 @@ async def availability():
7587
asyncio.run(availability())
7688
```
7789

78-
This will give you True, if the servers are up and running, otherwise False. It will also report False, if you exeeded your rate limits.
90+
This will give you True, if the servers are up and running, otherwise False. It will also report False, if you exceeded your rate limits.
7991

8092
### Retrieve API information
8193

@@ -107,7 +119,7 @@ async def root_certs():
107119
asyncio.run(root_certs())
108120
```
109121

110-
This will give you a string containing the latest root certificates used for trust validation. By default it used the certificates provided by Mozilla. You can choose a differenty store by changing trust_store to 1: Mozilla, 2: Apple MacOS, 3: Android, 4: Java or 5: Windows.
122+
This will give you a string containing the latest root certificates used for trust validation. By default it used the certificates provided by Mozilla. You can choose a differently store by changing trust_store to 1: Mozilla, 2: Apple MacOS, 3: Android, 4: Java or 5: Windows.
111123

112124
### Retrieve known status codes
113125

ssllabs/ssllabs.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from typing import Optional
44

5-
from httpx import AsyncClient, ConnectTimeout, HTTPStatusError, ReadTimeout
5+
from httpx import AsyncClient, ConnectTimeout, HTTPStatusError, ReadError, ReadTimeout
66

77
from .api import Analyze, Info, RootCertsRaw, StatusCodes
88
from .data.host import HostData
@@ -29,17 +29,26 @@ async def availability(self) -> bool:
2929
await i.get()
3030
self._logger.info("SSL Labs servers are up an running.")
3131
return True
32-
except (HTTPStatusError, ReadTimeout, ConnectTimeout) as ex:
32+
except (HTTPStatusError, ReadError, ReadTimeout, ConnectTimeout) as ex:
3333
self._logger.error(ex)
3434
return False
3535

36-
async def analyze(self, host: str, publish: bool = False, ignore_mismatch: bool = False) -> HostData:
36+
async def analyze(
37+
self,
38+
host: str,
39+
publish: bool = False,
40+
ignore_mismatch: bool = False,
41+
from_cache: bool = False,
42+
max_age: Optional[int] = None,
43+
) -> HostData:
3744
"""
3845
Test a particular host with respect to the cool off and the maximum number of assessments.
3946
4047
:param host: Host to test
4148
:param publish: True if assessment results should be published on the public results boards
4249
:param ignore_mismatch: True if assessment shall proceed even when the server certificate doesn't match the hostname
50+
:param from_cache: True if cached results should be used instead of new assessments
51+
:param max_age: Maximum age cached data might have in hours
4352
4453
See also: https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v3.md#protocol-usage
4554
"""
@@ -60,7 +69,11 @@ async def analyze(self, host: str, publish: bool = False, ignore_mismatch: bool
6069

6170
a = Analyze(self._client)
6271
host_object = await a.get(
63-
host=host, startNew="on", publish="on" if publish else "off", ignoreMismatch="on" if ignore_mismatch else "off"
72+
host=host,
73+
startNew="off" if from_cache else "on",
74+
publish="on" if publish else "off",
75+
ignoreMismatch="on" if ignore_mismatch else "off",
76+
maxAge=max_age,
6477
)
6578
self._semaphore.release()
6679
while host_object.status not in ["READY", "ERROR"]:

tests/test_ssllabs.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66
from dacite import from_dict
7-
from httpx import ConnectTimeout, HTTPStatusError, ReadTimeout
7+
from httpx import ConnectTimeout, HTTPStatusError, ReadError, ReadTimeout, TransportError
88

99
from ssllabs import Ssllabs
1010
from ssllabs.api.analyze import Analyze
@@ -33,16 +33,20 @@ async def test_ssllabs(self, request, api, result, parameters):
3333
assert dataclasses.asdict(api_data) == getattr(request.cls, call)
3434

3535
@pytest.mark.asyncio
36-
async def test_analyze(self, request):
36+
async def test_analyze(self, request: pytest.FixtureRequest) -> None:
3737
with patch(
3838
"ssllabs.api.info.Info.get", new=AsyncMock(return_value=from_dict(data_class=InfoData, data=request.cls.info))
3939
), patch(
4040
"ssllabs.api.analyze.Analyze.get",
4141
new=AsyncMock(return_value=from_dict(data_class=HostData, data=request.cls.analyze)),
42-
):
42+
) as get:
4343
ssllabs = Ssllabs()
4444
api_data = await ssllabs.analyze(host="devolo.de")
4545
assert dataclasses.asdict(api_data) == request.cls.analyze
46+
get.assert_called_with(host="devolo.de", ignoreMismatch="off", publish="off", startNew="on", maxAge=None)
47+
api_data = await ssllabs.analyze(host="devolo.de", from_cache=True, max_age=1)
48+
assert dataclasses.asdict(api_data) == request.cls.analyze
49+
get.assert_called_with(host="devolo.de", ignoreMismatch="off", publish="off", startNew="off", maxAge=1)
4650

4751
@pytest.mark.asyncio
4852
async def test_analyze_not_ready_yet(self, request, mocker):
@@ -119,8 +123,8 @@ async def test_availabile(self, request):
119123
assert await ssllabs.availability()
120124

121125
@pytest.mark.asyncio
122-
@pytest.mark.parametrize("exception", [ReadTimeout, ConnectTimeout])
123-
async def test_unavailabile_timeout(self, exception):
126+
@pytest.mark.parametrize("exception", [ReadError, ReadTimeout, ConnectTimeout])
127+
async def test_unavailabile_timeout(self, exception: TransportError) -> None:
124128
with patch("ssllabs.api.info.Info.get", new=AsyncMock(side_effect=exception(message="", request=""))):
125129
ssllabs = Ssllabs()
126130
assert not await ssllabs.availability()

0 commit comments

Comments
 (0)