domaintools-api 2.7.2__tar.gz → 2.7.4__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 (48) hide show
  1. {domaintools_api-2.7.2/domaintools_api.egg-info → domaintools_api-2.7.4}/PKG-INFO +1 -1
  2. domaintools_api-2.7.4/VERSION +1 -0
  3. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/_version.py +1 -1
  4. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/api.py +10 -31
  5. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/decorators.py +4 -4
  6. {domaintools_api-2.7.2 → domaintools_api-2.7.4/domaintools_api.egg-info}/PKG-INFO +1 -1
  7. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_api.py +3 -12
  8. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_decorators.py +32 -0
  9. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_filters.py +5 -12
  10. domaintools_api-2.7.2/VERSION +0 -1
  11. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/LICENSE +0 -0
  12. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/README.md +0 -0
  13. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/__init__.py +0 -0
  14. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/base_results.py +0 -0
  15. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/__init__.py +0 -0
  16. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/api.py +0 -0
  17. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/__init__.py +0 -0
  18. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/accounts.py +0 -0
  19. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/detects.py +0 -0
  20. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/domains.py +0 -0
  21. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/feeds.py +0 -0
  22. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/ips.py +0 -0
  23. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/commands/iris.py +0 -0
  24. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/constants.py +0 -0
  25. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/main.py +0 -0
  26. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/cli/utils.py +0 -0
  27. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/constants.py +0 -0
  28. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/docstring_patcher.py +0 -0
  29. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/exceptions.py +0 -0
  30. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/filters.py +0 -0
  31. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/request_validator.py +0 -0
  32. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/results.py +0 -0
  33. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/specs/iris-openapi.yaml +0 -0
  34. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools/utils.py +0 -0
  35. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools_api.egg-info/SOURCES.txt +0 -0
  36. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools_api.egg-info/dependency_links.txt +0 -0
  37. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools_api.egg-info/entry_points.txt +0 -0
  38. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools_api.egg-info/requires.txt +0 -0
  39. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools_api.egg-info/top_level.txt +0 -0
  40. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/domaintools_async/__init__.py +0 -0
  41. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/pyproject.toml +0 -0
  42. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/setup.cfg +0 -0
  43. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/setup.py +0 -0
  44. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_async.py +0 -0
  45. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_cli.py +0 -0
  46. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_docstring_patcher.py +0 -0
  47. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_request_validator.py +0 -0
  48. {domaintools_api-2.7.2 → domaintools_api-2.7.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: domaintools_api
3
- Version: 2.7.2
3
+ Version: 2.7.4
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.7.4
@@ -20,4 +20,4 @@ OTHER DEALINGS IN THE SOFTWARE.
20
20
 
21
21
  """
22
22
 
23
- current = "2.7.2"
23
+ current = "2.7.4"
@@ -128,11 +128,7 @@ class API(object):
128
128
  print(f"Error loading {specs_file_path}: {e}")
129
129
 
130
130
  def _get_ssl_default_context(self, verify_ssl: Union[str, bool]):
131
- return (
132
- ssl.create_default_context(cafile=verify_ssl)
133
- if isinstance(verify_ssl, str)
134
- else verify_ssl
135
- )
131
+ return ssl.create_default_context(cafile=verify_ssl) if isinstance(verify_ssl, str) else verify_ssl
136
132
 
137
133
  def _build_api_url(self, api_url=None, api_port=None):
138
134
  """Build the API url based on the given url and port. Defaults to `https://api.domaintools.com`"""
@@ -160,18 +156,11 @@ class API(object):
160
156
  hours = limit_hours and 3600 / float(limit_hours)
161
157
  minutes = limit_minutes and 60 / float(limit_minutes)
162
158
 
163
- self.limits[product["id"]] = {
164
- "interval": timedelta(seconds=minutes or hours or default)
165
- }
159
+ self.limits[product["id"]] = {"interval": timedelta(seconds=minutes or hours or default)}
166
160
 
167
161
  def _results(self, product, path, cls=Results, **kwargs):
168
162
  """Returns _results for the specified API path with the specified **kwargs parameters"""
169
- if (
170
- product != "account-information"
171
- and self.rate_limit
172
- and not self.limits_set
173
- and not self.limits
174
- ):
163
+ if product != "account-information" and self.rate_limit and not self.limits_set and not self.limits:
175
164
  always_sign_api_key_previous_value = self.always_sign_api_key
176
165
  header_authentication_previous_value = self.header_authentication
177
166
  self._rate_limit(product)
@@ -215,9 +204,7 @@ class API(object):
215
204
  else:
216
205
  raise ValueError(
217
206
  "Invalid value '{0}' for 'key_sign_hash'. "
218
- "Values available are {1}".format(
219
- self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES)
220
- )
207
+ "Values available are {1}".format(self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES))
221
208
  )
222
209
 
223
210
  parameters["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -229,9 +216,7 @@ class API(object):
229
216
 
230
217
  def account_information(self, **kwargs):
231
218
  """Provides a snapshot of your accounts current API usage"""
232
- return self._results(
233
- "account-information", "/v1/account", items_path=("products",), **kwargs
234
- )
219
+ return self._results("account-information", "/v1/account", items_path=("products",), **kwargs)
235
220
 
236
221
  def available_api_calls(self):
237
222
  """Provides a list of api calls that you can use based on your account information."""
@@ -434,9 +419,7 @@ class API(object):
434
419
 
435
420
  def reverse_ip(self, domain=None, limit=None, **kwargs):
436
421
  """Pass in a domain name."""
437
- return self._results(
438
- "reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs
439
- )
422
+ return self._results("reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs)
440
423
 
441
424
  def host_domains(self, ip=None, limit=None, **kwargs):
442
425
  """Pass in an IP address."""
@@ -610,12 +593,8 @@ class API(object):
610
593
  younger_than_date = kwargs.pop("younger_than_date", {}) or None
611
594
  older_than_date = kwargs.pop("older_than_date", {}) or None
612
595
  updated_after = kwargs.pop("updated_after", {}) or None
613
- include_domains_with_missing_field = (
614
- kwargs.pop("include_domains_with_missing_field", {}) or None
615
- )
616
- exclude_domains_with_missing_field = (
617
- kwargs.pop("exclude_domains_with_missing_field", {}) or None
618
- )
596
+ include_domains_with_missing_field = kwargs.pop("include_domains_with_missing_field", {}) or None
597
+ exclude_domains_with_missing_field = kwargs.pop("exclude_domains_with_missing_field", {}) or None
619
598
 
620
599
  filtered_results = DTResultFilter(result_set=results).by(
621
600
  [
@@ -677,7 +656,7 @@ class API(object):
677
656
  create_date=None,
678
657
  active=None,
679
658
  search_hash=None,
680
- risk_score=None,
659
+ risk_score_threshold=None,
681
660
  younger_than_date=None,
682
661
  older_than_date=None,
683
662
  updated_after=None,
@@ -732,7 +711,7 @@ class API(object):
732
711
 
733
712
  filtered_results = DTResultFilter(result_set=results).by(
734
713
  [
735
- filter_by_riskscore(threshold=risk_score),
714
+ filter_by_riskscore(threshold=risk_score_threshold),
736
715
  filter_by_expire_date(date=younger_than_date, lookup_type="before"),
737
716
  filter_by_expire_date(date=older_than_date, lookup_type="after"),
738
717
  filter_by_date_updated_after(date=updated_after),
@@ -51,9 +51,7 @@ def api_endpoint(spec_name: str, path: str, methods: Union[str, List[str]]):
51
51
 
52
52
  if "domains" in arguments.keys():
53
53
  domains = arguments.pop("domains")
54
- arguments["domain"] = (
55
- ",".join(domains) if isinstance(domains, (list, tuple)) else domains
56
- )
54
+ arguments["domain"] = ",".join(domains) if isinstance(domains, (list, tuple)) else domains
57
55
 
58
56
  if spec:
59
57
  # Determine which HTTP method is currently being executed.
@@ -61,6 +59,8 @@ def api_endpoint(spec_name: str, path: str, methods: Union[str, List[str]]):
61
59
  # Otherwise, default to the first method defined in the decorator.
62
60
  current_method = kwargs.get("method", normalized_methods[0])
63
61
 
62
+ filtered_arguments = {k: v for k, v in arguments.items() if v is not None}
63
+
64
64
  # Run Validation
65
65
  # This will raise a ValueError and stop execution if validation fails.
66
66
  try:
@@ -68,7 +68,7 @@ def api_endpoint(spec_name: str, path: str, methods: Union[str, List[str]]):
68
68
  spec=spec,
69
69
  path=path,
70
70
  method=current_method,
71
- parameters=arguments,
71
+ parameters=filtered_arguments,
72
72
  )
73
73
  except ValueError as e:
74
74
  print(f"[Validation Error] {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: domaintools_api
3
- Version: 2.7.2
3
+ Version: 2.7.4
4
4
  Summary: DomainTools Official Python API
5
5
  Author-email: DomainTools <integrations@domaintools.com>
6
6
  License: The MIT License (MIT)
@@ -143,7 +143,6 @@ def test_name_server_monitor():
143
143
  def test_parsed_whois():
144
144
  api_call = api.parsed_whois("google.com")
145
145
  with api_call as result:
146
- assert "registrant" in result
147
146
  assert "registration" in result
148
147
  assert "name_servers" in result
149
148
  assert "whois" in result
@@ -291,8 +290,6 @@ def test_whois():
291
290
  assert "whois" in whois
292
291
  assert "record_source" in whois
293
292
 
294
- assert "abusecomplaints@markmonitor.com" in api_call.emails()
295
-
296
293
 
297
294
  @vcr.use_cassette
298
295
  def test_whois_history():
@@ -345,9 +342,7 @@ def test_exception_handling():
345
342
  ValueError,
346
343
  match=r"Invalid value 'notahash' for 'key_sign_hash'. Values available are sha1,sha256",
347
344
  ):
348
- API(
349
- "notauser", "notakey", always_sign_api_key=True, key_sign_hash="notahash"
350
- ).domain_search("amazon")
345
+ API("notauser", "notakey", always_sign_api_key=True, key_sign_hash="notahash").domain_search("amazon")
351
346
 
352
347
 
353
348
  @vcr.use_cassette
@@ -356,9 +351,7 @@ def test_md5_is_not_supported():
356
351
  ValueError,
357
352
  match=r"Invalid value 'md5' for 'key_sign_hash'. Values available are sha1,sha256",
358
353
  ):
359
- API("notauser", "notakey", always_sign_api_key=True, key_sign_hash="md5").domain_search(
360
- "amazon"
361
- )
354
+ API("notauser", "notakey", always_sign_api_key=True, key_sign_hash="md5").domain_search("amazon")
362
355
 
363
356
 
364
357
  # @vcr.use_cassette
@@ -468,9 +461,7 @@ def test_iris_detect_monitors():
468
461
 
469
462
  @vcr.use_cassette
470
463
  def test_iris_detect_new_domains():
471
- detect_results = api.iris_detect_new_domains(
472
- monitor_id="nAwmQg2pqg", sort=["risk_score"], order="desc"
473
- )
464
+ detect_results = api.iris_detect_new_domains(monitor_id="nAwmQg2pqg", sort=["risk_score"], order="desc")
474
465
  assert detect_results["watchlist_domains"][0]["risk_score"] == 100
475
466
 
476
467
 
@@ -217,3 +217,35 @@ class TestApiEndpointDecorator:
217
217
  call_kwargs = mock_validate.call_args[1]
218
218
 
219
219
  assert call_kwargs.get("parameters") == {"name": "test-name"}
220
+
221
+ def test_none_arguments_are_filtered_before_validation(self, mock_client):
222
+ """
223
+ Test that parameters with None values are excluded from the arguments
224
+ passed to the validator.
225
+ """
226
+
227
+ @api_endpoint(spec_name="v1", path="/users", methods="POST")
228
+ def create_user(name=None, status=None):
229
+ return "Success"
230
+
231
+ with patch("domaintools.request_validator.RequestValidator.validate") as mock_validate:
232
+ create_user(mock_client, name="Alice", status=None)
233
+
234
+ call_kwargs = mock_validate.call_args[1]
235
+ assert call_kwargs.get("parameters") == {"name": "Alice"}
236
+ assert "status" not in call_kwargs.get("parameters", {})
237
+
238
+ def test_all_none_arguments_passes_empty_parameters(self, mock_client):
239
+ """
240
+ Test that when all arguments are None, an empty dict is passed to the validator.
241
+ """
242
+
243
+ @api_endpoint(spec_name="v1", path="/users", methods="POST")
244
+ def create_user(name=None, body=None):
245
+ return "Success"
246
+
247
+ with patch("domaintools.request_validator.RequestValidator.validate") as mock_validate:
248
+ create_user(mock_client, name=None, body=None)
249
+
250
+ call_kwargs = mock_validate.call_args[1]
251
+ assert call_kwargs.get("parameters") == {}
@@ -16,20 +16,17 @@ class DTFiltersTest(unittest.TestCase):
16
16
  domaintools_iris_result = iris_investigate_data.domaintools()
17
17
  int_chase_iris_result = iris_investigate_data.int_chase()
18
18
 
19
- test_results = {
20
- "results": domaintools_iris_result["results"]
21
- + int_chase_iris_result["results"]
22
- }
19
+ test_results = {"results": domaintools_iris_result["results"] + int_chase_iris_result["results"]}
23
20
 
24
21
  self.dt_res_filter = DTResultFilter(result_set=test_results)
25
22
 
26
23
  def test_filter_by_riskscore(self):
27
24
  """Test domaintools filter by riskcore"""
28
- risk_score = 69
25
+ risk_score_threshold = 69
29
26
 
30
27
  result = self.dt_res_filter.by(
31
28
  [
32
- filter_by_riskscore(threshold=risk_score),
29
+ filter_by_riskscore(threshold=risk_score_threshold),
33
30
  ]
34
31
  )
35
32
 
@@ -77,9 +74,7 @@ class DTFiltersTest(unittest.TestCase):
77
74
 
78
75
  results = self.dt_res_filter.by(
79
76
  [
80
- filter_by_field(
81
- field=include_domains_with_missing_field, filter_type="include"
82
- ),
77
+ filter_by_field(field=include_domains_with_missing_field, filter_type="include"),
83
78
  ]
84
79
  )
85
80
 
@@ -90,9 +85,7 @@ class DTFiltersTest(unittest.TestCase):
90
85
 
91
86
  results = self.dt_res_filter.by(
92
87
  [
93
- filter_by_field(
94
- field=exclude_domains_with_missing_field, filter_type="exclude"
95
- ),
88
+ filter_by_field(field=exclude_domains_with_missing_field, filter_type="exclude"),
96
89
  ]
97
90
  )
98
91
 
@@ -1 +0,0 @@
1
- 2.7.2
File without changes