domaintools-api 2.8.0__tar.gz → 2.8.1__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.
- {domaintools_api-2.8.0/domaintools_api.egg-info → domaintools_api-2.8.1}/PKG-INFO +1 -1
- domaintools_api-2.8.1/VERSION +1 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/_version.py +1 -1
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/api.py +7 -1
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/base_results.py +1 -1
- {domaintools_api-2.8.0 → domaintools_api-2.8.1/domaintools_api.egg-info}/PKG-INFO +1 -1
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/SOURCES.txt +1 -0
- domaintools_api-2.8.1/tests/test_iris_enrich_account_info.py +142 -0
- domaintools_api-2.8.0/VERSION +0 -1
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/LICENSE +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/README.md +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/__init__.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/__init__.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/api.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/__init__.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/accounts.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/detects.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/domains.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/feeds.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/ips.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/iris.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/constants.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/main.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/utils.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/constants.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/decorators.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/docstring_patcher.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/exceptions.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/filters.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/request_validator.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/results.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/specs/iris-openapi.yaml +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/utils.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/dependency_links.txt +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/entry_points.txt +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/requires.txt +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/top_level.txt +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_async/__init__.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/pyproject.toml +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/setup.cfg +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/setup.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_api.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_async.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_cli.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_decorators.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_docstring_patcher.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_filters.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_request_validator.py +0 -0
- {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_utils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.8.1
|
|
@@ -34,6 +34,7 @@ from domaintools.filters import (
|
|
|
34
34
|
filter_by_field,
|
|
35
35
|
DTResultFilter,
|
|
36
36
|
)
|
|
37
|
+
from domaintools.exceptions import ServiceUnavailableException, ServiceException
|
|
37
38
|
from domaintools.utils import validate_feeds_parameters
|
|
38
39
|
|
|
39
40
|
|
|
@@ -163,7 +164,12 @@ class API(object):
|
|
|
163
164
|
if product != "account-information" and self.rate_limit and not self.limits_set and not self.limits:
|
|
164
165
|
always_sign_api_key_previous_value = self.always_sign_api_key
|
|
165
166
|
header_authentication_previous_value = self.header_authentication
|
|
166
|
-
|
|
167
|
+
try:
|
|
168
|
+
self._rate_limit(product)
|
|
169
|
+
except (ServiceUnavailableException, ServiceException):
|
|
170
|
+
# If account_information fails (e.g. rate limited itself),
|
|
171
|
+
# skip client-side rate limiting and proceed with the original call.
|
|
172
|
+
self.limits_set = True
|
|
167
173
|
# Reset always_sign_api_key and header_authentication to its original
|
|
168
174
|
# User-set values as these might be affected when self.account_information() was executed
|
|
169
175
|
self.always_sign_api_key = always_sign_api_key_previous_value
|
|
@@ -140,7 +140,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
140
140
|
wait_for = self._wait_time()
|
|
141
141
|
if self.api.rate_limit and (wait_for is None or self.product == "account-information"):
|
|
142
142
|
data = self._make_request()
|
|
143
|
-
if data.status_code == 503: # pragma: no cover
|
|
143
|
+
if data.status_code == 503 and self.product != "account-information": # pragma: no cover
|
|
144
144
|
sleeptime = 60
|
|
145
145
|
log.info(
|
|
146
146
|
"503 encountered for [%s] - sleeping [%s] seconds before retrying request.",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Test that iris_enrich only triggers account_information once per API instance."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
|
|
5
|
+
from domaintools import API
|
|
6
|
+
from domaintools.exceptions import ServiceUnavailableException
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_iris_enrich_does_not_repeat_account_info_call():
|
|
10
|
+
"""Confirm account_information is fetched only once for multiple iris_enrich calls on same API instance."""
|
|
11
|
+
# Reset class-level state that may have been set by previous tests
|
|
12
|
+
API.limits = {}
|
|
13
|
+
API.limits_set = False
|
|
14
|
+
|
|
15
|
+
api = API("test_user", "test_key", rate_limit=True)
|
|
16
|
+
|
|
17
|
+
# Fake account info response
|
|
18
|
+
fake_account_data = {
|
|
19
|
+
"response": {
|
|
20
|
+
"products": [
|
|
21
|
+
{"id": "iris-enrich", "per_minute_limit": 10, "per_hour_limit": 100}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Fake iris-enrich response
|
|
27
|
+
fake_enrich_data = {
|
|
28
|
+
"response": {
|
|
29
|
+
"results": [{"domain": "google.com"}],
|
|
30
|
+
"results_count": 1,
|
|
31
|
+
"missing_domains": [],
|
|
32
|
+
"limit_exceeded": False,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
call_log = []
|
|
37
|
+
|
|
38
|
+
with patch("domaintools.base_results.Client") as mock_client:
|
|
39
|
+
mock_session = MagicMock()
|
|
40
|
+
mock_client.return_value.__enter__ = MagicMock(return_value=mock_session)
|
|
41
|
+
mock_client.return_value.__exit__ = MagicMock(return_value=False)
|
|
42
|
+
|
|
43
|
+
def fake_get(url, **kwargs):
|
|
44
|
+
call_log.append(("GET", url))
|
|
45
|
+
resp = MagicMock()
|
|
46
|
+
resp.status_code = 200
|
|
47
|
+
if "/v1/account" in url:
|
|
48
|
+
resp.json.return_value = fake_account_data
|
|
49
|
+
return resp
|
|
50
|
+
|
|
51
|
+
def fake_post(url, **kwargs):
|
|
52
|
+
call_log.append(("POST", url))
|
|
53
|
+
resp = MagicMock()
|
|
54
|
+
resp.status_code = 200
|
|
55
|
+
resp.json.return_value = fake_enrich_data
|
|
56
|
+
return resp
|
|
57
|
+
|
|
58
|
+
mock_session.get.side_effect = fake_get
|
|
59
|
+
mock_session.post.side_effect = fake_post
|
|
60
|
+
|
|
61
|
+
# First call - should trigger account_information (GET /v1/account)
|
|
62
|
+
api.iris_enrich("google.com")
|
|
63
|
+
# Second call - should NOT trigger account_information again
|
|
64
|
+
api.iris_enrich("amazon.com")
|
|
65
|
+
|
|
66
|
+
# Count how many times /v1/account was called
|
|
67
|
+
account_calls = [c for c in call_log if "/v1/account" in c[1]]
|
|
68
|
+
enrich_calls = [c for c in call_log if "/v1/iris-enrich" in c[1]]
|
|
69
|
+
|
|
70
|
+
assert len(account_calls) == 1, (
|
|
71
|
+
f"Expected exactly 1 account_information call, got {len(account_calls)}. "
|
|
72
|
+
f"Full call log: {call_log}"
|
|
73
|
+
)
|
|
74
|
+
assert len(enrich_calls) == 2, (
|
|
75
|
+
f"Expected exactly 2 iris_enrich calls, got {len(enrich_calls)}. "
|
|
76
|
+
f"Full call log: {call_log}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_iris_enrich_succeeds_when_account_info_is_rate_limited():
|
|
81
|
+
"""Verify that iris_enrich proceeds normally even if account_information returns a 503."""
|
|
82
|
+
# Reset class-level state that may have been set by previous tests
|
|
83
|
+
API.limits = {}
|
|
84
|
+
API.limits_set = False
|
|
85
|
+
|
|
86
|
+
api = API("test_user", "test_key", rate_limit=True)
|
|
87
|
+
|
|
88
|
+
# Fake iris-enrich response
|
|
89
|
+
fake_enrich_data = {
|
|
90
|
+
"response": {
|
|
91
|
+
"results": [{"domain": "google.com"}],
|
|
92
|
+
"results_count": 1,
|
|
93
|
+
"missing_domains": [],
|
|
94
|
+
"limit_exceeded": False,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
call_log = []
|
|
99
|
+
|
|
100
|
+
with patch("domaintools.base_results.Client") as mock_client, \
|
|
101
|
+
patch("domaintools.base_results.time.sleep"):
|
|
102
|
+
mock_session = MagicMock()
|
|
103
|
+
mock_client.return_value.__enter__ = MagicMock(return_value=mock_session)
|
|
104
|
+
mock_client.return_value.__exit__ = MagicMock(return_value=False)
|
|
105
|
+
|
|
106
|
+
def fake_get(url, **kwargs):
|
|
107
|
+
call_log.append(("GET", url))
|
|
108
|
+
resp = MagicMock()
|
|
109
|
+
if "/v1/account" in url:
|
|
110
|
+
# Simulate a 503 rate limit on account_information
|
|
111
|
+
resp.status_code = 503
|
|
112
|
+
resp.json.return_value = {"error": {"message": "Rate limit exceeded"}}
|
|
113
|
+
else:
|
|
114
|
+
resp.status_code = 200
|
|
115
|
+
resp.json.return_value = {}
|
|
116
|
+
return resp
|
|
117
|
+
|
|
118
|
+
def fake_post(url, **kwargs):
|
|
119
|
+
call_log.append(("POST", url))
|
|
120
|
+
resp = MagicMock()
|
|
121
|
+
resp.status_code = 200
|
|
122
|
+
resp.json.return_value = fake_enrich_data
|
|
123
|
+
return resp
|
|
124
|
+
|
|
125
|
+
mock_session.get.side_effect = fake_get
|
|
126
|
+
mock_session.post.side_effect = fake_post
|
|
127
|
+
|
|
128
|
+
# This should NOT raise an exception despite account_info returning 503
|
|
129
|
+
result = api.iris_enrich("google.com")
|
|
130
|
+
|
|
131
|
+
# Verify the iris_enrich call succeeded
|
|
132
|
+
enrich_calls = [c for c in call_log if "/v1/iris-enrich" in c[1]]
|
|
133
|
+
assert len(enrich_calls) == 1, (
|
|
134
|
+
f"Expected 1 iris_enrich call, got {len(enrich_calls)}. Full call log: {call_log}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Verify limits_set was set to True so it won't retry account_info
|
|
138
|
+
assert api.limits_set is True
|
|
139
|
+
|
|
140
|
+
# Verify the result contains expected data
|
|
141
|
+
assert result["results_count"] == 1
|
|
142
|
+
assert result["results"][0]["domain"] == "google.com"
|
domaintools_api-2.8.0/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.8.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|