domaintools-api-test-upload 2.6.1__tar.gz → 2.6.3__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_test_upload-2.6.1/domaintools_api_test_upload.egg-info → domaintools_api_test_upload-2.6.3}/PKG-INFO +6 -2
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/README.md +4 -0
- domaintools_api_test_upload-2.6.3/VERSION +1 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/_version.py +1 -1
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/api.py +82 -44
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/base_results.py +4 -3
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/api.py +1 -2
- domaintools_api_test_upload-2.6.3/domaintools/constants.py +63 -0
- domaintools_api_test_upload-2.6.3/domaintools/decorators.py +57 -0
- domaintools_api_test_upload-2.6.3/domaintools/docstring_patcher.py +334 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/results.py +37 -67
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/utils.py +5 -3
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3/domaintools_api_test_upload.egg-info}/PKG-INFO +6 -2
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/SOURCES.txt +3 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/pyproject.toml +1 -1
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/setup.py +1 -1
- domaintools_api_test_upload-2.6.3/tests/test_docstring_patcher.py +351 -0
- domaintools_api_test_upload-2.6.1/VERSION +0 -1
- domaintools_api_test_upload-2.6.1/domaintools/constants.py +0 -41
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/LICENSE +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/accounts.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/detects.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/domains.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/feeds.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/ips.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/iris.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/constants.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/main.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/utils.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/exceptions.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/filters.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/dependency_links.txt +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/entry_points.txt +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/requires.txt +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/top_level.txt +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools_async/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/setup.cfg +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/tests/test_api.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/tests/test_async.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/tests/test_cli.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/tests/test_filters.py +0 -0
- {domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
|
-
Name:
|
|
3
|
-
Version: 2.6.
|
|
2
|
+
Name: domaintools_api_test_upload
|
|
3
|
+
Version: 2.6.3
|
|
4
4
|
Summary: DomainTools Official Python API
|
|
5
5
|
Author-email: DomainTools <integrations@domaintools.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -354,6 +354,10 @@ To add more e2e tests, put these in the `../tests/e2e` folder.
|
|
|
354
354
|
export TEST_USER=<user-key>
|
|
355
355
|
export TEST_KEY=<api-key>
|
|
356
356
|
```
|
|
357
|
+
- Run unit tests.
|
|
358
|
+
```bash
|
|
359
|
+
tox -e
|
|
360
|
+
```
|
|
357
361
|
|
|
358
362
|
## Run the end-to-end test script
|
|
359
363
|
- Before running the test, be sure that docker is running.
|
|
@@ -304,6 +304,10 @@ To add more e2e tests, put these in the `../tests/e2e` folder.
|
|
|
304
304
|
export TEST_USER=<user-key>
|
|
305
305
|
export TEST_KEY=<api-key>
|
|
306
306
|
```
|
|
307
|
+
- Run unit tests.
|
|
308
|
+
```bash
|
|
309
|
+
tox -e
|
|
310
|
+
```
|
|
307
311
|
|
|
308
312
|
## Run the end-to-end test script
|
|
309
313
|
- Before running the test, be sure that docker is running.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.6.3
|
|
@@ -5,8 +5,16 @@ from typing import Union
|
|
|
5
5
|
|
|
6
6
|
import re
|
|
7
7
|
import ssl
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from domaintools.constants import (
|
|
11
|
+
Endpoint,
|
|
12
|
+
OutputFormat,
|
|
13
|
+
ENDPOINT_TO_SOURCE_MAP,
|
|
14
|
+
RTTF_PRODUCTS_LIST,
|
|
15
|
+
RTTF_PRODUCTS_CMD_MAPPING,
|
|
16
|
+
SPECS_MAPPING,
|
|
17
|
+
)
|
|
10
18
|
from domaintools._version import current as version
|
|
11
19
|
from domaintools.results import (
|
|
12
20
|
GroupedIterable,
|
|
@@ -16,6 +24,7 @@ from domaintools.results import (
|
|
|
16
24
|
Results,
|
|
17
25
|
FeedsResults,
|
|
18
26
|
)
|
|
27
|
+
from domaintools.decorators import api_endpoint, auto_patch_docstrings
|
|
19
28
|
from domaintools.filters import (
|
|
20
29
|
filter_by_riskscore,
|
|
21
30
|
filter_by_expire_date,
|
|
@@ -34,6 +43,7 @@ def delimited(items, character="|"):
|
|
|
34
43
|
return character.join(items) if type(items) in (list, tuple, set) else items
|
|
35
44
|
|
|
36
45
|
|
|
46
|
+
@auto_patch_docstrings
|
|
37
47
|
class API(object):
|
|
38
48
|
"""Enables interacting with the DomainTools API via Python:
|
|
39
49
|
|
|
@@ -88,8 +98,10 @@ class API(object):
|
|
|
88
98
|
self.key_sign_hash = key_sign_hash
|
|
89
99
|
self.default_parameters["app_name"] = app_name
|
|
90
100
|
self.default_parameters["app_version"] = app_version
|
|
101
|
+
self.specs = {}
|
|
91
102
|
|
|
92
103
|
self._build_api_url(api_url, api_port)
|
|
104
|
+
self._initialize_specs()
|
|
93
105
|
|
|
94
106
|
if not https:
|
|
95
107
|
raise Exception(
|
|
@@ -98,8 +110,25 @@ class API(object):
|
|
|
98
110
|
if proxy_url and not isinstance(proxy_url, str):
|
|
99
111
|
raise Exception("Proxy URL must be a string. For example: '127.0.0.1:8888'")
|
|
100
112
|
|
|
113
|
+
def _initialize_specs(self):
|
|
114
|
+
for spec_name, file_path in SPECS_MAPPING.items():
|
|
115
|
+
try:
|
|
116
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
117
|
+
spec_content = yaml.safe_load(f)
|
|
118
|
+
if not spec_content:
|
|
119
|
+
raise ValueError("Spec file is empty or invalid.")
|
|
120
|
+
|
|
121
|
+
self.specs[spec_name] = spec_content
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"Error loading {file_path}: {e}")
|
|
125
|
+
|
|
101
126
|
def _get_ssl_default_context(self, verify_ssl: Union[str, bool]):
|
|
102
|
-
return
|
|
127
|
+
return (
|
|
128
|
+
ssl.create_default_context(cafile=verify_ssl)
|
|
129
|
+
if isinstance(verify_ssl, str)
|
|
130
|
+
else verify_ssl
|
|
131
|
+
)
|
|
103
132
|
|
|
104
133
|
def _build_api_url(self, api_url=None, api_port=None):
|
|
105
134
|
"""Build the API url based on the given url and port. Defaults to `https://api.domaintools.com`"""
|
|
@@ -112,8 +141,12 @@ class API(object):
|
|
|
112
141
|
|
|
113
142
|
self._rest_api_url = rest_api_url
|
|
114
143
|
|
|
115
|
-
def _rate_limit(self):
|
|
144
|
+
def _rate_limit(self, product):
|
|
116
145
|
"""Pulls in and enforces the latest rate limits for the specified user"""
|
|
146
|
+
if product in RTTF_PRODUCTS_LIST:
|
|
147
|
+
self.limits_set = False
|
|
148
|
+
return
|
|
149
|
+
|
|
117
150
|
self.limits_set = True
|
|
118
151
|
for product in self.account_information():
|
|
119
152
|
limit_minutes = product["per_minute_limit"] or None
|
|
@@ -123,14 +156,21 @@ class API(object):
|
|
|
123
156
|
hours = limit_hours and 3600 / float(limit_hours)
|
|
124
157
|
minutes = limit_minutes and 60 / float(limit_minutes)
|
|
125
158
|
|
|
126
|
-
self.limits[product["id"]] = {
|
|
159
|
+
self.limits[product["id"]] = {
|
|
160
|
+
"interval": timedelta(seconds=minutes or hours or default)
|
|
161
|
+
}
|
|
127
162
|
|
|
128
163
|
def _results(self, product, path, cls=Results, **kwargs):
|
|
129
164
|
"""Returns _results for the specified API path with the specified **kwargs parameters"""
|
|
130
|
-
if
|
|
165
|
+
if (
|
|
166
|
+
product != "account-information"
|
|
167
|
+
and self.rate_limit
|
|
168
|
+
and not self.limits_set
|
|
169
|
+
and not self.limits
|
|
170
|
+
):
|
|
131
171
|
always_sign_api_key_previous_value = self.always_sign_api_key
|
|
132
172
|
header_authentication_previous_value = self.header_authentication
|
|
133
|
-
self._rate_limit()
|
|
173
|
+
self._rate_limit(product)
|
|
134
174
|
# Reset always_sign_api_key and header_authentication to its original
|
|
135
175
|
# User-set values as these might be affected when self.account_information() was executed
|
|
136
176
|
self.always_sign_api_key = always_sign_api_key_previous_value
|
|
@@ -171,7 +211,9 @@ class API(object):
|
|
|
171
211
|
else:
|
|
172
212
|
raise ValueError(
|
|
173
213
|
"Invalid value '{0}' for 'key_sign_hash'. "
|
|
174
|
-
"Values available are {1}".format(
|
|
214
|
+
"Values available are {1}".format(
|
|
215
|
+
self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES)
|
|
216
|
+
)
|
|
175
217
|
)
|
|
176
218
|
|
|
177
219
|
parameters["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -183,7 +225,9 @@ class API(object):
|
|
|
183
225
|
|
|
184
226
|
def account_information(self, **kwargs):
|
|
185
227
|
"""Provides a snapshot of your accounts current API usage"""
|
|
186
|
-
return self._results(
|
|
228
|
+
return self._results(
|
|
229
|
+
"account-information", "/v1/account", items_path=("products",), **kwargs
|
|
230
|
+
)
|
|
187
231
|
|
|
188
232
|
def available_api_calls(self):
|
|
189
233
|
"""Provides a list of api calls that you can use based on your account information."""
|
|
@@ -205,13 +249,23 @@ class API(object):
|
|
|
205
249
|
if not api_call.startswith("_") and callable(getattr(API, api_call, None))
|
|
206
250
|
)
|
|
207
251
|
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
]
|
|
214
|
-
|
|
252
|
+
|
|
253
|
+
account_information = self.account_information()
|
|
254
|
+
|
|
255
|
+
available_calls = set()
|
|
256
|
+
for product in self.account_information():
|
|
257
|
+
product_id = product["id"]
|
|
258
|
+
# for RTUF endpoints as we use different func name in our wrapper
|
|
259
|
+
if product_id in RTTF_PRODUCTS_LIST:
|
|
260
|
+
if rttf_api_command := RTTF_PRODUCTS_CMD_MAPPING.get(product_id):
|
|
261
|
+
available_calls.add(rttf_api_command)
|
|
262
|
+
|
|
263
|
+
# for IRIS endpoints
|
|
264
|
+
snakecase_pid = snakecase(product_id)
|
|
265
|
+
if snakecase_pid in api_calls:
|
|
266
|
+
available_calls.add(snakecase_pid)
|
|
267
|
+
|
|
268
|
+
return sorted(available_calls)
|
|
215
269
|
|
|
216
270
|
def brand_monitor(self, query, exclude=None, domain_status=None, days_back=None, **kwargs):
|
|
217
271
|
"""Pass in one or more terms as a list or separated by the pipe character ( | )"""
|
|
@@ -376,7 +430,9 @@ class API(object):
|
|
|
376
430
|
|
|
377
431
|
def reverse_ip(self, domain=None, limit=None, **kwargs):
|
|
378
432
|
"""Pass in a domain name."""
|
|
379
|
-
return self._results(
|
|
433
|
+
return self._results(
|
|
434
|
+
"reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs
|
|
435
|
+
)
|
|
380
436
|
|
|
381
437
|
def host_domains(self, ip=None, limit=None, **kwargs):
|
|
382
438
|
"""Pass in an IP address."""
|
|
@@ -550,8 +606,12 @@ class API(object):
|
|
|
550
606
|
younger_than_date = kwargs.pop("younger_than_date", {}) or None
|
|
551
607
|
older_than_date = kwargs.pop("older_than_date", {}) or None
|
|
552
608
|
updated_after = kwargs.pop("updated_after", {}) or None
|
|
553
|
-
include_domains_with_missing_field =
|
|
554
|
-
|
|
609
|
+
include_domains_with_missing_field = (
|
|
610
|
+
kwargs.pop("include_domains_with_missing_field", {}) or None
|
|
611
|
+
)
|
|
612
|
+
exclude_domains_with_missing_field = (
|
|
613
|
+
kwargs.pop("exclude_domains_with_missing_field", {}) or None
|
|
614
|
+
)
|
|
555
615
|
|
|
556
616
|
filtered_results = DTResultFilter(result_set=results).by(
|
|
557
617
|
[
|
|
@@ -604,6 +664,7 @@ class API(object):
|
|
|
604
664
|
**kwargs,
|
|
605
665
|
)
|
|
606
666
|
|
|
667
|
+
@api_endpoint(spec_name="iris", path="/v1/iris-investigate/", methods="post")
|
|
607
668
|
def iris_investigate(
|
|
608
669
|
self,
|
|
609
670
|
domains=None,
|
|
@@ -621,29 +682,6 @@ class API(object):
|
|
|
621
682
|
**kwargs,
|
|
622
683
|
):
|
|
623
684
|
"""Returns back a list of domains based on the provided filters.
|
|
624
|
-
The following filters are available beyond what is parameterized as kwargs:
|
|
625
|
-
|
|
626
|
-
- ip: Search for domains having this IP.
|
|
627
|
-
- email: Search for domains with this email in their data.
|
|
628
|
-
- email_domain: Search for domains where the email address uses this domain.
|
|
629
|
-
- nameserver_host: Search for domains with this nameserver.
|
|
630
|
-
- nameserver_domain: Search for domains with a nameserver that has this domain.
|
|
631
|
-
- nameserver_ip: Search for domains with a nameserver on this IP.
|
|
632
|
-
- registrar: Search for domains with this registrar.
|
|
633
|
-
- registrant: Search for domains with this registrant name.
|
|
634
|
-
- registrant_org: Search for domains with this registrant organization.
|
|
635
|
-
- mailserver_host: Search for domains with this mailserver.
|
|
636
|
-
- mailserver_domain: Search for domains with a mailserver that has this domain.
|
|
637
|
-
- mailserver_ip: Search for domains with a mailserver on this IP.
|
|
638
|
-
- redirect_domain: Search for domains which redirect to this domain.
|
|
639
|
-
- ssl_hash: Search for domains which have an SSL certificate with this hash.
|
|
640
|
-
- ssl_subject: Search for domains which have an SSL certificate with this subject string.
|
|
641
|
-
- ssl_email: Search for domains which have an SSL certificate with this email in it.
|
|
642
|
-
- ssl_org: Search for domains which have an SSL certificate with this organization in it.
|
|
643
|
-
- google_analytics: Search for domains which have this Google Analytics code.
|
|
644
|
-
- adsense: Search for domains which have this AdSense code.
|
|
645
|
-
- tld: Filter by TLD. Must be combined with another parameter.
|
|
646
|
-
- search_hash: Use search hash from Iris to bring back domains.
|
|
647
685
|
|
|
648
686
|
You can loop over results of your investigation as if it was a native Python list:
|
|
649
687
|
|
|
@@ -1275,7 +1313,7 @@ class API(object):
|
|
|
1275
1313
|
kwargs.pop("headers", None)
|
|
1276
1314
|
|
|
1277
1315
|
return self._results(
|
|
1278
|
-
f"domain-risk-
|
|
1316
|
+
f"real-time-domain-risk-({source})",
|
|
1279
1317
|
f"v1/{endpoint}/domainrisk/",
|
|
1280
1318
|
response_path=(),
|
|
1281
1319
|
cls=FeedsResults,
|
|
@@ -1309,7 +1347,7 @@ class API(object):
|
|
|
1309
1347
|
kwargs.pop("headers", None)
|
|
1310
1348
|
|
|
1311
1349
|
return self._results(
|
|
1312
|
-
f"domain-hotlist-
|
|
1350
|
+
f"real-time-domain-hotlist-({source})",
|
|
1313
1351
|
f"v1/{endpoint}/domainhotlist/",
|
|
1314
1352
|
response_path=(),
|
|
1315
1353
|
cls=FeedsResults,
|
{domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/base_results.py
RENAMED
|
@@ -94,8 +94,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
94
94
|
headers["accept"] = HEADER_ACCEPT_KEY_CSV_FORMAT
|
|
95
95
|
|
|
96
96
|
if self.api.header_authentication:
|
|
97
|
-
|
|
98
|
-
headers[header_key_for_api_key] = self.api.key
|
|
97
|
+
headers["X-Api-Key"] = self.api.key
|
|
99
98
|
|
|
100
99
|
session_param_and_headers = {"parameters": parameters, "headers": headers}
|
|
101
100
|
return session_param_and_headers
|
|
@@ -342,7 +341,9 @@ class Results(MutableMapping, MutableSequence):
|
|
|
342
341
|
)
|
|
343
342
|
|
|
344
343
|
def as_list(self):
|
|
345
|
-
return "\n".join(
|
|
344
|
+
return "\n".join(
|
|
345
|
+
[json.dumps(item, indent=4, separators=(",", ": ")) for item in self._items()]
|
|
346
|
+
)
|
|
346
347
|
|
|
347
348
|
def __str__(self):
|
|
348
349
|
return str(
|
{domaintools_api_test_upload-2.6.1 → domaintools_api_test_upload-2.6.3}/domaintools/cli/api.py
RENAMED
|
@@ -222,7 +222,6 @@ class DTCLICommand:
|
|
|
222
222
|
header_authentication=header_authentication,
|
|
223
223
|
)
|
|
224
224
|
dt_api_func = getattr(dt_api, name)
|
|
225
|
-
|
|
226
225
|
params = params | kwargs
|
|
227
226
|
|
|
228
227
|
response = dt_api_func(**params)
|
|
@@ -241,7 +240,7 @@ class DTCLICommand:
|
|
|
241
240
|
description=f"Printing the results with format of {response_format}...",
|
|
242
241
|
)
|
|
243
242
|
# use rich `print` command to prettify the ouput in sys.stdout
|
|
244
|
-
if response.product in RTTF_PRODUCTS_LIST:
|
|
243
|
+
if name not in ("available_api_calls",) and response.product in RTTF_PRODUCTS_LIST:
|
|
245
244
|
for feeds in response.response():
|
|
246
245
|
print(feeds)
|
|
247
246
|
else:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Endpoint(Enum):
|
|
5
|
+
FEED = "feed"
|
|
6
|
+
DOWNLOAD = "download"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Source(Enum):
|
|
10
|
+
API = "api"
|
|
11
|
+
S3 = "s3"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OutputFormat(Enum):
|
|
15
|
+
JSONL = "jsonl"
|
|
16
|
+
CSV = "csv"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
HEADER_ACCEPT_KEY_CSV_FORMAT = "text/csv"
|
|
20
|
+
|
|
21
|
+
ENDPOINT_TO_SOURCE_MAP = {
|
|
22
|
+
Endpoint.FEED.value: Source.API,
|
|
23
|
+
Endpoint.DOWNLOAD.value: Source.S3,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
RTTF_PRODUCTS_LIST = [
|
|
27
|
+
"newly-active-domains-feed-(api)",
|
|
28
|
+
"newly-active-domains-feed-(s3)",
|
|
29
|
+
"newly-observed-domains-feed-(api)",
|
|
30
|
+
"newly-observed-domains-feed-(s3)",
|
|
31
|
+
"newly-observed-hosts-feed-(api)",
|
|
32
|
+
"newly-observed-hosts-feed-(s3)",
|
|
33
|
+
"real-time-domain-hotlist-(api)",
|
|
34
|
+
"real-time-domain-hotlist-(s3)",
|
|
35
|
+
"domain-registration-data-access-protocol-feed-(api)",
|
|
36
|
+
"domain-registration-data-access-protocol-feed-(s3)",
|
|
37
|
+
"real-time-domain-risk-(api)",
|
|
38
|
+
"real-time-domain-risk-(s3)",
|
|
39
|
+
"real-time-domain-discovery-feed-(api)",
|
|
40
|
+
"real-time-domain-discovery-feed-(s3)",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
RTTF_PRODUCTS_CMD_MAPPING = {
|
|
44
|
+
"newly-active-domains-feed-(api)": "nad",
|
|
45
|
+
"newly-active-domains-feed-(s3)": "nad",
|
|
46
|
+
"newly-observed-domains-feed-(api)": "nod",
|
|
47
|
+
"newly-observed-domains-feed-(s3)": "nod",
|
|
48
|
+
"newly-observed-hosts-feed-(api)": "noh",
|
|
49
|
+
"newly-observed-hosts-feed-(s3)": "noh",
|
|
50
|
+
"real-time-domain-hotlist-(api)": "domainhotlist",
|
|
51
|
+
"real-time-domain-hotlist-(s3)": "domainhotlist",
|
|
52
|
+
"domain-registration-data-access-protocol-feed-(api)": "domainrdap",
|
|
53
|
+
"domain-registration-data-access-protocol-feed-(s3)": "domainrdap",
|
|
54
|
+
"real-time-domain-risk-(api)": "realtime_domain_risk",
|
|
55
|
+
"real-time-domain-risk-(s3)": "realtime_domain_risk",
|
|
56
|
+
"real-time-domain-discovery-feed-(api)": "domaindiscovery",
|
|
57
|
+
"real-time-domain-discovery-feed-(s3)": "domaindiscovery",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
SPECS_MAPPING = {
|
|
61
|
+
"iris": "domaintools/specs/iris-openapi.yaml",
|
|
62
|
+
# "rttf": "domaintools/specs/feeds-openapi.yaml",
|
|
63
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from typing import List, Union
|
|
4
|
+
|
|
5
|
+
from domaintools.docstring_patcher import DocstringPatcher
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def api_endpoint(spec_name: str, path: str, methods: Union[str, List[str]]):
|
|
9
|
+
"""
|
|
10
|
+
Decorator to tag a method as an API endpoint.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
spec_name: The key for the spec in api_instance.specs
|
|
14
|
+
path: The API path (e.g., "/users")
|
|
15
|
+
methods: A single method ("get") or list of methods (["get", "post"])
|
|
16
|
+
that this function handles.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def decorator(func):
|
|
20
|
+
func._api_spec_name = spec_name
|
|
21
|
+
func._api_path = path
|
|
22
|
+
|
|
23
|
+
# Always store the methods as a list
|
|
24
|
+
if isinstance(methods, str):
|
|
25
|
+
func._api_methods = [methods]
|
|
26
|
+
else:
|
|
27
|
+
func._api_methods = methods
|
|
28
|
+
|
|
29
|
+
@functools.wraps(func)
|
|
30
|
+
def wrapper(self, *args, **kwargs):
|
|
31
|
+
return func(*args, **kwargs)
|
|
32
|
+
|
|
33
|
+
# Copy all tags to the wrapper
|
|
34
|
+
wrapper._api_spec_name = func._api_spec_name
|
|
35
|
+
wrapper._api_path = func._api_path
|
|
36
|
+
wrapper._api_methods = func._api_methods
|
|
37
|
+
return wrapper
|
|
38
|
+
|
|
39
|
+
return decorator
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def auto_patch_docstrings(cls):
|
|
43
|
+
original_init = cls.__init__
|
|
44
|
+
|
|
45
|
+
@functools.wraps(original_init)
|
|
46
|
+
def new_init(self, *args, **kwargs):
|
|
47
|
+
original_init(self, *args, **kwargs)
|
|
48
|
+
try:
|
|
49
|
+
# We instantiate our patcher and run it
|
|
50
|
+
patcher = DocstringPatcher()
|
|
51
|
+
patcher.patch(self)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Auto-patching failed: {e}")
|
|
54
|
+
|
|
55
|
+
cls.__init__ = new_init
|
|
56
|
+
|
|
57
|
+
return cls
|