domaintools-api-test-upload 2.6.2__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.2/domaintools_api_test_upload.egg-info → domaintools_api_test_upload-2.6.3}/PKG-INFO +6 -2
- {domaintools_api_test_upload-2.6.2 → 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.2 → domaintools_api_test_upload-2.6.3}/domaintools/_version.py +1 -1
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/api.py +76 -42
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/base_results.py +4 -3
- {domaintools_api_test_upload-2.6.2 → 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.2 → domaintools_api_test_upload-2.6.3}/domaintools/results.py +36 -57
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/utils.py +5 -3
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3/domaintools_api_test_upload.egg-info}/PKG-INFO +6 -2
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/SOURCES.txt +3 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/pyproject.toml +1 -1
- {domaintools_api_test_upload-2.6.2 → 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.2/VERSION +0 -1
- domaintools_api_test_upload-2.6.2/domaintools/constants.py +0 -41
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/LICENSE +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/accounts.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/detects.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/domains.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/feeds.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/ips.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/commands/iris.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/constants.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/main.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/cli/utils.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/exceptions.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools/filters.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/dependency_links.txt +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/entry_points.txt +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/requires.txt +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools_api_test_upload.egg-info/top_level.txt +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/domaintools_async/__init__.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/setup.cfg +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/tests/test_api.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/tests/test_async.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/tests/test_cli.py +0 -0
- {domaintools_api_test_upload-2.6.2 → domaintools_api_test_upload-2.6.3}/tests/test_filters.py +0 -0
- {domaintools_api_test_upload-2.6.2 → 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`"""
|
|
@@ -127,11 +156,18 @@ class API(object):
|
|
|
127
156
|
hours = limit_hours and 3600 / float(limit_hours)
|
|
128
157
|
minutes = limit_minutes and 60 / float(limit_minutes)
|
|
129
158
|
|
|
130
|
-
self.limits[product["id"]] = {
|
|
159
|
+
self.limits[product["id"]] = {
|
|
160
|
+
"interval": timedelta(seconds=minutes or hours or default)
|
|
161
|
+
}
|
|
131
162
|
|
|
132
163
|
def _results(self, product, path, cls=Results, **kwargs):
|
|
133
164
|
"""Returns _results for the specified API path with the specified **kwargs parameters"""
|
|
134
|
-
if
|
|
165
|
+
if (
|
|
166
|
+
product != "account-information"
|
|
167
|
+
and self.rate_limit
|
|
168
|
+
and not self.limits_set
|
|
169
|
+
and not self.limits
|
|
170
|
+
):
|
|
135
171
|
always_sign_api_key_previous_value = self.always_sign_api_key
|
|
136
172
|
header_authentication_previous_value = self.header_authentication
|
|
137
173
|
self._rate_limit(product)
|
|
@@ -175,7 +211,9 @@ class API(object):
|
|
|
175
211
|
else:
|
|
176
212
|
raise ValueError(
|
|
177
213
|
"Invalid value '{0}' for 'key_sign_hash'. "
|
|
178
|
-
"Values available are {1}".format(
|
|
214
|
+
"Values available are {1}".format(
|
|
215
|
+
self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES)
|
|
216
|
+
)
|
|
179
217
|
)
|
|
180
218
|
|
|
181
219
|
parameters["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -187,7 +225,9 @@ class API(object):
|
|
|
187
225
|
|
|
188
226
|
def account_information(self, **kwargs):
|
|
189
227
|
"""Provides a snapshot of your accounts current API usage"""
|
|
190
|
-
return self._results(
|
|
228
|
+
return self._results(
|
|
229
|
+
"account-information", "/v1/account", items_path=("products",), **kwargs
|
|
230
|
+
)
|
|
191
231
|
|
|
192
232
|
def available_api_calls(self):
|
|
193
233
|
"""Provides a list of api calls that you can use based on your account information."""
|
|
@@ -209,13 +249,23 @@ class API(object):
|
|
|
209
249
|
if not api_call.startswith("_") and callable(getattr(API, api_call, None))
|
|
210
250
|
)
|
|
211
251
|
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
]
|
|
218
|
-
|
|
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)
|
|
219
269
|
|
|
220
270
|
def brand_monitor(self, query, exclude=None, domain_status=None, days_back=None, **kwargs):
|
|
221
271
|
"""Pass in one or more terms as a list or separated by the pipe character ( | )"""
|
|
@@ -380,7 +430,9 @@ class API(object):
|
|
|
380
430
|
|
|
381
431
|
def reverse_ip(self, domain=None, limit=None, **kwargs):
|
|
382
432
|
"""Pass in a domain name."""
|
|
383
|
-
return self._results(
|
|
433
|
+
return self._results(
|
|
434
|
+
"reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs
|
|
435
|
+
)
|
|
384
436
|
|
|
385
437
|
def host_domains(self, ip=None, limit=None, **kwargs):
|
|
386
438
|
"""Pass in an IP address."""
|
|
@@ -554,8 +606,12 @@ class API(object):
|
|
|
554
606
|
younger_than_date = kwargs.pop("younger_than_date", {}) or None
|
|
555
607
|
older_than_date = kwargs.pop("older_than_date", {}) or None
|
|
556
608
|
updated_after = kwargs.pop("updated_after", {}) or None
|
|
557
|
-
include_domains_with_missing_field =
|
|
558
|
-
|
|
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
|
+
)
|
|
559
615
|
|
|
560
616
|
filtered_results = DTResultFilter(result_set=results).by(
|
|
561
617
|
[
|
|
@@ -608,6 +664,7 @@ class API(object):
|
|
|
608
664
|
**kwargs,
|
|
609
665
|
)
|
|
610
666
|
|
|
667
|
+
@api_endpoint(spec_name="iris", path="/v1/iris-investigate/", methods="post")
|
|
611
668
|
def iris_investigate(
|
|
612
669
|
self,
|
|
613
670
|
domains=None,
|
|
@@ -625,29 +682,6 @@ class API(object):
|
|
|
625
682
|
**kwargs,
|
|
626
683
|
):
|
|
627
684
|
"""Returns back a list of domains based on the provided filters.
|
|
628
|
-
The following filters are available beyond what is parameterized as kwargs:
|
|
629
|
-
|
|
630
|
-
- ip: Search for domains having this IP.
|
|
631
|
-
- email: Search for domains with this email in their data.
|
|
632
|
-
- email_domain: Search for domains where the email address uses this domain.
|
|
633
|
-
- nameserver_host: Search for domains with this nameserver.
|
|
634
|
-
- nameserver_domain: Search for domains with a nameserver that has this domain.
|
|
635
|
-
- nameserver_ip: Search for domains with a nameserver on this IP.
|
|
636
|
-
- registrar: Search for domains with this registrar.
|
|
637
|
-
- registrant: Search for domains with this registrant name.
|
|
638
|
-
- registrant_org: Search for domains with this registrant organization.
|
|
639
|
-
- mailserver_host: Search for domains with this mailserver.
|
|
640
|
-
- mailserver_domain: Search for domains with a mailserver that has this domain.
|
|
641
|
-
- mailserver_ip: Search for domains with a mailserver on this IP.
|
|
642
|
-
- redirect_domain: Search for domains which redirect to this domain.
|
|
643
|
-
- ssl_hash: Search for domains which have an SSL certificate with this hash.
|
|
644
|
-
- ssl_subject: Search for domains which have an SSL certificate with this subject string.
|
|
645
|
-
- ssl_email: Search for domains which have an SSL certificate with this email in it.
|
|
646
|
-
- ssl_org: Search for domains which have an SSL certificate with this organization in it.
|
|
647
|
-
- google_analytics: Search for domains which have this Google Analytics code.
|
|
648
|
-
- adsense: Search for domains which have this AdSense code.
|
|
649
|
-
- tld: Filter by TLD. Must be combined with another parameter.
|
|
650
|
-
- search_hash: Use search hash from Iris to bring back domains.
|
|
651
685
|
|
|
652
686
|
You can loop over results of your investigation as if it was a native Python list:
|
|
653
687
|
|
|
@@ -1279,7 +1313,7 @@ class API(object):
|
|
|
1279
1313
|
kwargs.pop("headers", None)
|
|
1280
1314
|
|
|
1281
1315
|
return self._results(
|
|
1282
|
-
f"domain-risk-
|
|
1316
|
+
f"real-time-domain-risk-({source})",
|
|
1283
1317
|
f"v1/{endpoint}/domainrisk/",
|
|
1284
1318
|
response_path=(),
|
|
1285
1319
|
cls=FeedsResults,
|
|
@@ -1313,7 +1347,7 @@ class API(object):
|
|
|
1313
1347
|
kwargs.pop("headers", None)
|
|
1314
1348
|
|
|
1315
1349
|
return self._results(
|
|
1316
|
-
f"domain-hotlist-
|
|
1350
|
+
f"real-time-domain-hotlist-({source})",
|
|
1317
1351
|
f"v1/{endpoint}/domainhotlist/",
|
|
1318
1352
|
response_path=(),
|
|
1319
1353
|
cls=FeedsResults,
|
{domaintools_api_test_upload-2.6.2 → 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.2 → 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
|