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.
Files changed (49) hide show
  1. {domaintools_api-2.8.0/domaintools_api.egg-info → domaintools_api-2.8.1}/PKG-INFO +1 -1
  2. domaintools_api-2.8.1/VERSION +1 -0
  3. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/_version.py +1 -1
  4. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/api.py +7 -1
  5. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/base_results.py +1 -1
  6. {domaintools_api-2.8.0 → domaintools_api-2.8.1/domaintools_api.egg-info}/PKG-INFO +1 -1
  7. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/SOURCES.txt +1 -0
  8. domaintools_api-2.8.1/tests/test_iris_enrich_account_info.py +142 -0
  9. domaintools_api-2.8.0/VERSION +0 -1
  10. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/LICENSE +0 -0
  11. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/README.md +0 -0
  12. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/__init__.py +0 -0
  13. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/__init__.py +0 -0
  14. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/api.py +0 -0
  15. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/__init__.py +0 -0
  16. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/accounts.py +0 -0
  17. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/detects.py +0 -0
  18. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/domains.py +0 -0
  19. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/feeds.py +0 -0
  20. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/ips.py +0 -0
  21. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/commands/iris.py +0 -0
  22. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/constants.py +0 -0
  23. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/main.py +0 -0
  24. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/cli/utils.py +0 -0
  25. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/constants.py +0 -0
  26. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/decorators.py +0 -0
  27. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/docstring_patcher.py +0 -0
  28. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/exceptions.py +0 -0
  29. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/filters.py +0 -0
  30. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/request_validator.py +0 -0
  31. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/results.py +0 -0
  32. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/specs/iris-openapi.yaml +0 -0
  33. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools/utils.py +0 -0
  34. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/dependency_links.txt +0 -0
  35. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/entry_points.txt +0 -0
  36. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/requires.txt +0 -0
  37. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_api.egg-info/top_level.txt +0 -0
  38. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/domaintools_async/__init__.py +0 -0
  39. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/pyproject.toml +0 -0
  40. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/setup.cfg +0 -0
  41. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/setup.py +0 -0
  42. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_api.py +0 -0
  43. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_async.py +0 -0
  44. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_cli.py +0 -0
  45. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_decorators.py +0 -0
  46. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_docstring_patcher.py +0 -0
  47. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_filters.py +0 -0
  48. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_request_validator.py +0 -0
  49. {domaintools_api-2.8.0 → domaintools_api-2.8.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: domaintools_api
3
- Version: 2.8.0
3
+ Version: 2.8.1
4
4
  Summary: DomainTools Official Python API
5
5
  Author-email: DomainTools <integrations@domaintools.com>
6
6
  License: The MIT License (MIT)
@@ -0,0 +1 @@
1
+ 2.8.1
@@ -20,4 +20,4 @@ OTHER DEALINGS IN THE SOFTWARE.
20
20
 
21
21
  """
22
22
 
23
- current = "2.8.0"
23
+ current = "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
- self._rate_limit(product)
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.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: domaintools_api
3
- Version: 2.8.0
3
+ Version: 2.8.1
4
4
  Summary: DomainTools Official Python API
5
5
  Author-email: DomainTools <integrations@domaintools.com>
6
6
  License: The MIT License (MIT)
@@ -42,5 +42,6 @@ tests/test_cli.py
42
42
  tests/test_decorators.py
43
43
  tests/test_docstring_patcher.py
44
44
  tests/test_filters.py
45
+ tests/test_iris_enrich_account_info.py
45
46
  tests/test_request_validator.py
46
47
  tests/test_utils.py
@@ -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"
@@ -1 +0,0 @@
1
- 2.8.0
File without changes