contrastapi 1.22.3__py3-none-any.whl

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.
@@ -0,0 +1,134 @@
1
+ """ATLAS namespace — MITRE ATLAS AI/ML attack catalog (techniques + case studies)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from urllib.parse import quote
7
+
8
+ from .._transport import _AsyncTransport, _SyncTransport
9
+ from ..models import (
10
+ AtlasCaseStudyResponse,
11
+ AtlasCaseStudySearchResponse,
12
+ AtlasTechniqueResponse,
13
+ AtlasTechniqueSearchResponse,
14
+ BulkAtlasTechniqueResponse,
15
+ )
16
+
17
+
18
+ def _enc(value: str) -> str:
19
+ if value is None or value == "":
20
+ raise ValueError("Missing required parameter")
21
+ return quote(str(value), safe="")
22
+
23
+
24
+ def _resolve_keyword(keyword: str | None, q: str | None) -> str | None:
25
+ """Server param is `keyword`; SDK accepts `keyword=` or back-compat `q=` (Node parity)."""
26
+ if keyword is not None and q is not None:
27
+ raise ValueError("Pass only one of `keyword` or `q` (q is a back-compat alias)")
28
+ return keyword if keyword is not None else q
29
+
30
+
31
+ def _technique_search_params(
32
+ *,
33
+ keyword: str | None = None,
34
+ q: str | None = None,
35
+ tactic: str | None = None,
36
+ maturity: str | None = None,
37
+ limit: int | None = None,
38
+ offset: int | None = None,
39
+ include: str | None = None,
40
+ exclude_id: str | None = None,
41
+ ) -> dict[str, Any]:
42
+ params: dict[str, Any] = {}
43
+ kw = _resolve_keyword(keyword, q)
44
+ if kw is not None:
45
+ params["keyword"] = kw
46
+ if tactic is not None:
47
+ params["tactic"] = tactic
48
+ if maturity is not None:
49
+ params["maturity"] = maturity
50
+ if limit is not None:
51
+ params["limit"] = limit
52
+ if offset is not None:
53
+ params["offset"] = offset
54
+ if include is not None:
55
+ params["include"] = include
56
+ if exclude_id is not None:
57
+ params["exclude_id"] = exclude_id
58
+ return params
59
+
60
+
61
+ def _case_study_search_params(
62
+ *,
63
+ keyword: str | None = None,
64
+ q: str | None = None,
65
+ target_type: str | None = None,
66
+ limit: int | None = None,
67
+ offset: int | None = None,
68
+ include: str | None = None,
69
+ ) -> dict[str, Any]:
70
+ params: dict[str, Any] = {}
71
+ kw = _resolve_keyword(keyword, q)
72
+ if kw is not None:
73
+ params["keyword"] = kw
74
+ if target_type is not None:
75
+ params["target_type"] = target_type
76
+ if limit is not None:
77
+ params["limit"] = limit
78
+ if offset is not None:
79
+ params["offset"] = offset
80
+ if include is not None:
81
+ params["include"] = include
82
+ return params
83
+
84
+
85
+ def _validate_bulk(ids: list[str]) -> list[str]:
86
+ if not isinstance(ids, list) or not all(isinstance(i, str) for i in ids):
87
+ raise ValueError("technique_ids must be a list of strings")
88
+ return ids
89
+
90
+
91
+ class Atlas:
92
+ def __init__(self, transport: _SyncTransport) -> None:
93
+ self._t = transport
94
+
95
+ def technique(self, technique_id: str) -> AtlasTechniqueResponse:
96
+ return self._t.get(f"/v1/atlas/{_enc(technique_id)}")
97
+
98
+ def technique_search(self, **kwargs: Any) -> AtlasTechniqueSearchResponse:
99
+ return self._t.get("/v1/atlas/techniques", params=_technique_search_params(**kwargs))
100
+
101
+ def bulk_technique_lookup(self, technique_ids: list[str]) -> BulkAtlasTechniqueResponse:
102
+ return self._t.post(
103
+ "/v1/atlas/techniques/bulk",
104
+ json_body={"technique_ids": _validate_bulk(technique_ids)},
105
+ )
106
+
107
+ def case_study(self, case_study_id: str) -> AtlasCaseStudyResponse:
108
+ return self._t.get(f"/v1/atlas/case-studies/{_enc(case_study_id)}")
109
+
110
+ def case_study_search(self, **kwargs: Any) -> AtlasCaseStudySearchResponse:
111
+ return self._t.get("/v1/atlas/case-studies", params=_case_study_search_params(**kwargs))
112
+
113
+
114
+ class AsyncAtlas:
115
+ def __init__(self, transport: _AsyncTransport) -> None:
116
+ self._t = transport
117
+
118
+ async def technique(self, technique_id: str) -> AtlasTechniqueResponse:
119
+ return await self._t.aget(f"/v1/atlas/{_enc(technique_id)}")
120
+
121
+ async def technique_search(self, **kwargs: Any) -> AtlasTechniqueSearchResponse:
122
+ return await self._t.aget("/v1/atlas/techniques", params=_technique_search_params(**kwargs))
123
+
124
+ async def bulk_technique_lookup(self, technique_ids: list[str]) -> BulkAtlasTechniqueResponse:
125
+ return await self._t.apost(
126
+ "/v1/atlas/techniques/bulk",
127
+ json_body={"technique_ids": _validate_bulk(technique_ids)},
128
+ )
129
+
130
+ async def case_study(self, case_study_id: str) -> AtlasCaseStudyResponse:
131
+ return await self._t.aget(f"/v1/atlas/case-studies/{_enc(case_study_id)}")
132
+
133
+ async def case_study_search(self, **kwargs: Any) -> AtlasCaseStudySearchResponse:
134
+ return await self._t.aget("/v1/atlas/case-studies", params=_case_study_search_params(**kwargs))
@@ -0,0 +1,125 @@
1
+ """CVE namespace — vulnerabilities, KEV, CWE, exploits, bulk lookup, leading list."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from urllib.parse import quote
7
+
8
+ from .._transport import _AsyncTransport, _SyncTransport
9
+ from ..models import (
10
+ BulkCveResponse,
11
+ CveResponse,
12
+ CveSearchResponse,
13
+ CweLookupResponse,
14
+ ExploitResponse,
15
+ KevDetailResponse,
16
+ )
17
+
18
+
19
+ def _enc(value: str) -> str:
20
+ if value is None or value == "":
21
+ raise ValueError("Missing required parameter")
22
+ return quote(str(value), safe="")
23
+
24
+
25
+ def _search_params(
26
+ *,
27
+ product: str | None = None,
28
+ severity: str | None = None,
29
+ days: int | None = None,
30
+ limit: int | None = None,
31
+ ) -> dict[str, Any]:
32
+ params: dict[str, Any] = {}
33
+ if product is not None:
34
+ params["product"] = product
35
+ if severity is not None:
36
+ params["severity"] = severity
37
+ if days is not None:
38
+ params["days"] = days
39
+ if limit is not None:
40
+ params["limit"] = limit
41
+ return params
42
+
43
+
44
+ def _leading_params(
45
+ *,
46
+ limit: int | None = None,
47
+ offset: int | None = None,
48
+ include: str | None = None,
49
+ ) -> dict[str, Any]:
50
+ params: dict[str, Any] = {}
51
+ if limit is not None:
52
+ params["limit"] = limit
53
+ if offset is not None:
54
+ params["offset"] = offset
55
+ if include is not None:
56
+ params["include"] = include
57
+ return params
58
+
59
+
60
+ def _validate_bulk_ids(ids: list[str]) -> list[str]:
61
+ if not isinstance(ids, list) or not all(isinstance(c, str) for c in ids):
62
+ raise ValueError("cve_ids must be a list of strings")
63
+ return ids
64
+
65
+
66
+ class Cve:
67
+ def __init__(self, transport: _SyncTransport) -> None:
68
+ self._t = transport
69
+
70
+ def lookup(self, cve_id: str) -> CveResponse:
71
+ return self._t.get(f"/v1/cve/{_enc(cve_id)}")
72
+
73
+ def search(self, **kwargs: Any) -> CveSearchResponse:
74
+ return self._t.get("/v1/cves", params=_search_params(**kwargs))
75
+
76
+ def leading(self, **kwargs: Any) -> CveSearchResponse:
77
+ return self._t.get("/v1/cve/leading", params=_leading_params(**kwargs))
78
+
79
+ def kev(self, cve_id: str) -> KevDetailResponse:
80
+ return self._t.get(f"/v1/kev/{_enc(cve_id)}")
81
+
82
+ def exploit(self, cve_id: str) -> ExploitResponse:
83
+ return self._t.get(f"/v1/exploit/{_enc(cve_id)}")
84
+
85
+ def bulk(self, cve_ids: list[str]) -> BulkCveResponse:
86
+ return self._t.post("/v1/cves/bulk", json_body={"cve_ids": _validate_bulk_ids(cve_ids)})
87
+
88
+
89
+ class Cwe:
90
+ def __init__(self, transport: _SyncTransport) -> None:
91
+ self._t = transport
92
+
93
+ def lookup(self, cwe_id: str) -> CweLookupResponse:
94
+ return self._t.get(f"/v1/cwe/{_enc(cwe_id)}")
95
+
96
+
97
+ class AsyncCve:
98
+ def __init__(self, transport: _AsyncTransport) -> None:
99
+ self._t = transport
100
+
101
+ async def lookup(self, cve_id: str) -> CveResponse:
102
+ return await self._t.aget(f"/v1/cve/{_enc(cve_id)}")
103
+
104
+ async def search(self, **kwargs: Any) -> CveSearchResponse:
105
+ return await self._t.aget("/v1/cves", params=_search_params(**kwargs))
106
+
107
+ async def leading(self, **kwargs: Any) -> CveSearchResponse:
108
+ return await self._t.aget("/v1/cve/leading", params=_leading_params(**kwargs))
109
+
110
+ async def kev(self, cve_id: str) -> KevDetailResponse:
111
+ return await self._t.aget(f"/v1/kev/{_enc(cve_id)}")
112
+
113
+ async def exploit(self, cve_id: str) -> ExploitResponse:
114
+ return await self._t.aget(f"/v1/exploit/{_enc(cve_id)}")
115
+
116
+ async def bulk(self, cve_ids: list[str]) -> BulkCveResponse:
117
+ return await self._t.apost("/v1/cves/bulk", json_body={"cve_ids": _validate_bulk_ids(cve_ids)})
118
+
119
+
120
+ class AsyncCwe:
121
+ def __init__(self, transport: _AsyncTransport) -> None:
122
+ self._t = transport
123
+
124
+ async def lookup(self, cwe_id: str) -> CweLookupResponse:
125
+ return await self._t.aget(f"/v1/cwe/{_enc(cwe_id)}")
@@ -0,0 +1,124 @@
1
+ """D3FEND namespace — MITRE D3FEND defense catalog mapped to ATT&CK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from urllib.parse import quote
7
+
8
+ from .._transport import _AsyncTransport, _SyncTransport
9
+ from ..models import (
10
+ D3fendCoverageResponse,
11
+ D3fendDefenseResponse,
12
+ D3fendDefenseSearchResponse,
13
+ D3fendForAttackResponse,
14
+ )
15
+
16
+
17
+ def _enc(value: str) -> str:
18
+ if value is None or value == "":
19
+ raise ValueError("Missing required parameter")
20
+ return quote(str(value), safe="")
21
+
22
+
23
+ def _resolve_keyword(keyword: str | None, q: str | None) -> str | None:
24
+ """Server param is `keyword`; SDK accepts `keyword=` or back-compat `q=` (Node parity).
25
+ Passing both raises so we don't silently pick a winner.
26
+ """
27
+ if keyword is not None and q is not None:
28
+ raise ValueError("Pass only one of `keyword` or `q` (q is a back-compat alias)")
29
+ return keyword if keyword is not None else q
30
+
31
+
32
+ def _defense_search_params(
33
+ *,
34
+ keyword: str | None = None,
35
+ q: str | None = None,
36
+ tactic: str | None = None,
37
+ artifact: str | None = None,
38
+ limit: int | None = None,
39
+ offset: int | None = None,
40
+ include: str | None = None,
41
+ exclude_id: str | None = None,
42
+ ) -> dict[str, Any]:
43
+ params: dict[str, Any] = {}
44
+ kw = _resolve_keyword(keyword, q)
45
+ if kw is not None:
46
+ params["keyword"] = kw
47
+ if tactic is not None:
48
+ params["tactic"] = tactic
49
+ if artifact is not None:
50
+ params["artifact"] = artifact
51
+ if limit is not None:
52
+ params["limit"] = limit
53
+ if offset is not None:
54
+ params["offset"] = offset
55
+ if include is not None:
56
+ params["include"] = include
57
+ if exclude_id is not None:
58
+ params["exclude_id"] = exclude_id
59
+ return params
60
+
61
+
62
+ def _for_attack_params(
63
+ *,
64
+ include: str | None = None,
65
+ exclude_id: str | None = None,
66
+ ) -> dict[str, Any]:
67
+ params: dict[str, Any] = {}
68
+ if include is not None:
69
+ params["include"] = include
70
+ if exclude_id is not None:
71
+ params["exclude_id"] = exclude_id
72
+ return params
73
+
74
+
75
+ def _validate_attack_ids(ids: list[str]) -> list[str]:
76
+ if not isinstance(ids, list) or not all(isinstance(i, str) for i in ids):
77
+ raise ValueError("attack_technique_ids must be a list of strings")
78
+ return ids
79
+
80
+
81
+ class D3fend:
82
+ def __init__(self, transport: _SyncTransport) -> None:
83
+ self._t = transport
84
+
85
+ def defense(self, defense_id: str) -> D3fendDefenseResponse:
86
+ return self._t.get(f"/v1/d3fend/{_enc(defense_id)}")
87
+
88
+ def defense_search(self, **kwargs: Any) -> D3fendDefenseSearchResponse:
89
+ return self._t.get("/v1/d3fend/defenses", params=_defense_search_params(**kwargs))
90
+
91
+ def defense_for_attack(self, attack_technique_id: str, **kwargs: Any) -> D3fendForAttackResponse:
92
+ return self._t.get(
93
+ f"/v1/d3fend/attack/{_enc(attack_technique_id)}",
94
+ params=_for_attack_params(**kwargs),
95
+ )
96
+
97
+ def coverage(self, attack_technique_ids: list[str]) -> D3fendCoverageResponse:
98
+ return self._t.post(
99
+ "/v1/d3fend/coverage",
100
+ json_body={"attack_technique_ids": _validate_attack_ids(attack_technique_ids)},
101
+ )
102
+
103
+
104
+ class AsyncD3fend:
105
+ def __init__(self, transport: _AsyncTransport) -> None:
106
+ self._t = transport
107
+
108
+ async def defense(self, defense_id: str) -> D3fendDefenseResponse:
109
+ return await self._t.aget(f"/v1/d3fend/{_enc(defense_id)}")
110
+
111
+ async def defense_search(self, **kwargs: Any) -> D3fendDefenseSearchResponse:
112
+ return await self._t.aget("/v1/d3fend/defenses", params=_defense_search_params(**kwargs))
113
+
114
+ async def defense_for_attack(self, attack_technique_id: str, **kwargs: Any) -> D3fendForAttackResponse:
115
+ return await self._t.aget(
116
+ f"/v1/d3fend/attack/{_enc(attack_technique_id)}",
117
+ params=_for_attack_params(**kwargs),
118
+ )
119
+
120
+ async def coverage(self, attack_technique_ids: list[str]) -> D3fendCoverageResponse:
121
+ return await self._t.apost(
122
+ "/v1/d3fend/coverage",
123
+ json_body={"attack_technique_ids": _validate_attack_ids(attack_technique_ids)},
124
+ )
@@ -0,0 +1,129 @@
1
+ """Domain namespace — DNS, WHOIS, SSL, subdomains, tech, threat, monitor, vulns, audit, wayback."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from urllib.parse import quote
7
+
8
+ from .._transport import _AsyncTransport, _SyncTransport
9
+ from ..models import (
10
+ AuditResponse,
11
+ BulkDomainResponse,
12
+ CertsResponse,
13
+ DnsResponse,
14
+ DomainReportResponse,
15
+ SslResponse,
16
+ SubdomainsResponse,
17
+ TechResponse,
18
+ ThreatResponse,
19
+ WaybackResponse,
20
+ WhoisResponse,
21
+ )
22
+
23
+
24
+ def _enc(value: str) -> str:
25
+ if value is None or value == "":
26
+ raise ValueError("Missing required parameter")
27
+ return quote(str(value), safe="")
28
+
29
+
30
+ def _validate_domains(domains: list[str]) -> list[str]:
31
+ if not isinstance(domains, list) or not all(isinstance(d, str) for d in domains):
32
+ raise ValueError("domains must be a list of strings")
33
+ return domains
34
+
35
+
36
+ def _report_params(lite: bool) -> dict[str, Any] | None:
37
+ """`lite=True` → {"lite": "true"}; `lite=False` → None (clean URL).
38
+
39
+ Server treats only the literal string `true` as truthy (lowercase).
40
+ """
41
+ return {"lite": "true"} if lite else None
42
+
43
+
44
+ class Domain:
45
+ def __init__(self, transport: _SyncTransport) -> None:
46
+ self._t = transport
47
+
48
+ def report(self, domain: str, *, lite: bool = False) -> DomainReportResponse:
49
+ return self._t.get(f"/v1/domain/{_enc(domain)}", params=_report_params(lite))
50
+
51
+ def dns(self, domain: str) -> DnsResponse:
52
+ return self._t.get(f"/v1/dns/{_enc(domain)}")
53
+
54
+ def whois(self, domain: str) -> WhoisResponse:
55
+ return self._t.get(f"/v1/whois/{_enc(domain)}")
56
+
57
+ def subdomains(self, domain: str) -> SubdomainsResponse:
58
+ return self._t.get(f"/v1/subdomains/{_enc(domain)}")
59
+
60
+ def certs(self, domain: str) -> CertsResponse:
61
+ return self._t.get(f"/v1/certs/{_enc(domain)}")
62
+
63
+ def ssl(self, domain: str) -> SslResponse:
64
+ return self._t.get(f"/v1/ssl/{_enc(domain)}")
65
+
66
+ def tech(self, domain: str) -> TechResponse:
67
+ return self._t.get(f"/v1/tech/{_enc(domain)}")
68
+
69
+ def threat(self, domain: str) -> ThreatResponse:
70
+ return self._t.get(f"/v1/threat/{_enc(domain)}")
71
+
72
+ def monitor(self, domain: str) -> dict[str, Any]:
73
+ return self._t.get(f"/v1/monitor/{_enc(domain)}")
74
+
75
+ def vulns(self, domain: str) -> dict[str, Any]:
76
+ return self._t.get(f"/v1/domain/{_enc(domain)}/vulns")
77
+
78
+ def audit(self, domain: str) -> AuditResponse:
79
+ return self._t.get(f"/v1/audit/{_enc(domain)}")
80
+
81
+ def wayback(self, domain: str) -> WaybackResponse:
82
+ return self._t.get(f"/v1/archive/{_enc(domain)}")
83
+
84
+ def bulk(self, domains: list[str]) -> BulkDomainResponse:
85
+ return self._t.post("/v1/domains/bulk", json_body={"domains": _validate_domains(domains)})
86
+
87
+
88
+ class AsyncDomain:
89
+ def __init__(self, transport: _AsyncTransport) -> None:
90
+ self._t = transport
91
+
92
+ async def report(self, domain: str, *, lite: bool = False) -> DomainReportResponse:
93
+ return await self._t.aget(f"/v1/domain/{_enc(domain)}", params=_report_params(lite))
94
+
95
+ async def dns(self, domain: str) -> DnsResponse:
96
+ return await self._t.aget(f"/v1/dns/{_enc(domain)}")
97
+
98
+ async def whois(self, domain: str) -> WhoisResponse:
99
+ return await self._t.aget(f"/v1/whois/{_enc(domain)}")
100
+
101
+ async def subdomains(self, domain: str) -> SubdomainsResponse:
102
+ return await self._t.aget(f"/v1/subdomains/{_enc(domain)}")
103
+
104
+ async def certs(self, domain: str) -> CertsResponse:
105
+ return await self._t.aget(f"/v1/certs/{_enc(domain)}")
106
+
107
+ async def ssl(self, domain: str) -> SslResponse:
108
+ return await self._t.aget(f"/v1/ssl/{_enc(domain)}")
109
+
110
+ async def tech(self, domain: str) -> TechResponse:
111
+ return await self._t.aget(f"/v1/tech/{_enc(domain)}")
112
+
113
+ async def threat(self, domain: str) -> ThreatResponse:
114
+ return await self._t.aget(f"/v1/threat/{_enc(domain)}")
115
+
116
+ async def monitor(self, domain: str) -> dict[str, Any]:
117
+ return await self._t.aget(f"/v1/monitor/{_enc(domain)}")
118
+
119
+ async def vulns(self, domain: str) -> dict[str, Any]:
120
+ return await self._t.aget(f"/v1/domain/{_enc(domain)}/vulns")
121
+
122
+ async def audit(self, domain: str) -> AuditResponse:
123
+ return await self._t.aget(f"/v1/audit/{_enc(domain)}")
124
+
125
+ async def wayback(self, domain: str) -> WaybackResponse:
126
+ return await self._t.aget(f"/v1/archive/{_enc(domain)}")
127
+
128
+ async def bulk(self, domains: list[str]) -> BulkDomainResponse:
129
+ return await self._t.apost("/v1/domains/bulk", json_body={"domains": _validate_domains(domains)})
@@ -0,0 +1,151 @@
1
+ """Compact namespaces for endpoints with 1-2 methods each:
2
+ ip, asn, email, phone, password, username.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from urllib.parse import quote
8
+
9
+ from .._transport import _AsyncTransport, _SyncTransport
10
+ from ..models import (
11
+ AsnResponse,
12
+ DisposableResponse,
13
+ EmailMxResponse,
14
+ IpLookupResponse,
15
+ PasswordResponse,
16
+ PhoneLookupResponse,
17
+ ThreatReportResponse,
18
+ UsernameLookupResponse,
19
+ )
20
+
21
+
22
+ def _enc(value: str) -> str:
23
+ if value is None or value == "":
24
+ raise ValueError("Missing required parameter")
25
+ return quote(str(value), safe="")
26
+
27
+
28
+ # --- IP ---
29
+
30
+
31
+ class Ip:
32
+ def __init__(self, transport: _SyncTransport) -> None:
33
+ self._t = transport
34
+
35
+ def lookup(self, ip: str) -> IpLookupResponse:
36
+ return self._t.get(f"/v1/ip/{_enc(ip)}")
37
+
38
+ def threat_report(self, ip: str) -> ThreatReportResponse:
39
+ return self._t.get(f"/v1/threat-report/{_enc(ip)}")
40
+
41
+
42
+ class AsyncIp:
43
+ def __init__(self, transport: _AsyncTransport) -> None:
44
+ self._t = transport
45
+
46
+ async def lookup(self, ip: str) -> IpLookupResponse:
47
+ return await self._t.aget(f"/v1/ip/{_enc(ip)}")
48
+
49
+ async def threat_report(self, ip: str) -> ThreatReportResponse:
50
+ return await self._t.aget(f"/v1/threat-report/{_enc(ip)}")
51
+
52
+
53
+ # --- ASN ---
54
+
55
+
56
+ class Asn:
57
+ def __init__(self, transport: _SyncTransport) -> None:
58
+ self._t = transport
59
+
60
+ def lookup(self, target: str) -> AsnResponse:
61
+ return self._t.get(f"/v1/asn/{_enc(target)}")
62
+
63
+
64
+ class AsyncAsn:
65
+ def __init__(self, transport: _AsyncTransport) -> None:
66
+ self._t = transport
67
+
68
+ async def lookup(self, target: str) -> AsnResponse:
69
+ return await self._t.aget(f"/v1/asn/{_enc(target)}")
70
+
71
+
72
+ # --- Email ---
73
+
74
+
75
+ class Email:
76
+ def __init__(self, transport: _SyncTransport) -> None:
77
+ self._t = transport
78
+
79
+ def mx(self, domain: str) -> EmailMxResponse:
80
+ return self._t.get(f"/v1/email/mx/{_enc(domain)}")
81
+
82
+ def disposable(self, email: str) -> DisposableResponse:
83
+ return self._t.get(f"/v1/email/disposable/{_enc(email)}")
84
+
85
+
86
+ class AsyncEmail:
87
+ def __init__(self, transport: _AsyncTransport) -> None:
88
+ self._t = transport
89
+
90
+ async def mx(self, domain: str) -> EmailMxResponse:
91
+ return await self._t.aget(f"/v1/email/mx/{_enc(domain)}")
92
+
93
+ async def disposable(self, email: str) -> DisposableResponse:
94
+ return await self._t.aget(f"/v1/email/disposable/{_enc(email)}")
95
+
96
+
97
+ # --- Phone ---
98
+
99
+
100
+ class Phone:
101
+ def __init__(self, transport: _SyncTransport) -> None:
102
+ self._t = transport
103
+
104
+ def lookup(self, number: str) -> PhoneLookupResponse:
105
+ return self._t.get(f"/v1/phone/{_enc(number)}")
106
+
107
+
108
+ class AsyncPhone:
109
+ def __init__(self, transport: _AsyncTransport) -> None:
110
+ self._t = transport
111
+
112
+ async def lookup(self, number: str) -> PhoneLookupResponse:
113
+ return await self._t.aget(f"/v1/phone/{_enc(number)}")
114
+
115
+
116
+ # --- Password ---
117
+
118
+
119
+ class Password:
120
+ def __init__(self, transport: _SyncTransport) -> None:
121
+ self._t = transport
122
+
123
+ def check(self, sha1_hash: str) -> PasswordResponse:
124
+ return self._t.get(f"/v1/password/{_enc(sha1_hash)}")
125
+
126
+
127
+ class AsyncPassword:
128
+ def __init__(self, transport: _AsyncTransport) -> None:
129
+ self._t = transport
130
+
131
+ async def check(self, sha1_hash: str) -> PasswordResponse:
132
+ return await self._t.aget(f"/v1/password/{_enc(sha1_hash)}")
133
+
134
+
135
+ # --- Username (NEW vs Node SDK v1.3.0) ---
136
+
137
+
138
+ class Username:
139
+ def __init__(self, transport: _SyncTransport) -> None:
140
+ self._t = transport
141
+
142
+ def lookup(self, username: str) -> UsernameLookupResponse:
143
+ return self._t.get(f"/v1/username/{_enc(username)}")
144
+
145
+
146
+ class AsyncUsername:
147
+ def __init__(self, transport: _AsyncTransport) -> None:
148
+ self._t = transport
149
+
150
+ async def lookup(self, username: str) -> UsernameLookupResponse:
151
+ return await self._t.aget(f"/v1/username/{_enc(username)}")